linux rbtree 详解(红黑树)

               linux rbtree 详解(红黑树)

在讲红黑树的插入删除之前,我们还是先讲讲红黑树的性质叭。

  • 红黑树的性质:(还必须满足二叉搜索树)

性质1:每个节点要么是黑色,要么是红色。
性质2:根节点是黑色。
性质3:每个叶子节点(NIL)是黑色。
性质4:每个红色结点的两个子结点一定都是黑色。
性质5:任意一结点到每个叶子结点的路径都包含数量相同的黑结点。

总结后的精华就是下面两点:

红黑树的性质是每条路径的黑色节点数目相同
红黑树保证最长路径不超过最短路径的二倍,因而近似平衡

接下来我们的插入删除操作都会主要围绕着红黑树的平衡来讲解。

这里建议一边打开源码一边按着我的博客的顺序理解。

结构体的定义:

1.红黑节点结构体的创建:
struct rb_node{
unsigned long  rb_parent_color;//父节点指针和自己的颜色信息
   #defineRB_RED0  //红为0
   #defineRB_BLACK1  //黑为1
struct rb_node *rb_right; //右儿子
struct rb_node *rb_left;  //左儿子
} __attribute__((aligned(sizeof(long))));//字节对齐

下面三句话由博客中发现,对我理解源码有莫大的帮助,阿里阿多。
本结构体四字节对齐,因而其地址中低位两个bit永远是0。
Linux内核开发人员非常爱惜内存,他们用其中一个空闲的位来存储颜色信息。
rb_parent_color成员实际上包含了父节点指针和自己的颜色信息。

这里是解释字节对齐和rb_parent_color 是用来存父节点指针和自己的颜色信息的原因:

linux的红黑树是用一个 unsigned long类型来存储指针和保存该节点的color 源码中结构体用 '__attribute__((aligned(sizeof(long))))'包装,字节对齐的作用:
对于32位机,sizeof(long)为4 (结构体内的数据类型的地址为4的倍数)
对于64位机,sizeof(long)为8(结构体内的数据类型的地址为8的倍数)
一个变量的内存地址正好位于它长度的整数倍,他就被称做字节对齐。
至于为什么要字节对齐:CPU访问数据的效率问题
所以无论是32位的还是64位的最后两位,RB_node结构体的地址的低两位(或者四位)肯定都是零,与其空着不用,还不如用它们表示颜色,反正颜色就两种,其实一位就已经够了。

2.根节点结构体的创建:
struct rb_root{ //根节点
struct rb_node *rb_node;
};

内联函数

1.设置节点的新父节点:
//内联函数,设置节点的父节点
static inline void rb_set_parent(struct rb_node *rb, struct rb_node *p){ //改变节点的颜色,参数为(节点,新父节点) ,将节点的父节点的指针变成新父节点
rb->rb_parent_color = (rb->rb_parent_color & 3) | (unsigned long)p; //&3 清空掉除了自己的颜色以外的信息,将前面地址变为新父节点的地址
}

结合上面字节对齐的内容,我们分析一下:
原来的 rb->rb_parent_color  
存的内容是原来的父节点地址和自己的颜色信息
​\&3后我们可以得到自己节点的颜色信息,父节点的地址全部被清0
之后的
|(unsigned long)p​这时候就是将前面的地址变为新父节点的地址
这样子这个内联函数的功能就很明显了,就是将新的节点rb的父节点信息设置为p。
例子:

假设 rb->rb_parent_color=0x6f1711
说明对于来说,它的 ​parent 0x6f1710而它的颜色是黑色
rb->rb_parent_color&3=0x000001之后只剩自己的颜色
若此时p是指向​0x6f1720的结构体指针
rb->rb_parent_color0x0000 = 0x000001|0x6f1720 = 0x6f1721
则此时的rb,它的parent是0x6f1720,颜色为黑色

如果是​c++实现的话,其实就是​node.parent=p;
(​linux的大部分操作都是位运算操作,这样可以提高一定的运行速度)

2.设置节点的color
//内联函数,设置节点的color
static inline void rb_set_color(struct rb_node *rb, int color){  //改变当前节点的颜色,参数为(当前节点,和需要设置颜色)
rb->rb_parent_color = (rb->rb_parent_color & ~1) | color; //&~1 去点最后一个的颜色, |color 将颜色设置为color
}

有了上面的详解,这里就很简单啦。具体就不解释啦。

3.把parent设为node的父结点,并且让rb_link指向node。
//它把parent设为node的父结点,并且让rb_link指向node。
static inline void rb_link_node(struct rb_node * node, struct rb_node * parent,struct rb_node ** rb_link){
node->rb_parent_color = (unsigned long )parent;
node->rb_left = node->rb_right = NULL; //将节点左右儿子清空
*rb_link = node; //红黑链指向该节点
}

宏定义

宏定义相关的内容直接看我源码的注释即可。

插入节点后的平衡调整

红黑树插入节点的过程:

  • RBTree为二叉搜索树,我们按照二叉搜索树的方法对其进行节点插入 (事实上对于所有种类的平衡树,插入都是按着二叉搜索树的方法插入,之后再进行旋转,拉伸,或者变成最小堆等)

  • RBTree有颜色约束性质,因此我们在插入新节点之后要进行颜色调整(为了满足性质4:每个红色结点的两个子结点一定都是黑色,对于每次更新完成后的node,都一定还是红色的,所以只要当父亲为黑色的时候就一定达到平衡了

对于查找插入位置的操作我们已经没啥可以讲的啦,如果不会二叉搜索树的插入,应该也也也看不到这里了叭。插入时需要遵循的操作。

  1. 根节点为NULL,直接插入新节点并将其颜色置为黑色

  2. 根节点不为NULL,找到要插入新节点的位置

  3. 插入新节点

  4. 判断新插入节点对全树颜色的影响,更新调整颜色

好了最最最重点的来了,就是更新调整颜色。这里每个博客都有着自己的分类和顺序来讲解。我们还是紧跟着rbtree源码的顺序进行分析。 其实本质上就两种情况:

情况1(自己和兄弟相同颜色和父亲不同颜色) 情况2(Z字抖动) 情况3 (直链情况,其实就在Z字抖动里,可以先分析是不是Z字抖动,如果是就转为直链,然后再进行旋转),所以情况3可以不记(概念这么多,能少记点就少记点啦)

1.父节点红色

1.1 父节点是爷爷节点的左儿子
1.1.1如果存在舅舅节点且为红色

则是如下情况:

我们称为情况1(自己和兄弟相同颜色和父亲不同颜色) 这是最简单的情况,可以不用旋转就能让红黑树继续保持平衡。

此时node的父亲也是红色不满足性质4(每个红色结点的两个子结点一定都是黑色。) 所以我们需要进行调整,并且不能破坏原来的平衡 (这里和后面的平衡都是指1红黑树的性质是每条路径的黑色节点数目相同 ,2每个红色结点的两个子结点一定都是黑色。只强调一次)

因为插入的是红色节点,所以树本来就是平衡的,但是不满足红色节点的儿子一定是黑色节点,所以我们满足这个性质并且维持红黑树每条路径的黑色节点数目相同,不旋转的话,我们的做法是 1.如果存在两个红儿子,将节点的黑色往下拉(即父亲变红色,两个儿子变黑色)

下图的两次操作都是这个操作演化而来的。

相关操作:

//第一种情况
rb_set_black(uncle);
rb_set_black(parent);
rb_set_red(gparent);
node = gparent;
continue;//继续往上调整
1.1.2 如果节点是父亲的右儿子

我们称为情况2(Z字抖动(哈哈哈哈全职高手的技巧名))
很明显这种情况并不符合情况1,所以这时候要么没有舅舅,要么舅舅为黑色。
所以为了平衡我们只能旋转了。那这种情况很明显是旋转成一个黑带两个红或者一个红带两个黑。
所以想想(原)parent<(原)cur<pParent,所以最后肯定是cur作为父亲节点,parent和pParent作为儿子节点

所以分成以下两个步骤:
进行左旋操作变成一条链的状态

相关操作:

register struct rb_node *tmp;
__rb_rotate_left(parent, root); //先左旋根节点
tmp = parent;
parent = node;
node = tmp;

如果本身node就是parent的左儿子,则直接到这一步 下面这个图的cur其实是原来的parent,parent是原来的cur,所以 (原)parent<(原)cur<pParent

rb_set_black(parent);
rb_set_red(gparent);
__rb_rotate_right(gparent, root);

会发现有没有舅舅节点都是一样的操作(所以就不用特殊考虑了)

下面的代码都是左右颠倒操作而已,所以真正的插入就上面三小步。

2.根节点一定是黑色

rb_set_black(root->rb_node);
总结:

对于插入后的平衡调整主要是为了维护性质4:每个红色结点的两个子结点一定都是黑色 并且我们的两种操作完成后node节点都还是红色,还不破坏树的平衡,所以我们只需要使当前节点满足性质4后向上更新node,直到遇到一个黑色的父亲的时候,我们的树就完全满足性质4了。

删除节点后的平衡

(红黑树最难的地方)

一句话概括:判断类型的时候,先看待删除的节点的颜色,再看兄弟节点的颜色,再看侄子节点的颜色(侄子节点先看远侄子再看近侄子),最后看父亲节点的颜色。

这里其实我更建议先看下面的删除,再来看删除节点后的平衡,不过为了对应源码,所以我把这部分放在了这里。
删除节点后的平衡一共有三种大情况:
情况1(删除红色节点) 源码在这里实现
情况2(单红儿子)源码在这里实现
情况3 (删除黑色叶子节点)
情况3.1(红兄)
情况3.2( 黑兄二黑侄 )
情况3.3( 黑兄一黑一红侄 )

下面我们具体了解一下删除后关于恢复平衡的基本情况:

1.情况1(删除红色节点) 当删除节点n是红色的叶子节点,直接删除节点n,不影响红黑树平衡性质,源码在下面删除函数里

  1. 当删除节点n是红色的单支节点。不可能出现,如果孩子是红色,违反限制4;如果孩子是黑色的,违反限制5. 

(小黑色节点是叶子节点,红黑树最后结束的节点都是没有键值的黑色节点(此黑色也会算入性质5)) 所以两种情况都不满足,不存在这种情况

  1. 情况2(单红)删除的黑色节点仅有左子树或者仅有右子树,且儿子为红色

    直接删除黑色节点,并且将儿子接上并改为黑色,源码在下面删除函数里

    (因为路径上少了一个黑节点,所已将红节点变成黑节点以保持红黑树的性质)。

4.情况3 (删除黑色叶子节点) 这里就接上rbtree源码__rb_erase_color函数刚开始的情况了

首先我们先理解node和root的含义:

node是删除节点所影响的子树
root是删除这个节点影响最近的父节点(即root开始左右子树开始不平衡)
root一定是node的祖先节点
如果node=root
的时候说明影响全部消除了,则可以跳出循环

1.如果节点不存在或者节点为黑色并且节点不等于根节点

1.1该节点是父亲节点的左儿子
1.1.1如果兄弟节点是红色

情况3.1(红兄) AVL树种的RR型操作 转化为黑兄
这种操作并不改变平衡,只是为了将兄弟节点变成黑色,所以node不会向上更新。兄弟节点变成黑色后就来到这种情况

rb_set_black(other);
rb_set_red(parent);
__rb_rotate_left(parent, root);
other = parent->rb_right;
1.1.1.1兄弟节点有两个黑色的儿子

情况3.2( 黑兄二黑侄 )
当遇到这种情况时候我们很明显可以通过把other变成红色来平衡parent这棵子树。这样子对于parent这个子树我们已经成功平衡了,所以node可以向上更新。(不懂为什么向上更新的,可以回到上面看node和root的定义,只有不断的将node更新到root,这个红黑树才彻底平衡了)

重点解释:
这种操作只能平衡一个子树patent,因为此时如果删除点node中的节点,对于other这个子树已经可以平衡了,但是对于整个平衡树,parent这个子树总的黑色节点还是少了1,所以并不能平衡整棵红黑树。

rb_set_red(other);
node = parent;
parent = rb_parent(node);
1.1.1.2兄弟节点的两个儿子至少有一个是红色

情况3.3( 黑兄一黑一红侄 )

1.1.1.2.1兄弟节点没有右儿子或者右儿子是黑色
当遇到这种情况的时候,我们只是单纯的为了将右侄子变成红色,所以并没有使树达到平衡,所以node不向上更新。只是为了单纯的转化为下面的状态。

rb_set_black(other->rb_left);
rb_set_red(other);
__rb_rotate_right(other, root);
other = parent->rb_right;

 

1.1.1.3兄弟节点没有右儿子或者右儿子是红色的情况

当遇到这种情况的时候我们只需要如图旋转,即可通过如图的方法进行旋转,这时即可使删除点node中一个黑色节点且又不会干扰整棵树的平衡,(当我们删除node中的一个节点后,由于情况3.2( 黑兄二黑侄 ),node内部的节点也是一定是可以平衡的,那么对于整棵树,这种情况删除并不会改变黑色节点的个数,所以整棵树也可以达到平衡)所以当我们遇到这种情况的时候即可实现整棵树的平衡了。

rb_set_color(other, rb_color(parent)); //把兄弟节点的颜色变成父亲的颜色
rb_set_black(parent); //将父亲变成黑色
rb_set_black(other->rb_right); //讲兄弟节点右儿子的颜色变成黑色
__rb_rotate_left(parent, root); //左旋
node = root->rb_node; //成功调整平衡
break;

剩余的代码只是左右颠倒,就不再做解释了。

总结:

对于删除后的平衡调整,主要是为了满足性质5:任意一结点到每个叶子结点的路径都包含数量相同的黑结点。 对于每次操作我们都能保证当前的node子树删除后保持平衡,且不会破环红黑树的其他性质。所以我们只需要每次保证删除掉node子树里的一个节点后,兄弟子树仍然能平衡。这时就可以继续向上更新node,如果遇到最后一种情况,则可以直接平衡整棵树。

 

红黑树的删除

所有的二叉搜索树的删除,最后都能归为: 所有的删除问题都可以转化成删除叶子节点或单支节点(只有一个孩子)的问题

删除函数,类似于二叉树删除函数,不过在删除完成以后需要调用调整函数恢复性质

总的过程也是按z的左右儿子情况进行分类.

1.z只有左儿子/右儿子
2.z有左右儿子,z的后继结点是z的右儿子
3.z有左右儿子,z的后继结点不是z的右儿子

先记住一个概念:

在下面的过程中
parent总是指向树中会被删除的结点或是会被替代的结点
child总是指向要替代node或parent的结点

 

1.当前节点没有左儿子
child = node->rb_right;  //删除节点的儿子

说明右儿子要替代节点 下一步跳到这里

2.当前节点没有右儿子
child = node->rb_right;  //删除节点的儿子

说明右儿子要替代节点 下一步跳到这里

3.如果左右儿子都有

3.1先找在右儿子里面找后继

node = node->rb_right;  //先跳到右儿子
while ((left = node->rb_left) != NULL)
   node = left;  // node为原来节点的后继节点 (找到右儿子的最左的孩子)

如果删除的节点不是根节点

if (rb_parent(old)->rb_left == old)
   rb_parent(old)->rb_left = node;  //让原来节点的父亲指向后继节点,相当于删除该节点
else
   rb_parent(old)->rb_right = node;  //让原来节点的父亲指向后继节点,相当于删除该节点

如果是根节点

root->rb_node = node;  //则最终删除的节点要和根root脱离关系

后继节点一定没有左儿子

child = node->rb_right;    //后继节点的唯一儿子
parent = rb_parent(node);  //后继节点的父亲
color = rb_color(node);    //后继节点的颜色

如果后继节点是原来节点的儿子

则直接接上就可以了

parent = node;//被替代的就是node节点

后继节点不是原来节点的儿子

则将删除节点换到后继节点,然后再删除

if (child)
    rb_set_parent( child, parent);  //将后继节点的儿子和后继节点的父亲接上(相当于删除掉后继节点)
parent->rb_left = child;  //将后继节点的儿子和后继节点的父亲接上(相当于删除掉后继节点)
node->rb_right = old->rb_right;  //将后继节点完全代替成原来的节点
rb_set_parent(old->rb_right, node);

将后继节点换到原来节点的位置:

node->rb_parent_color = old->rb_parent_color;  //将后继节点完全代替成原来的节点
node->rb_left = old->rb_left;  //将后继节点完全代替成原来的节点
rb_set_parent(old->rb_left, node);

判断是否会影响树的平衡

goto color;  //判断是否会影响树的平衡

当删除的节点最多只有一个儿子的时候

也是简单的删除操作

parent = rb_parent(node);  //删除节点的父亲
color = rb_color(node);    //删除节点的颜色
// child是删除节点的儿子
if (child)
   rb_set_parent(child,parent);  //如果有儿子,则儿子的父亲变成自己的父亲(相当于删除了该节点)
if (parent) {     //就是简单的删除节点,让父亲指向儿子
  if (parent->rb_left == node)
        parent->rb_left = child;
  else
        parent->rb_right = child;
} else
    root->rb_node = child;

最重要的地方: 这里就回到了上面的删除后调整平衡的部分

//如果node原来的颜色是黑色,那么就意味着有一个黑色结点被覆盖了,
//红黑树的性质可能会被破坏(性质4或5),需要调整
color:
   if (color == RB_BLACK)
       __rb_erase_color(child, parent, root);  //如果颜色为黑色则开始调整平衡
}

至此我们linux rbtree的源码重要的部分都讲解得差不多了,如果有什么疑问可以评论区告诉我或者私聊我。其他细节的地方就具体看文章最末尾的源码里的注解。

下面是阅读源码过程中遇到的问题和整理的细节知识点,看完会更有助于大家理解rbtree的源码。

linux rbtree树和我们正常c++实现的红黑树的区别:

1.linux的实现中没有key域,结构不包括数据,只有红黑树的结构。当使用的时候是将数据和基本结构被包括在同一个struct中。(linux的一大特色)在c++中是通过泛型,传入基本类型或者传入类,结构体类型实现。

这里可以看看linux 的链表list_head的构成

2.linux的红黑树是用一个 unsigned long类型来存储指针和保存该节点的color
源码中结构体用 '__attribute__((aligned(sizeof(long))))'包装,字节对齐的作用:
对于32位机,sizeof(long)为4 (结构体内的数据类型的地址为4的倍数)
对于64位机,sizeof(long)为8(结构体内的数据类型的地址为8的倍数)
一个变量的内存地址正好位于它长度的整数倍,他就被称做字节对齐。
至于为什么要字节对齐:CPU访问数据的效率问题

所以无论是32位的还是64位的最后两位,RB_node结构体的地址的低两位(或者四位)肯定都是零,与其空着不用,还不如用它们表示颜色,反正颜色就两种,其实一位就已经够了。

3.linux的rbtree的功能是由多种接口组成 相比于我们c++的面向对象实现的红黑树或者其他数据结构,我们实现一个链表类都是将所有的功能完全的在一个类中实现,而对于linux(不过linux大部分源码都是c实现的,所以没有面向对象的思想),则是写成很多的接口。至于写成接口的好处就是:当linux实现完链表的结构后,后面实现栈和队列的时候,可以不用赋值链表的源码或者在实现栈和队列时引用链表类,这里可以直接使用链表的接口。就可以大大缩减代码量和内存空间。

额外知识点:

1.c++ 将指针指向的地址存在unsigned long类型中。

struct node {
   unsigned long w;
} Node;
struct node *p = new node;
*(struct node **)&Node.w = p;
cout << p << endl;              // 0x721710
cout << hex << Node.w << endl;  // 721710

​&Node.w是取址操作,获取​Node.w的地址
node**后面的*是指针变量的定义形式,没有前面的*则表示这是一个指向​node的指针变量,加上前面的*表示它指向的是一个的node指针型变量的指针
最外的的​*是解引用操作,就是改变指向位置的值
简单直白点就是将​node.w的地址指向的位置转化成指向​node*的地址,并(最外面的*)将该地址存储的值改为要保存的值。

2.我们看源码的时候可以看到有下面的定义:

#define rb_set_black(r) do { (r)->rb_parent_color |= 1; } while (0)

有没有发现这个do while好像并没有什么作用,无论怎样子都是循环一次。

//假设定义了这样的一个宏:(宏定义换行会自动添加\)
#define macro(condition) \
if(condition) dosomething();
//在这样的情况下使用这个宏:
if(temp)
    macro(i);
else
    doanotherting();
//那么这样的话展开成是这样的:
if(temp)
    if(i) dosomething();
else //else会变成if(i)的else
    doanotherthing();

这样的话问题就出现了,else与第二句匹配了。
如果加上do while即可避免这类情况的发生。
这样的宏定义是有测效的,上面的那种用空循环的方法是种解决的途径。
因为一般的编译器对这种无用的循环都进行优化。
所以阅读linux源码的时候就可以发现,只要是涉及完整操作的宏定义,一般都会加上do while

源码:

rbtree.h

/*
 Red Black Trees
 (C) 1999 Andrea Arcangeli <andrea@suse.de>
 
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 GNU General Public License for more details.
 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 linux/include/linux/rbtree.h
 To use rbtrees you'll have to implement your own insert and search cores.
 This will avoid us to use callbacks and to drop drammatically performances.
 I know it's not the cleaner way, but in C (not in C++) to get
 performances and genericity...
 Some example of insert and search follows here. The search is a plain
 normal search over an ordered tree. The insert instead must be implemented
 int two steps: as first thing the code must insert the element in
 order as a red leaf in the tree, then the support library function
 rb_insert_color() must be called. Such function will do the
 not trivial work to rebalance the rbtree if necessary.
-----------------------------------------------------------------------
static inline struct page * rb_search_page_cache(struct inode * inode,
 unsigned long offset)
{
struct rb_node * n = inode->i_rb_page_cache.rb_node;
struct page * page;
while (n)
{
page = rb_entry(n, struct page, rb_page_cache);
if (offset < page->offset)
n = n->rb_left;
else if (offset > page->offset)
n = n->rb_right;
else
return page;
}
return NULL;
}
static inline struct page * __rb_insert_page_cache(struct inode * inode,
   unsigned long offset,
   struct rb_node * node)
{
struct rb_node ** p = &inode->i_rb_page_cache.rb_node;
struct rb_node * parent = NULL;
struct page * page;
while (*p)
{
parent = *p;
page = rb_entry(parent, struct page, rb_page_cache);
if (offset < page->offset)
p = &(*p)->rb_left;
else if (offset > page->offset)
p = &(*p)->rb_right;
else
return page;
}
rb_link_node(node, parent, p);
return NULL;
}
static inline struct page * rb_insert_page_cache(struct inode * inode,
 unsigned long offset,
 struct rb_node * node)
{
struct page * ret;
if ((ret = __rb_insert_page_cache(inode, offset, node)))
goto out;
rb_insert_color(node, &inode->i_rb_page_cache);
out:
return ret;
}
-----------------------------------------------------------------------
*/

#ifndef_LINUX_RBTREE_H
#define_LINUX_RBTREE_H

#include <linux/kernel.h>
#include <linux/stddef.h>

struct rb_node
{
    /** 下面三句话由博客https://blog.csdn.net/zhangchiytu/article/details/8471202 中发现,对理解源码有莫大的帮助
       * 本结构体四字节对齐,因而其地址中低位两个bit永远是0。//为什么?
       * Linux内核开发人员非常爱惜内存,他们用其中一个空闲的位来存储颜色信息。
       * parent_color成员实际上包含了父节点指针和自己的颜色信息。
       */
unsigned long  rb_parent_color;//父节点指针和自己的颜色信息。
   #defineRB_RED0  //红为0
   #defineRB_BLACK1  //黑为1
struct rb_node *rb_right; //右儿子
struct rb_node *rb_left;  //左儿子
} __attribute__((aligned(sizeof(long))));//扩大占用内存
/*
把结构体的地址按“sizeof(long)”对齐,
这个结构window不适用,因为linux 任何int类型的对象或 指针的地址的最低两位必须是0
在linux的实现中没有key域
*/struct rb_root{ //根节点
struct rb_node *rb_node;
};

​
#define rb_parent(r)   ((struct rb_node *)((r)->rb_parent_color & ~3)) //最后两位为00,其他为1 所以用来得到父节点的指针
#define rb_color(r)   ((r)->rb_parent_color & 1) //最后一位为1,其他为0 所以用来得到当前节点的颜色
#define rb_is_red(r)   (!rb_color(r))  //判断当前节点是否为红色,是返回true,不会返回true
#define rb_is_black(r) rb_color(r) //判断当前节点是否为黑色,是返回true,不会返回true
#define rb_set_red(r) do { (r)->rb_parent_color &= ~1; } while (0) //最后一位为0,其他为1 将当前节点颜色变为red,不过搞不懂为什么要用do while
#define rb_set_black(r) do { (r)->rb_parent_color |= 1; } while (0) //最后一位为1,其他为1 将当前节点颜色变为black,不过搞不懂为什么要用do while//内联函数,设置节点的父节点
static inline void rb_set_parent(struct rb_node *rb, struct rb_node *p){ //改变节点的颜色,参数为(节点1,节点2) ,将节点1的父节点的指针变成节点2
rb->rb_parent_color = (rb->rb_parent_color & 3) | (unsigned long)p; //&3 清空掉除了自己的颜色以外的信息,将前面颜色变为节点2的地址
}
​
//内联函数,设置节点的color
static inline void rb_set_color(struct rb_node *rb, int color){  //改变当前节点的颜色,参数为(当前节点,和需要设置颜色)
rb->rb_parent_color = (rb->rb_parent_color & ~1) | color; //&~1 去点最后一个的颜色, |color 将颜色设置为color
}

#define RB_ROOT(struct rb_root) { NULL, }
#definerb_entry(ptr, type, member) container_of(ptr, type, member) //linux内核的宏函数container_of(ptr,type,member),作用:通过一个结构变量中一个成员的地址找到这个结构体变量的首地址。

#define RB_EMPTY_ROOT(root)((root)->rb_node == NULL) //判断树是否为空
#define RB_EMPTY_NODE(node)(rb_parent(node) == node) //判断节点是否为空
#define RB_CLEAR_NODE(node)(rb_set_parent(node, node)) //清除这个节点

extern void rb_insert_color(struct rb_node *, struct rb_root *);//插入节点后调整平衡函数
extern void rb_erase(struct rb_node *, struct rb_root *); //删除函数

extern struct rb_node *rb_next(const struct rb_node *); //返回node在树中的后继,这个稍微复杂一点。如果node的右孩子不为空,它只要返回node的右子树中最小的结点即可;如果为空,它要向上查找,找到迭带结点是其父亲的左孩子的结点,返回父结点(如图中16的next是17)。如果一直上述到了根结点,返回NULL。
extern struct rb_node *rb_prev(const struct rb_node *); //返回node的前驱,和rb_next中的操作对称。
extern struct rb_node *rb_first(const struct rb_root *); //在以root为根的树中找出并返回最小的那个结点,只要从根结点一直向左走就是了。
extern struct rb_node *rb_last(const struct rb_root *); //是找出并返回最大的那个,一直向右走。

extern void rb_replace_node(struct rb_node *victim, struct rb_node *new, struct rb_root *root); //用new替换以root为根的树中的victim结点。
//它把parent设为node的父结点,并且让rb_link指向node。
static inline void rb_link_node(struct rb_node * node, struct rb_node * parent,struct rb_node ** rb_link){
node->rb_parent_color = (unsigned long )parent;
node->rb_left = node->rb_right = NULL; //将节点左右儿子清空
*rb_link = node; //红黑链指向该节点
}

#endif/* _LINUX_RBTREE_H */

 

rbtree.c

/*
 Red Black Trees
 (C) 1999 Andrea Arcangeli <andrea@suse.de>
 (C) 2002 David Woodhouse <dwmw2@infradead.org>
​
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 GNU General Public License for more details.
 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 linux/lib/rbtree.c
*/
​
#include <linux/module.h>
#include <linux/rbtree.h>//正常左旋操作,比AVL平衡树多更新了一个更新父节点
static void __rb_rotate_left(struct rb_node *node, struct rb_root *root) {  //节点左旋
   struct rb_node *right = node->rb_right;           //右儿子
   struct rb_node *parent = rb_parent(node);         //该节点的父亲
   if ((node->rb_right = right ->rb_left))   //将右节点的左儿子变成当前节点的右儿子,正常左旋操作
       rb_set_parent(right->rb_left, node);  //将过继的儿子的父节点指针换成新父亲
   right->rb_left = node;  //将右节点的左儿子变成当前节点,正常左旋操作
   rb_set_parent(right, parent);  //将过继的儿子的父节点指针换成新父亲
   if (parent) {                  //如果父亲节点存在
       if (node == parent->rb_left)  //正常过继操作
           parent->rb_left = right;
       else
           parent->rb_right = right;  //正常过继操作
  } else
       root->rb_node = right;  //如果不存在则right变成root
   rb_set_parent(node, right);  //将过继的儿子的父节点指针换成新父亲
}
​
//正常右旋操作,比AVL平衡树多更新了一个更新父节点
static void __rb_rotate_right(struct rb_node *node, struct rb_root *root) {
   struct rb_node *left = node->rb_left;
   struct rb_node *parent = rb_parent(node);
​
   if ((node->rb_left = left->rb_right)) rb_set_parent(left->rb_right, node);
   left->rb_right = node;
​
   rb_set_parent(left, parent);
​
   if (parent) {
       if (node == parent->rb_right)
           parent->rb_right = left;
       else
           parent->rb_left = left;
  } else
       root->rb_node = left;
   rb_set_parent(node, left);
}
​
//(其实是个红黑子树,可以不断向上找父亲变成更大的红黑子树)在红黑树root中对于插入node结点后的自平衡处理。
void rb_insert_color(struct rb_node *node, struct rb_root *root) {  //在当前节点插入一个新节点
   struct rb_node *parent, *gparent;
   while ((parent = rb_parent(node)) && rb_is_red(parent)) {  //得到父节点,当父节点是红色的时候
       gparent = rb_parent(parent);  //得到爷爷节点
       if (parent == gparent->rb_left) {  //当父节点是爷爷节点的左儿子
          {
               register struct rb_node *uncle = gparent->rb_right;  //舅舅节点
               if (uncle && rb_is_red(uncle)) {  //如果舅舅节点也是红色
                   //**情况1**(自己和兄弟相同颜色和父亲不同颜色)
                   rb_set_black(uncle);
                   rb_set_black(parent);
                   rb_set_red(gparent);
                   node = gparent;
                   continue;  //继续往上调整
              }
          }
​
           if (parent->rb_right == node) {  
               //**情况2**(Z字抖动)
               register struct rb_node *tmp;
               __rb_rotate_left(parent, root);  //先左旋根节点
               tmp = parent;
               parent = node;
               node = tmp;
          }
           //**情况3**(直链)
           rb_set_black(parent);
           rb_set_red(gparent);
           __rb_rotate_right(gparent, root);
      } else {  //左右颠倒的相同操作,父亲节点是爷爷节点的右儿子
          {
               register struct rb_node *uncle = gparent->rb_left;
               if (uncle && rb_is_red(uncle)) {  //如果存在舅舅节点且为红色
                   rb_set_black(uncle);
                   rb_set_black(parent);
                   rb_set_red(gparent);
                   node = gparent;
                   continue;
              }
          }
​
           if (parent->rb_left == node) {  //如果节点是父节点的左儿子
               register struct rb_node *tmp;
               __rb_rotate_right(parent, root);
               tmp = parent;
               parent = node;
               node = tmp;
          }
           rb_set_black(parent);
           rb_set_red(gparent);
           __rb_rotate_left(gparent, root);
      }
  }
   rb_set_black(root->rb_node); //最后平衡后的节点一定是黑色
}
EXPORT_SYMBOL(rb_insert_color);  //将一个函数以符号的方式导出给其他模块使用。
//(其实是个红黑子树,可以不断向上找父亲变成更大的红黑子树)
static void __rb_erase_color(struct rb_node *node, struct rb_node *parent, struct rb_root *root) {
   struct rb_node *other;
   while ( (!node || rb_is_black(node)) && node != root->rb_node) {  //如果节点不存在或者节点为黑色并且节点不等于影响的子红黑树的根
       if (parent->rb_left == node) {  //该节点是父亲节点的左儿子
           other = parent->rb_right;   //兄弟节点
           if (rb_is_red(other)) {     //如果兄弟节点是红色
               //**情况3.1(红兄)**
               rb_set_black(other);
               rb_set_red(parent);
               __rb_rotate_left(parent, root);
               other = parent->rb_right;
          }
           //下面都是黑兄问题
           if ((!other->rb_left || rb_is_black(other->rb_left)) && (!other->rb_right || rb_is_black(other ->rb_right))) {  //如果(兄弟节点没有左儿子或者左儿子为黑色)&&(兄弟节点没有右儿子或者右儿子为黑色)
               //**情况3.2( 黑兄二黑侄 )**
               rb_set_red(other);
               node = parent;
               parent = rb_parent(node);
          } else {  //如果兄弟节点的两个儿子至少有一个是红色
               //**情况3.3( 黑兄一黑一红侄 )**
               if (!other->rb_right || rb_is_black( other ->rb_right)) {  //如果兄弟节点没有右儿子或者右儿子是黑色
                   //右黑侄
                   rb_set_black(other->rb_left);
                   rb_set_red(other);
                   __rb_rotate_right(other, root);
                   other = parent->rb_right;
              }
               ////右红侄,这里一定是兄弟节点没有右儿子或者右儿子是红色的情况
               rb_set_color(other, rb_color(parent));
               rb_set_black(parent);
               rb_set_black(other->rb_right);
               __rb_rotate_left(parent, root);
               node = root->rb_node;
               break;
          }
      } else {
           //和上面一样的操作,方向相反而已
           other = parent->rb_left;
           if (rb_is_red(other)) {
               rb_set_black(other);
               rb_set_red(parent);
               __rb_rotate_right(parent, root);
               other = parent->rb_left;
          }
           if ((!other->rb_left || rb_is_black(other->rb_left)) &&
              (!other->rb_right || rb_is_black(other->rb_right))) {
               rb_set_red(other);
               node = parent;
               parent = rb_parent(node);
          } else {
               if (!other->rb_left || rb_is_black(other->rb_left)) {
                   rb_set_black(other->rb_right);
                   rb_set_red(other);
                   __rb_rotate_left(other, root);
                   other = parent->rb_left;
              }
               rb_set_color(other, rb_color(parent));
               rb_set_black(parent);
               rb_set_black(other->rb_left);
               __rb_rotate_right(parent, root);
               node = root->rb_node;
               break;
          }
      }
  }
   if (node) rb_set_black(node);
}
​
/*
*删除函数,类似于二叉树删除函数,不过在删除完成以后需要调用调整函数恢复性质
*总的过程也是按z的左右儿子情况进行分类.
*1.z只有左儿子/右儿子
*2.z有左右儿子,z的后继结点是z的右儿子
*3.z有左右儿子,z的后继结点不是z的右儿子
*
* 其实它并没有真正删除node,而只是让它和以root为根的树脱离关系,最后它还要判断是否调用__rb_erase_color来调整
*/
void rb_erase(struct rb_node *node, struct rb_root *root) {
   //在下面的过程中parent总是指向树中会被删除的结点或是会被替代的结点
   // child总是指向要替代node或parent的结点
   struct rb_node *child, *parent;
   int color;
   if (!node->rb_left)          //如果当前节点没有左儿子
       child = node->rb_right;  //删除节点的儿子
   else if (!node->rb_right)    //如果当前节点没有右儿子
       child = node->rb_left;   //删除节点的儿子
   else {                       //如果两个都有的话
       struct rb_node *old = node, *left; //old为原来的删除节点
       node = node->rb_right;  //先跳到右儿子
       while ((left = node->rb_left) != NULL)
           node = left;  // node为原来节点的后继节点 (找到右儿子的最左的孩子)
       if (rb_parent(old)) {  //如果删除的节点不是根节点
           if (rb_parent(old)->rb_left == old)
               rb_parent(old)->rb_left = node;  //让原来节点的父亲指向后继节点
           else
               rb_parent(old)->rb_right = node;  //让原来节点的父亲指向后继节点
      } else
           root->rb_node = node;  
​
       //后继节点一定没有左儿子
       child = node->rb_right;    //后继节点的唯一儿子
       parent = rb_parent(node);  //后继节点的父亲
       color = rb_color(node);    //后继节点的颜色
if (parent == old) {  //后继节点是原来节点的儿子
           parent = node;
      } else {  //后继节点不是原来节点的儿子
           if (child)
               rb_set_parent( child, parent);  //将后继节点的儿子和后继节点的父亲接上(相当于删除掉后继节点)
           parent->rb_left = child;  //将后继节点的儿子和后继节点的父亲接上(相当于删除掉后继节点)
           node->rb_right = old->rb_right;  //将后继节点完全代替成原来的节点
           rb_set_parent(old->rb_right, node);
      }
​
       node->rb_parent_color = old->rb_parent_color;  //将后继节点完全代替成原来的节点
       node->rb_left = old->rb_left;  //将后继节点完全代替成原来的节点
       rb_set_parent(old->rb_left, node);
       goto color;  //判断是否会影响树的平衡
  }
   //删除节点最多只有一个儿子的时候
   parent = rb_parent(node);  //删除节点的父亲
   color = rb_color(node);    //删除节点的颜色
   // child是删除节点的儿子
   if (child)
       rb_set_parent(child,parent);  //如果有儿子,则儿子的父亲变成自己的父亲(相当于删除了该节点)
   if (parent) {     //就是简单的删除节点,让父亲指向儿子
       if (parent->rb_left == node)
           parent->rb_left = child;
       else
           parent->rb_right = child;
  } else
       root->rb_node = child;
​
//如果node原来的颜色是黑色,那么就意味着有一个黑色结点被覆盖了,
//红黑树的性质可能会被破坏(性质4或5),需要调整
color:
   if (color == RB_BLACK)
       __rb_erase_color(child, parent, root);  //如果颜色为黑色则开始调整平衡
}
EXPORT_SYMBOL(rb_erase);  //将一个函数以符号的方式导出给其他模块使用。
/*
* 返回树的第一个节点(key最小的点)(按排序顺序)
*/
struct rb_node *rb_first(const struct rb_root *root) {
   struct rb_node *n;
   n = root->rb_node;
   if (!n) return NULL;
   while (n->rb_left) n = n->rb_left;
   return n;
}
EXPORT_SYMBOL(rb_first);  //将一个函数以符号的方式导出给其他模块使用。
/*
* 返回树的最后一个节点(key最大的点)(按排序顺序)
*/
struct rb_node *rb_last(const struct rb_root *root) {
   struct rb_node *n;
​
   n = root->rb_node;
   if (!n) return NULL;
   while (n->rb_right) n = n->rb_right;
   return n;
}
EXPORT_SYMBOL(rb_last);  //将一个函数以符号的方式导出给其他模块使用。
//返回node在树中的后继
struct rb_node *rb_next(const struct rb_node *node) {
   struct rb_node *parent;
   if (rb_parent(node) == node) return NULL;
​
   /* If we have a right-hand child, go down and then left as far
      as we can. */
   if (node->rb_right) {  //如果有右儿子,找右儿子最左的后继节点
       node = node->rb_right;
       while (node->rb_left) node = node->rb_left;
       return (struct rb_node *)node;
  }
​
   /*如果没有右儿子,则往上找,知道找到第一个(左链)的节点,这个节点一定是第一个大于删除节点的点
    */
   while ((parent = rb_parent(node)) && node == parent->rb_right)
       node = parent;
​
   return parent;
}
EXPORT_SYMBOL(rb_next);  //将一个函数以符号的方式导出给其他模块使用。
//返回node在树中的前继
struct rb_node *rb_prev(const struct rb_node *node) {
   struct rb_node *parent;
​
   //如果是根节点
   if (rb_parent(node) == node) return NULL;
​
   //如果有左儿子,找最儿子最右的节点
   if (node->rb_left) {
       node = node->rb_left;
       while (node->rb_right) node = node->rb_right;
       return (struct rb_node *)node;
  }
​
   /* 一直往上找,直到第一个(右儿子链)时的节点就是第一个小于删除的节点 */
   while ((parent = rb_parent(node)) && node == parent->rb_left) node = parent;
​
   return parent;
}
EXPORT_SYMBOL(rb_prev);  //将一个函数以符号的方式导出给其他模块使用。
//替换节点
void rb_replace_node(struct rb_node *victim, struct rb_node *new,
                    struct rb_root *root) {
   struct rb_node *parent = rb_parent(victim);  //被替换的节点的父亲
/* 将周围的节点指向替换掉节点*/
   if (parent) {
       if (victim == parent->rb_left)
           parent->rb_left = new;
       else
           parent->rb_right = new;
  } else {
       root->rb_node = new;
  }
​
   //改替换节点的左儿子右儿子的父亲指针
   if (victim->rb_left) rb_set_parent(victim->rb_left, new);
   if (victim->rb_right) rb_set_parent(victim->rb_right, new);
​
   /* 将受害者的指针/颜色复制到替换者 */
   *new = *victim;
}
EXPORT_SYMBOL(rb_replace_node); //将一个函数以符号的方式导出给其他模块使用。

 

 

 

posted @ 2020-07-21 21:02  Slimes  阅读(3301)  评论(3编辑  收藏  举报