红黑树是一种自平衡二叉搜索树。红黑树和AVL树类似,但是最差情况下的插入和删除更快,查询时间稍微变慢,但仍然是O(log n)。
Linux红黑树实现在/lib/rbtree.c,头文件linux/rbtree.h
Linux红黑树对速度进行了优化,减少了一层indirection,提高了缓存命中率。和list一样,每个struct rb_node节点嵌入到数据结构中。
为了不丢失性能,需要自己实现插入和搜索函数。
结构体
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(*new, struct 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 *); |
正向遍历的时候,从最左侧的节点开始,每次都要花费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 *); |
有些特定的树并不能用前面的普通红黑树实现,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