内核红黑树源码注解

 1 typedef struct st_rb_node {   
 2     /**  
 3      * 本结构体四字节对齐,因而其地址中低位两个bit永远是0。  
 4      * Linux内核开发人员非常爱惜内存,他们用其中一个空闲的位来存储颜色信息。  
 5      * parent_color成员实际上包含了父节点指针和自己的颜色信息。  
 6      */  
 7     unsigned long parent_color;   
 8 #define RB_RED   0   
 9 #define RB_BLACK 1   
10     struct st_rb_node *left, *right;   
11 }__attribute__((aligned(sizeof(long)))) rb_node;  

Linux的数据结构有个特点就是结构不包括数据,而是由数据来包括结构信息。由结构体获取数据信息是通过 CONTAINER_OF 这个宏来实现的,它利用了一些编译器的特性,有兴趣的可以参考Linux的链表源码。

 

头文件中其它主要内容如下(比较好理解,未作注释):

 1 #define rb_parent(r)    ((rb_node *)((r)->parent_color & ~3))   
 2 #define rb_color(r)     ((r)->parent_color & 1)   
 3 #define rb_is_red(r)    (!rb_color(r))   
 4 #define rb_is_black(r)  rb_color(r)   
 5 #define rb_set_red(r)   do { (r)->parent_color &= ~1; } while (0)   
 6 #define rb_set_black(r) do { (r)->parent_color |= 1; } while (0)   
 7   
 8 static inline void rb_set_parent(rb_node *node, rb_node *p) {   
 9     node->parent_color = (node->parent_color & 3) | (unsigned long) p;   
10 }   
11   
12 static inline void rb_set_color(rb_node *node, int color) {   
13     node->parent_color = (node->parent_color & ~1) | color;   
14 }   
15   
16 #define RB_ROOT (rb_root) { NULL, }   
17 #define rb_entry(ptr, type, member) container_of(ptr, type, member)   
18   
19 #define RB_EMPTY_ROOT(root) ((root)->node == NULL)   
20 #define RB_EMPTY_NODE(node) (rb_parent(node) == node)   
21 #define RB_CLEAR_NODE(node) (rb_set_parent(node, node))   
22   
23 void rb_insert_color(rb_node *node, rb_root *root);   
24 void rb_erase(rb_node *node, rb_root *root);   
25   
26 rb_node * rb_next(const rb_node *node);   
27 rb_node * rb_prev(const rb_node *node);   
28 rb_node * rb_first(const rb_root *root);   
29 rb_node * rb_last(const rb_root *root);   
30   
31 void rb_replace_node(rb_node *victim, rb_node *new, rb_root *root);   
32   
33 static inline void rb_link_node(rb_node *node, rb_node *p, rb_node **link) {   
34     node->parent_color = (unsigned long) p;   
35     node->left = node->right = NULL;   
36     *link = node;   
37 }  

两个基本操作:左旋与右旋(未注释,仅为方便参考)

 1 /**  
 2  * 对node进行左旋。有关概念可参考WIKI等资源。  
 3  */  
 4 static void _rb_rotate_left(rb_node *node, rb_root *root) {   
 5     rb_node *right = node->right, *parent = rb_parent(node);   
 6   
 7     if ((node->right = right->left))   
 8         rb_set_parent(node->right, node);   
 9   
10     right->left = node;   
11     rb_set_parent(node, right);   
12   
13     if (parent) {   
14         if (node == parent->left)   
15             parent->left = right;   
16         else  
17             parent->right = right;   
18     } else {   
19         root->node = right;   
20     }   
21     rb_set_parent(right, parent);   
22 }   
23   
24 /**  
25  * 对node进行右旋。有关概念可参考WIKI等资源。  
26  */  
27 static void _rb_rotate_right(rb_node *node, rb_root *root) {   
28     rb_node *left = node->left, *parent = rb_parent(node);   
29   
30     if ((node->left = left->right))   
31         rb_set_parent(node->left, node);   
32   
33     left->right = node;   
34     rb_set_parent(node, left);   
35   
36     if (parent) {   
37         if (node == parent->left)   
38             parent->left = left;   
39         else  
40             parent->right = left;   
41     } else {   
42         root->node = left;   
43     }   
44     rb_set_parent(left, parent);   
45 }  

插入操作的实现(前提是节点已经插入在目标位置,下面的函数只负责修正树的性质):

 1 /**  
 2  * node是新插入的结点,有着默认的红色。  
 3  * 本函数检查是否有违背红黑树性质的地方,并进行纠正。  
 4  */  
 5 void rb_insert_color(rb_node *node, rb_root *root) {   
 6     rb_node *parent, *gp;   
 7   
 8     /**  
 9      * 如果有父节点且父节点是红色,进行树的调整以保证树的性质。  
10      */  
11     while ((parent = rb_parent(node)) && rb_is_red(parent)) {   
12         gp = rb_parent(parent);   
13   
14         if (parent == gp->left) {   
15             /**  
16              * 父节点是祖父节点左子的情况。  
17              * "else"中的情况左右相反,这里只注释"if"里的代码。  
18              */  
19             register rb_node *uncle = gp->right;   
20             if (uncle && rb_is_red(uncle)) {   
21                 /**  
22                  * 如果有红叔,则将红叔与红父均涂黑,并将祖父节点涂红。  
23                  */  
24                 rb_set_black(parent);   
25                 rb_set_black(uncle);   
26                 rb_set_red(gp);   
27                 /**  
28                  * 现在简单路径中黑节点个数仍然平衡,但祖父变成了红色,  
29                  * 我们不确定有没有造成父子均红的情况,所以需要对祖父节点进行下一轮修复。  
30                  */  
31                 node = gp;   
32                 continue;   
33             }   
34   
35             /**  
36              * 现在是叔节点为空或为黑的情况。  
37              */  
38             if (node == parent->right) {   
39                 /**  
40                  * 如果新节点是父节点的右子,对父节点进行左旋。  
41                  * 旋转后树仍然平衡,但新节点占了原父节点的位子。  
42                  * 这两个节点交换角色后,新的父节点是红的,其左子也是红的。  
43                  */  
44                 _rb_rotate_left(parent, root);   
45                 register rb_node *tmp = node;   
46                 node = parent;   
47                 parent = tmp;   
48             }   
49             /**  
50              * 此时父红左子红,树平衡但有连续红节点。  
51              *  
52              * 父涂黑,祖父涂红,再对祖父右旋,树即调整到合法状态。  
53              */  
54             rb_set_black(parent);   
55             rb_set_red(gp);   
56             _rb_rotate_right(gp, root);   
57             return;   
58         } else {   
59             register rb_node *uncle = gp->left;   
60             if (uncle && rb_is_red(uncle)) {   
61                 rb_set_black(parent);   
62                 rb_set_black(uncle);   
63                 rb_set_red(gp);   
64                 node = gp;   
65                 continue;   
66             }   
67   
68             if (node == parent->left) {   
69                 _rb_rotate_right(parent, root);   
70                 register rb_node *tmp = node;   
71                 node = parent;   
72                 parent = tmp;   
73             }   
74             rb_set_black(parent);   
75             rb_set_red(gp);   
76             _rb_rotate_left(gp, root);   
77             return;   
78         }   
79     }   
80     /**  
81      * 若无父节点,只需将node涂黑;  
82      * 若父节点为黑,插入红节点不影响树的性质。  
83      * 循环体后直接将node涂黑,可以同时保证以上两点。  
84      */  
85      rb_set_black(root->node);   
86 }  

最复杂的是删除操作,分为两部分。

删除第一步,移出节点:

  1 /**  
  2  * 删除node节点(其实只是将其与树脱离),并修正树。  
  3  * 红黑树的节点删除相对比插入更复杂,加之Linux的源码风格也不易懂,  
  4  * 此段代码需认真体会,可以多画画图,对各种情况标记、对照一下。  
  5  */  
  6 void rb_erase(rb_node *node, rb_root *root) {   
  7     rb_node *child, *parent;   
  8     int color;   
  9        
 10     /**  
 11      * 这里其实分成了两个大分枝。  
 12      * 如果node有任一空子,则记录child并跳至条件语句后的语句,具体位置参考注释;  
 13      * 如果两子均不为空,则进入"else"分枝。  
 14      */  
 15     if (!node->left) {   
 16         child = node->right;   
 17     } else if (!node->right) {   
 18         child = node->left;   
 19     } else {   
 20         /**  
 21          * 乾坤大挪移,将要删除的节点转换成只有一个子节点或无子节点的节点。  
 22          * 具体方法是,找到它的下一个节点,也即先找其右节点,再循环找左节点,  
 23          * 直到没有左子节点。找到的节点一定是比node"大"且紧邻node的节点。  
 24          * 将此找到的节点放到node位置,并保持node的原有颜色,树的性质并不会受影响。  
 25          * 此时问题转化成了删除找到的没有左子的节点。  
 26          * (提示:也可以找左子的右节点,循环下去,得到的将是“紧邻”的小于node的节点)  
 27          */  
 28         rb_node *old = node, *left;   
 29         node = node->right;   
 30         while ((left = node->left) != NULL)   
 31             node = left;   
 32            
 33         /**  
 34          * 找到节点后,将其“大挪移”过去。  
 35          * 首先用old的父亲或root指向新找到的节点node.  
 36          */  
 37         if (rb_parent(old)) {   
 38             if (rb_parent(old)->left == old)   
 39                 rb_parent(old)->left = node;   
 40             else  
 41                 rb_parent(old)->right = node;   
 42         } else {   
 43             root->node = node;   
 44         }   
 45            
 46         /**  
 47          * 记录找到的node节点相关信息。对于这种删除操作,node节点移至old位置后会沿用old的颜色属性,  
 48          * 树的性质不受影响,但原node位置将失去一个节点,我们要记录节点的相关信息以便做节点的删除工作。  
 49          */  
 50         child = node->right;   
 51         parent = rb_parent(node);   
 52         color = rb_color(node);   
 53            
 54         if (parent == old) {   
 55             /**  
 56              * 参考前面while循环,如果parent==old,则找到的单子节点只能是old节点的右子。  
 57              * 这里保存的parent可以理 解为将用作node原来子节点的亲父节点。对于old是node右子的情况,  
 58              * node取代old后,原node的父节点还是node.  
 59              */  
 60             parent = node;   
 61         } else {   
 62             /**  
 63              * node节点的删除处理。主要是将相应的父、子联结起来。  
 64              * 注意我们的“乾坤大挪移”可能会比较绕,删的数据是old节点,但我们将node转移过去占了old的位置,  
 65              * 所以对于节点结构来说,我们删除的是node原来的位置。  
 66              */  
 67             if (child)   
 68                 rb_set_parent(child, parent);   
 69             parent->left = child;   
 70                
 71             node->right = old->right;   
 72             rb_set_parent(old->right, node);   
 73         }   
 74            
 75         /**  
 76          * node移过去后继续使用old节点的颜色和父节点属性。  
 77          * parent_color同时保存了父节点信息和自己的颜色信息。  
 78          * 关于parent_color请参考头文件中的宏。  
 79          */  
 80         node->parent_color = old->parent_color;   
 81         /**  
 82          * 之前已将old的parent的指向old子节点的指针指向了node,  
 83          * 并且node也已经连接了old的右子(如果node与old是子与父的关系,则node保留原右子即可)  
 84          * 上面一句完成了node指向新父亲即设置颜色的操作,现在只剩下左子了,把它搞定!  
 85          */  
 86         node->left = old->left;   
 87         rb_set_parent(old->left, node);   
 88            
 89         /**  
 90          * 该删的该改的都已搞定,现在要检查颜色了。  
 91          */  
 92         goto color;   
 93     }   
 94        
 95     /**  
 96      * 这里的条件语句和goto语句实在是让人晕头转向。现在对应的是本函数最上面的if语句中前两个case。  
 97      *  
 98      * 如果node有任一空子,也即node无子或有单子。这里跳过了前面的“乾坤大挪移”,同时相比挪移的情况,  
 99      * 这里的区别是node可能是右子,也可能是左子。但这里的删除操作要简单些。  
100      *  
101      * 思考:挪移与不挪移的情况,最终都是把问题转到了一个无子或只有单子的节点上,  
102      * 为什么不能让两种情况执行一段共同的删除代码呢?  
103      * 我想应该是挪移本身已经使node从原位置消失了,两者删除操作有差别,加之Linux内核对速度的狂热,  
104      * 就分情况写成了现在这种很绕的代码。  
105      */  
106     parent = rb_parent(node);   
107     color = rb_color(node);   
108        
109     if (child)   
110         rb_set_parent(child, parent);   
111     if (parent) {   
112         if (parent->left = node)   
113             parent->left = child;   
114         else  
115             parent->right = child;   
116     } else {   
117         root->node = child;   
118     }   
119   
120 color:   
121     /**  
122      * color是删除节点的颜色,删除的节点的层次正好在child参数与parent参数中间。  
123      * 如果删除的是红节点,对树没有影响,所以只有删除的是黑节点时才修正颜色。  
124      */  
125     if (color == RB_BLACK)   
126         _rb_erase_color(child, parent, root);   
127 }  

第二步,简单移出如果破坏了树的性质,则需要修复:

  1 /**  
  2  * 前置环境:在node与parent之前,刚刚删除了一个黑色节点。现在树很可能不平衡,  
  3  * node与parent也可能红色冲突。  
  4  * 本函数进行树的性质的修正,以使树恢复平衡。在一些情况下问题会转移到上一层节点,  
  5  * 则须对上一层节点进行递归检查与修正。本函数中的while循环实际上实现了这种递归。  
  6  *  
  7  * 提示:这是红黑树里最绕的地方,看不明白可以多画画图。  
  8  */  
  9 static void _rb_erase_color(rb_node *node, rb_node *parent, rb_root *root) {   
 10     /**  
 11      * other用来保存兄弟节点,这是树的修正过程中一个重要的参考节点。  
 12      */  
 13     rb_node *other;   
 14     /**  
 15      * 循环条件:node不是红节点且node不是根节点。  
 16      * 解释:对于红节点或根节点,直接涂黑即可解决问题。  
 17      */  
 18     while ((!node || rb_is_black(node)) && node != root->node) {   
 19         /**  
 20          * 为方便程序处理各种情况,这里和节点插入一样将问题分成了左右对称的两类。  
 21          * if-else两个分枝里代码逻辑完全相同,只是左右相反。所以我们只研究node是parent左子的情况。  
 22          *  
 23          * 在开始之前,我们先总结一下当前状态:  
 24          * 1:因为删除的是黑色节点,所以node与parent都有可能是红色节点。  
 25          * 2:node与parent之间少了一个黑色节点,则所有通过node的路径都少了一个黑色节点,不妨画图时用-1标出来;  
 26          * 但node的兄弟节点(node一定有兄弟,可以根据删除前树的平衡性质来反推)高度并未变化,可以记作0。  
 27          *  
 28          * 提示:在进行旋转、涂色等操作时,可以画图观查平衡状态的变化。  
 29          */  
 30         if (parent->left == node) {   
 31             other = parent->right;   
 32             if (rb_is_red(other)) {   
 33                 /**  
 34                  * 如果兄弟节点是红色,则父节点是黑色,交换父、兄节点的颜色,并对父节点进行左旋。  
 35                  * 旋转后,兄节点占了老的父节点位置,且和老的父节点颜色相同,所以不会向上造成颜色冲突。  
 36                  * 我们仍然以老的父节点为父节点来看,现在的状态是:  
 37                  * 父节点右子保持平衡,只有经过node的路径少了一个黑色节点。  
 38                  * 现在问题和之前相似,但node有了一个黑色的兄弟(还有一个红色父亲)。  
 39                  */  
 40                 rb_set_black(other);   
 41                 rb_set_red(parent);   
 42                 _rb_rotate_left(parent, root);   
 43                 /**  
 44                  * other指向新的兄弟节点。other现在必然是一个黑色节点,而不会是空。这一点可以根据旋转之前树的性质反证。  
 45                  */  
 46                 other = parent->right;   
 47             }   
 48             /**  
 49              * 此时状态:  
 50              * node有黑色兄弟,父亲可能黑也可能红。  
 51              */  
 52             if ((!other->left || rb_is_black(other->left)) &&   
 53                 (!other->right || rb_is_black(other->right))) {   
 54                 /**  
 55                  * 如果other没有红色子节点,那我们就可以把other涂红,并向上转移问题。  
 56                  * other涂红的后果是,other分枝少了一个黑节点,与node分枝保持了平衡,但parent整体少了一个黑色节点。  
 57                  * 细心的人可能会发现,如果父亲是红色的,父亲与兄弟有颜色冲突,直接向上转移能纠正吗?当然是可以的。  
 58                  * 向上一层之后,while里的黑节点判断失败,会直接执行while后面的语句,直接将parent涂黑,则树恢复平衡。  
 59                  */  
 60                 rb_set_red(other);   
 61                 node = parent;   
 62                 parent = rb_parent(node);   
 63             } else {   
 64                 /**  
 65                  * 现在黑兄弟有红子节点,父亲颜色未知。  
 66                  */  
 67                 if (!other->right || rb_is_black(other->right)) {   
 68                     /**  
 69                      * 如果黑兄弟右节点为空或为黑,则左节点一定是红的,我们想办法把它调整为右子为红。  
 70                      * 至于为什么,看后面就知了。  
 71                      * other->left与other交换颜色,对other进行右旋,other指向新的兄弟。根据右旋转的特点,  
 72                      * 可知现在other仍然是黑色,且它有了一个红色右子。同时other分枝高度不变,颜色也没有冲突。  
 73                      */  
 74                     rb_set_black(other->left);   
 75                     rb_set_red(other);   
 76                     _rb_rotate_right(other, root);   
 77                     other = parent->right;   
 78                 }   
 79                 /**  
 80                  * 此时状态:黑兄弟有红色右子节点。  
 81                  * 不管parent是什么颜色,把other涂成父亲的颜色(之后旋转,other占据父亲的位置,向上没有颜色冲突),  
 82                  * 把父亲涂黑,把黑兄的other涂黑,这时node分枝高度可能有变化也可能没变化,other分枝多了一个黑节点。  
 83                  * 现在对父亲进行左旋转。旋转后的情况是右边分枝(原other右子)少了一个黑节点,重归平衡;  
 84                  * 左边分枝则增加了一个黑节点,也恢复了平衡。此时也没有颜色冲突  
 85                  */  
 86                 rb_set_color(other, rb_color(parent));   
 87                 rb_set_black(parent);   
 88                 rb_set_black(other->right);   
 89                 _rb_rotate_left(parent, root);   
 90                 /**  
 91                  * 树已平衡,node置为根节点,并跳出循环。  
 92                  * 疑问:这里为什么还要检查根节点呢?  
 93                  */  
 94                 node = root->node;   
 95                 break;   
 96             }   
 97         } else {   
 98             other = parent->left;   
 99             if (rb_is_red(other)) {   
100                 rb_set_black(other);   
101                 rb_set_red(parent);   
102                 _rb_rotate_right(parent, root);   
103                 other = parent->left;   
104             }   
105             if ((!other->left || rb_is_black(other->left)) &&   
106                 (!other->right || rb_is_black(other->right))) {   
107                 rb_set_red(other);   
108                 node = parent;   
109                 parent = rb_parent(node);   
110             } else {   
111                 if (!other->left || rb_is_black(other->left)) {   
112                     rb_set_black(other->right);   
113                     rb_set_red(other);   
114                     _rb_rotate_left(other, root);   
115                     other = parent->left;   
116                 }   
117                 rb_set_color(other, rb_color(parent));   
118                 rb_set_black(parent);   
119                 rb_set_black(other->left);   
120                 _rb_rotate_right(parent, root);   
121                 node = root->node;   
122                 break;   
123             }   
124         }   
125     }   
126     /**  
127      * 对于红节点或根节点,直接涂黑即可解决问题。  
128      */  
129     if (node)   
130         rb_set_black(node);   
131 }  


转自 http://www.iteye.com/topic/1119331

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值