转自:https://www.cnblogs.com/hellokitty2/p/15362630.html

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\n", 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

Linux内核红黑树1—Documentation/rbtree.txt翻译的更多相关文章

  1. 详解Linux内核红黑树算法的实现

    转自:https://blog.csdn.net/npy_lp/article/details/7420689 内核源码:linux-2.6.38.8.tar.bz2 关于二叉查找树的概念请参考博文& ...

  2. Documentation/kobject.txt翻译--sysfs

    你从未想过的关于kobjects,ksets和ktypes的一切:作者:Greg Kroah-Hartman <gregkh@linuxfoundation.org>:上次更新时间:200 ...

  3. 红黑树(三)之 Linux内核中红黑树的经典实现

    概要 前面分别介绍了红黑树的理论知识 以及 通过C语言实现了红黑树.本章继续会红黑树进行介绍,下面将Linux 内核中的红黑树单独移植出来进行测试验证.若读者对红黑树的理论知识不熟悉,建立先学习红黑树 ...

  4. linux/Documentation/kobject.txt

    Everything you never wanted to know about kobjects, ksets, and ktypes Greg Kroah-Hartman <gregkh@ ...

  5. Documentation/sched-bwc.txt 的中文翻译

    Chinese translated version of Documentation/sched-bwc.txt If you have any comment or update to the c ...

  6. Linux内网渗透

    Linux虽然没有域环境,但是当我们拿到一台Linux 系统权限,难道只进行一下提权,捕获一下敏感信息就结束了吗?显然不只是这样的.本片文章将从拿到一个Linux shell开始,介绍Linux内网渗 ...

  7. 卸载Linux内置的AMP软件

    卸载Linux内置的AMP软件 在安装Linux软件的LAMP环境时,必须有一个前提:必须要完全卸载掉系统内置的AMP软件. 1.卸载httpd软件(Apache) 如果在卸载软件时出现依赖关系,我们 ...

  8. 将CMD内的显示内容输出到txt文件

    将CMD内的显示内容输出到txt文件 xxxx -t >c:\test.txt        //xxxx为命令  如ping www.baidu.com //-t >c:\test.tx ...

  9. 三十道linux内核面试题

      1. Linux中主要有哪几种内核锁? Linux的同步机制从2.0到2.6以来不断发展完善.从最初的原子操作,到后来的信号量,从大内核锁到今天的自旋锁.这些同步机制的发展伴随Linux从单处理器 ...

  10. LINUX内核面试题摘选

    转载:http://blog.csdn.net/zm1_1zm/article/details/77231197 1) Linux中主要有哪几种内核锁? 答:Linux的同步机制从2.0到2.6以来不 ...

随机推荐

  1. 利用ICSharpCode.SharpZipLib.dll解析 出错:“Wrong Local header signature: 0xFF8”

    分析原因 利用ICSharpCode.SharpZipLib.dll解析APK时,进入APK的AndroidXml获取时出现报错 出错代码 using (ICSharpCode.SharpZipLib ...

  2. 磁盘有限,Docker 垃圾很多怎么办

    你的电脑上可能 pull 或者 build 了很多 Docker 镜像,但是你不知道怎么清理,本文将介绍如何清理 Docker 垃圾的常见方法. docker prune 你可以通过原生的多种 pru ...

  3. HashTable HashMap concurrentHashMap区别

    HashTable HashMap concurrentHashMap区别 HashMap.HashTable.ConcurrentHashMap都是map接口的实现类 1.(同步性)HashTabl ...

  4. Qt实现简单的TCP协议(客户端的实现)

    1.QT提供了QTcpSocket类,可以直接实例化一个客户端.需要在pro文件中添加   QT += network 2.连接服务端 connect(connectbutton,SIGNAL(cli ...

  5. 奇迹网站编辑保存的时候提示Access is denied

    出现上面这个情况 只有在IIS模式下运行奇迹MU网站系统才会出现这个问题. 解决办法: 给网站目录赋予everyone权限 1.在网站目录右键属性 2在文件夹属性界面,点击"安全" ...

  6. [GKCTF2021]random

    [GKCTF2021]random 本题出现了MT19937伪随机数生成算法. 目录 [GKCTF2021]random 题目 分析 MT19937算法 步骤 代码实现 解法1 解法2 总结 题目 t ...

  7. $forceUpdate和this.$set('userInfo',name,'小红');

    在Vue官方文档中指出,$forceUpdate具有强制刷新的作用. 那在vue框架中,如果data中有一个变量:age,修改他,页面会自动更新. 但如果data中的变量为数组或对象,我们直接去给某个 ...

  8. 微信小程序(开发某些方式)

    1.开发工具:微信小程序开发工具(需要appid登录)2.调试:可使用微信开发者工具预览(用真机测试)3.真机调试:微信开发者工具真机调试(可打印以及查看网络等)4.扫一扫功能:   1.小程序里面可 ...

  9. HP DC7800 升级CPU出现:Missing or Invalid Processor Microcode Update —— 解决方案:更新主板BIOS

    1.所需文件在这个网盘里面:链接:https://pan.baidu.com/s/140DI2SyRmPf0Q-ikXcJMcQ 提取码:yjth 2.这个问题的解决参考了:https://h3043 ...

  10. 【4】Postman之Tests(断言)

    简介:使用postman完成请求后,验证返回结果,会用到tests断言.官方提供了很多可以直接使用的方法~ 接下来主要介绍这些方法是如何使用. 1. 变量相关: Get an environment ...