1. 什么是红黑树,它们有什么用?
------------------------------------------------
红黑树是一种自平衡二叉搜索树,用于存储可排序的 键/值 数据对。 这不同于 基数树(用于有效地存储稀疏数组,因此使用长整数索引来插入/访问/删除节点)和哈希表(不保持排序以便于按顺序遍历,并且必须针对 特定大小和哈希函数,其中 rbtrees 可以优雅地扩展存储任意键)。
红黑树类似于 AVL 树,但为插入和删除提供更快的实时有界最坏情况性能(最多分别旋转两次和三次,以平衡树),速度稍慢(但仍然是 O(log n)) 查找时间。
引用 Linux Weekly News:
内核中使用了许多红黑树。 Deadline 和 CFQ I/O 调度器使用 rbtrees 来跟踪请求; 数据包 CD/DVD 驱动程序也做同样的事情。 高分辨率计时器代码使用 rbtree 来组织未完成的计时器请求。 ext3 文件系统在红黑树中跟踪目录条目。 虚拟内存区域 (VMA)
使用红黑树进行跟踪,“分层令牌桶”调度程序中的 epoll 文件描述符、加密密钥和网络数据包也是如此。
本文档介绍了 Linux rbtree 实现的使用。 有关红黑树的性质和实施的更多信息,请参阅:
Linux Weekly News 关于红黑树的文章: http://lwn.net/Articles/184495/
维基百科关于红黑树的条目: http://en.wikipedia.org/wiki/Red-black_tree
2. 红黑树的Linux实现
-------------------------------
Linux 的 rbtree 实现存在于文件 “lib/rbtree.c” 中。 要使用它要 “#include <linux/rbtree.h>”。
Linux rbtree 实现针对速度进行了优化,因此比更传统的树实现少了一层间接(和更好的缓存局部性)。 不是使用指针来分隔 rb_node 和数据结构,而是将 struct rb_node 的每个实例嵌入到它组织的数据结构中。 而不是使用比较回调函数指针,用户应该编写自己的树搜索和插入函数,这些函数调用提供的 rbtree 函数。 锁定也由 rbtree 代码的用户决定。
(1) 创建一个新的 rbtree
---------------------
rbtree 树中的数据节点是包含 struct rb_node 成员的结构:
struct mytype { struct rb_node node; char *keystring; };
在处理指向嵌入式结构 rb_node 的指针时,可以使用标准的 container_of() 宏访问包含的数据结构。 此外,可以通过 rb_entry(node, type, member) 直接访问单个成员。
每个 rbtree 的根是一个 rb_root 结构,它通过以下方式初始化为空:
struct rb_root mytree = RB_ROOT;
(2) 在 rbtree 中搜索值
----------------------------------
为您的树编写搜索函数相当简单:从根开始,比较每个值,并根据需要遵循左侧或右侧分支。
例子:
struct mytype *my_search(struct rb_root *root, char *string) { struct rb_node *node = root->rb_node; while (node) { struct mytype *data = container_of(node, struct mytype, node); int result; result = strcmp(string, data->keystring); if (result < 0) node = node->rb_left; else if (result > 0) node = node->rb_right; else return data; } return NULL; }
(3) 将数据插入 rbtree
-----------------------------
在树中插入数据涉及首先搜索插入新节点的位置,然后插入节点并重新平衡(“重新着色”)树。
插入的搜索与之前的搜索不同,它查找要嫁接新节点的指针的位置。 为了重新平衡,新节点还需要一个到其父节点的链接。
例子:
int my_insert(struct rb_root *root, struct mytype *data) { struct rb_node **new = &(root->rb_node), *parent = NULL; /* Figure out where to put new node */ while (*new) { //*new为root->rb_node,然后往下遍历,直到指向的为NULL退出循环 struct mytype *this = container_of(*new, struct mytype, node); int result = strcmp(data->keystring, this->keystring); parent = *new; if (result < 0) new = &((*new)->rb_left); else if (result > 0) new = &((*new)->rb_right); else return FALSE; } /* Add new node and rebalance tree. */ rb_link_node(&data->node, parent, new); rb_insert_color(&data->node, root); return TRUE; }
(5) 删除或替换 rbtree 中的现有数据
------------------------------------------------
要从树中删除现有节点,请调用:
例子:
void rb_erase(struct rb_node *victim, struct rb_root *tree); struct mytype *data = mysearch(&mytree, "walrus"); if (data) { rb_erase(&data->node, &mytree); myfree(data); }
要将树中的现有节点替换为具有相同键的新节点,请调用:
void rb_replace_node(struct rb_node *old, struct rb_node *new, struct rb_root *tree);
以这种方式替换节点不会对树重新排序:如果新节点与旧节点的键不同,则 rbtree 可能会损坏。
(6) 遍历存储在 rbtree 中的元素(按排序顺序)
--------------------------------------------------
提供了四个函数来按排序顺序迭代 rbtree 的内容。 这些适用于任意树,不需要修改或包装(除了锁定目的)::
struct rb_node *rb_first(struct rb_root *tree); struct rb_node *rb_last(struct rb_root *tree); struct rb_node *rb_next(struct rb_node *node); struct rb_node *rb_prev(struct rb_node *node);
要开始迭代,请使用指向树根的指针调用 rb_first() 或 rb_last(),这将返回指向包含在树的第一个或最后一个元素中的节点结构的指针。 要继续,请通过在当前节点上调用 rb_next() 或 rb_prev() 来获取下一个或上一个节点。 当没有更多节点时,这将返回 NULL。
迭代器函数返回一个指向嵌入结构 rb_node 的指针,可以使用 container_of() 宏从中访问包含的数据结构,并且可以通过 rb_entry(node, type, member)直接访问各个成员。
例子:
struct rb_node *node; for (node = rb_first(&mytree); node; node = rb_next(node)) printk("key=%s ", rb_entry(node, struct mytype, node)->keystring);
3. 缓存的 rbtrees
--------------
计算最左边(最小)节点对于二叉搜索树来说是一项非常常见的任务,例如对于遍历或用户依赖于他们自己的逻辑的特定顺序。 为此,用户可以使用 'struct rb_root_cached'
将 O(logN) rb_first() 调用优化为简单的指针获取,从而避免潜在的昂贵的树迭代。 这是在维护的运行时开销可以忽略不计的情况下完成的; 尽管内存占用更大。
类似于 rb_root 结构,缓存的 rbtrees 被初始化为空通过::
struct rb_root_cached mytree = RB_ROOT_CACHED;
缓存的 rbtree 只是一个普通的 rb_root,带有一个额外的指针来缓存最左边的节点。 这允许 rb_root_cached 存在于 rb_root 所在的任何地方,这允许支持增强树以及仅几个额外的接口:
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 *);
插入和擦除调用都有各自对应的增强树:
void rb_insert_augmented_cached(struct rb_node *node, struct rb_root_cached *, bool, struct rb_augment_callbacks *); void rb_erase_augmented_cached(struct rb_node *, struct rb_root_cached *, struct rb_augment_callbacks *);
4. 支持增强型 rbtrees
-----------------------------
增强的 rbtree 是一个每个节点中存储了“一些”附加数据的 rbtree,其中节点 N 的附加数据必须是以 N 为根的子树中所有节点内容的函数。此数据可用于增强一些新功能到rbtree。 增强的 rbtree 是一个构建在基本 rbtree 基础设施之上的可选功能。 需要此功能的 rbtree 用户必须在插入和删除节点时使用用户提供的增强回调来调用增强函数。
实现增强 rbtree 操作的 C 文件必须包含 <linux/rbtree_augmented.h> 而不是 <linux/rbtree.h>。 请注意, linux/rbtree_augmented.h 公开了一些您不希望依赖的 rbtree 实现细节; 请坚持那里记录的 API,并且不要在头文件中包含 <linux/rbtree_augmented.h> 以尽量减少您的用户意外依赖此类实现细节的机会。
在插入时,用户必须更新通向插入节点的路径上的增强信息,然后像往常一样调用 rb_link_node() 和 rb_augment_inserted() 而不是通常的 rb_insert_color() 调用。 如果 rb_augment_inserted() 重新平衡 rbtree,它将回调到用户提供的函数中,以更新受影响子树的增强信息。
擦除节点时,用户必须调用 rb_erase_augmented() 而不是 rb_erase()。 rb_erase_augmented() 回调用户提供的函数以更新受影响子树的增强信息。
在这两种情况下,回调都是通过 struct rb_augment_callbacks 提供的。 必须定义 3 个回调:
- 传播回调,它更新给定节点及其祖先的增强值,直到给定的停止点(或 NULL 以一直更新到根)。 - 复制回调,将给定子树的增强值复制到新分配的子树根。 - 树旋转回调,将给定子树的增强值复制到新分配的子树根,并重新计算前子树根的增强信息。
rb_erase_augmented() 的编译代码可能会内联传播和复制回调,这会导致函数很大,因此每个增强的 rbtree 用户应该有一个 rb_erase_augmented() 调用站点,以限制编译代码的大小。
示例用法
^^^^^^^^^^^^^
Interval tree 是 增强rbtree 的一个例子。 参考 - Cormen、Leiserson、Rivest 和 Stein 的“算法简介”。有关区间树的更多详细信息:
经典的 rbtree 有一个单一的键,它不能直接用于存储区间范围,如 [lo:hi] 并快速查找与新 lo:hi 的任何重叠或查找是否有新 lo 的 lo:hi 的精确匹配。
但是,可以扩充 rbtree 以以结构化的方式存储此类间隔范围,从而可以进行有效的查找和精确匹配。
每个节点中存储的这个“额外信息”是其后代节点中的最大 hi (max_hi) 值。 只需查看节点及其直接子节点即可在每个节点上维护此信息。 这将用于 O(log n) 查找最低匹配(所有可能匹配中的最低起始地址),例如:
struct interval_tree_node *interval_tree_first_match(struct rb_root *root, unsigned long start, unsigned long last) { struct interval_tree_node *node; if (!root->rb_node) return NULL; node = rb_entry(root->rb_node, struct interval_tree_node, rb); while (true) { if (node->rb.rb_left) { struct interval_tree_node *left = rb_entry(node->rb.rb_left, struct interval_tree_node, rb); if (left->__subtree_last >= start) { /* * 左子树中的一些节点满足 Cond2。 迭代找到最左边的这样的节点 N。如果它也满足 Cond1, * 那就是我们正在寻找的匹配项。 否则,因为 N 右边的节点也不能满足 Cond1,所以没有匹配区间。 */ node = left; continue; } } if (node->start <= last) { /* Cond1 */ if (node->last >= start) /* Cond2 */ return node; /* node is leftmost match 满足在[start, last]区间里*/ if (node->rb.rb_right) { node = rb_entry(node->rb.rb_right, struct interval_tree_node, rb); if (node->__subtree_last >= start) continue; } } return NULL; /* No match */ } }
插入/删除是使用以下增强回调定义的:
static inline unsigned long compute_subtree_last(struct interval_tree_node *node) { unsigned long max = node->last, subtree_last; if (node->rb.rb_left) { subtree_last = rb_entry(node->rb.rb_left, struct interval_tree_node, rb)->__subtree_last; if (max < subtree_last) max = subtree_last; } if (node->rb.rb_right) { subtree_last = rb_entry(node->rb.rb_right, struct interval_tree_node, rb)->__subtree_last; if (max < subtree_last) max = subtree_last; } return max; } static void augment_propagate(struct rb_node *rb, struct rb_node *stop) { while (rb != stop) { struct interval_tree_node *node = rb_entry(rb, struct interval_tree_node, rb); unsigned long subtree_last = compute_subtree_last(node); if (node->__subtree_last == subtree_last) break; node->__subtree_last = subtree_last; rb = rb_parent(&node->rb); } } static void augment_copy(struct rb_node *rb_old, struct rb_node *rb_new) { struct interval_tree_node *old = rb_entry(rb_old, struct interval_tree_node, rb); struct interval_tree_node *new = rb_entry(rb_new, struct interval_tree_node, rb); new->__subtree_last = old->__subtree_last; } static void augment_rotate(struct rb_node *rb_old, struct rb_node *rb_new) { struct interval_tree_node *old = rb_entry(rb_old, struct interval_tree_node, rb); struct interval_tree_node *new = rb_entry(rb_new, struct interval_tree_node, rb); new->__subtree_last = old->__subtree_last; old->__subtree_last = compute_subtree_last(old); } static const struct rb_augment_callbacks augment_callbacks = { augment_propagate, augment_copy, augment_rotate }; void interval_tree_insert(struct interval_tree_node *node, struct rb_root *root) { struct rb_node **link = &root->rb_node, *rb_parent = NULL; unsigned long start = node->start, last = node->last; struct interval_tree_node *parent; while (*link) { rb_parent = *link; parent = rb_entry(rb_parent, struct interval_tree_node, rb); if (parent->__subtree_last < last) parent->__subtree_last = last; if (start < parent->start) link = &parent->rb.rb_left; else link = &parent->rb.rb_right; } node->__subtree_last = last; rb_link_node(&node->rb, rb_parent, link); rb_insert_augmented(&node->rb, root, &augment_callbacks); } void interval_tree_remove(struct interval_tree_node *node, struct rb_root *root) { rb_erase_augmented(&node->rb, root, &augment_callbacks); }
5. 补充:
Interval tree上的每个节点的 key 值是一个区间,区间的起点和终点通常为整数,同一层节点所代表的区间相互不会重叠,叶子节点的区间是单位长度,不能再分了。区间树可以参考:https://www.jianshu.com/p/e23ab4bc7dec