ILD

内核数据结构之:rbtree
作者:YUAN JIANPENG 邮箱:yuanjp@hust.edu.cn
发布时间:2018-7-11 站点:Inside Linux Development

红黑树是一种自平衡二叉搜索树。红黑树和AVL树类似,但是最差情况下的插入和删除更快,查询时间稍微变慢,但仍然是O(log n)。


Linux红黑树实现在/lib/rbtree.c,头文件linux/rbtree.h


Linux红黑树对速度进行了优化,减少了一层indirection,提高了缓存命中率。和list一样,每个struct rb_node节点嵌入到数据结构中。


为了不丢失性能,需要自己实现插入和搜索函数。

1 rbtree

结构体

1
2
3
4
5
6
7
8
9
10
struct rb_node {
    unsigned long  __rb_parent_color;
    struct rb_node *rb_right;                                                                                                                                                                                       
    struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));
    /* The alignment might seem pointless, but allegedly CRIS needs it */
 
struct rb_root {
    struct rb_node *rb_node;
};

__rb_parent_color用来存储parent节点的地址和颜色,低2位用于父节点的颜色。


为了学习,定义people结构体,将rb_node嵌入其中。定义rb_root变量people_tree。

1
2
3
4
5
6
7
static struct rb_root people_tree = RB_ROOT;
 
struct people
{
    int score;
    struct rb_node node;
};


插入需要自己找到插入的位置,然后插入,然后旋转上色。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int people_insert(struct rb_root *root, struct people *people)
{
      struct rb_node **new = &(root->rb_node), *parent = NULL;
      struct people *this;
 
      while (*new) {
          this = rb_entry(*newstruct people, node);
          parent = *new;
          if (people->score > this->score)
              new = &((*new)->rb_right);
          else if (people->score < this->score)
              new = &((*new)->rb_left);
          else
              return 0;
      }
      rb_link_node(&people->node, parent, new);
      rb_insert_color(&people->node, root);
      return 1;
}

parent是父节点,new是要插入的父节点的子节点的地址。

rb_link_node()插入节点:将parent的子节点指向新节点,将新节点的父设置为parent,将新节点的左右子节点设置为NULL。

rb_insert_color():进行上色旋转,保持为红黑树。


遍历:4个接口

1
2
3
4
extern struct rb_node *rb_next(const struct rb_node *);
extern struct rb_node *rb_prev(const struct rb_node *);
extern struct rb_node *rb_first(const struct rb_root *);
extern struct rb_node *rb_last(const struct rb_root *);

用于正向遍历和逆向遍历,正向遍历:

1
2
3
4
5
6
7
8
9
10
11
void people_interate(struct rb_root *root)
{
    struct rb_node *node = rb_first(root);
    struct people *this;
 
    while (node) {
        this = rb_entry(node, struct people, node);
        printk("%d\n"this->score);
        node = rb_next(node);
    }
}

使用rb_first获取最左边的节点,然后使用rb_next遍历所有的节点。逆向遍历是对应的另外两个接口。


搜索:自己写2叉树搜索:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct rb_node * people_search(struct rb_root *root, int score)
{
    struct rb_node *node = root->rb_node;
 
    while (node) {
        struct people *this = rb_entry(node, struct people, node);
        if (score < this->score)
            node = node->rb_left;
        else if (score > this->score)
            node = node->rb_right;
        else {
            return node;
        }
    }
    return NULL;
}


删除:使用封装好的接口:

1
extern void rb_erase(struct rb_node *, struct rb_root *);


2 cached rbtree

正向遍历的时候,从最左侧的节点开始,每次都要花费logN的代价寻找最左侧节点,为了解决这个问题,引出了cached rbtree,它添加一个指针变量存储最左侧节点。

1
2
3
4
struct rb_root_cached {
        struct rb_root rb_root;
        struct rb_node *rb_leftmost;
};


初始化

1
struct rb_root_cached mytree = RB_ROOT_CACHED;


涉及到最左的节点的,有cached接口:

1
2
3
  struct rb_node *rb_first_cached(struct rb_root_cached *tree);
  void rb_insert_color_cached(struct rb_node *, struct rb_root_cached *, bool);
  void rb_erase_cached(struct rb_node *node, struct rb_root_cached *);


3 augmented rbtree

有些特定的树并不能用前面的普通红黑树实现,Linux实现了一种增强红黑树,这种树,每个节点还存储了一个信息,该信息由所有的子节点确定。

区间树(interval tree)是典型的应用例子。区间树每个节点存储它的区间[lo, hi],同时还存储所有子节点的区间的最大值。树是按lo索引的二叉树。

增强树通过3个回调函数来维护:

A propagation callback

A copy callback

A tree rotation callback


参考

https://lwn.net/Articles/184495/

Document/rbtree.txt


Copyright © linuxdev.cc 2017-2024. Some Rights Reserved.