Linux内核数据结构之红黑树
Posted Linux与嵌入式系统
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux内核数据结构之红黑树相关的知识,希望对你有一定的参考价值。
红黑树由于节点颜色的特性,保证其是一种自平衡的二叉搜索树。
红黑树的一系列规则虽然实现起来比较复杂,但是遵循起来却比较简单,而且红黑树的插入,删除性能也还不错。
1. 红黑树必须满足的规则:
a. 所有节点都有颜色,要么红色,要么黑色
b. 根节点是黑色,所有叶子节点也是黑色
c. 叶子节点中不包含数据
d. 非叶子节点都有2个子节点
e. 如果一个节点是红色,那么它的父节点和子节点都是黑色的
f. 从任何一个节点开始,到其下叶子节点的路径中都包含相同数目的黑节点
红黑树中最长的路径就是红黑交替的路径,最短的路径是全黑节点的路径,再加上根节点和叶子节点都是黑色,
从而可以保证红黑树中最长路径的长度不会超过最短路径的2倍。Linux内核红黑树的算法都定义在linux-2.6.38.8/include/linux/rbtree.h和linux-2.6.38.8/lib/rbtree.c两个文件中。
内核中红黑树的使用和链表(list)有些类似,是将红黑树的节点放入自定义的数据结构中来使用的。
首先需要注意的一点是红黑树节点的定义:
struct rb_node
{
unsigned long rb_parent_color;
#define RB_RED 0
#define RB_BLACK 1
struct rb_node *rb_right;
struct rb_node *rb_left;
}__attribute__((aligned(sizeof(long))));
刚开始看到这个定义的时候,我觉得很奇怪,等到看懂了之后,才知道原来作者巧妙的利用内存对齐来将2个内容存入到一个字段中(不服不行啊^_^!)。
字段 rb_parent_color 中保存了2个信息:
b. 本节点的颜色
注:sizeof(long) 在32bit系统中是4 bytes,在64bit系统中是8 bytes。
明白了这点之后,rb_tree.h 中很多宏的定义也就很好懂了。
#definerb_parent(r) ((struct rb_node*)((r)->rb_parent_color & ~3))
/* 取得 rb_parent_color 二进制表示的最后一位,即用于保存颜色信息的那一位 */
#definerb_color(r) ((r)->rb_parent_color& 1)
/* 将 rb_parent_color 二进制表示的最后一位置为0,即置为红色 */
#definerb_set_red(r) do { (r)->rb_parent_color&= ~1; } while (0)
/* 将 rb_parent_color 二进制表示的最后一位置为1,即置为黑色 */
#definerb_set_black(r) do {(r)->rb_parent_color |= 1; } while (0)
2. rb_tree.c中的5个函数
a. 左旋操作:
当右子树的长度过大导致树不平衡时,进行左旋操作
/*
* 左旋操作其实就3个动作:见图left
* 1.node的右子树关联到right的左子树
* 2.right的左子树关联到node
* 3.right取代node的位置
* 其他带代码都是一些相应的parent指针的变化
*/
static void__rb_rotate_left(struct rb_node *node, struct rb_root *root)
{
/* 初始化相对于node节点的父节点(图中的P)和右节点(图中的R) */
struct rb_node *right = node->rb_right;
struct rb_node *parent = rb_parent(node);
/* 步骤1 */
if ((node->rb_right =right->rb_left))
rb_set_parent(right->rb_left, node);
/* 步骤2 */
right->rb_left = node;
rb_set_parent(right, parent);
/* node的parent NOT NULL 时,right取代原先的node的位置 */
if (parent)
{
if (node == parent->rb_left)
parent->rb_left = right;
else
parent->rb_right = right;
}
/* node的parent NULL 时,说明node原先时root节点,将新的root指向root即可 */
else
root->rb_node = right;
rb_set_parent(node, right);
}
左旋操作图解:
b. 右旋操作,和左旋操作类似。
c. 追加节点后,设置此节点的颜色。
/*
* 本函数没有插入节点的功能,只是在插入新节点后,设置新节点的颜色,从而保证红黑树的平衡性。
* 新插入的节点默认都是红色的。
*
* 下面的代码看着复杂,其实只要时时记住红黑树的几个重要特性,就会发现下面的都是在尽量保持住红黑树的这些特性。
* 1. 无论从哪个节点开始,到其叶子节点的路径中包含的黑色节点个数时一样的
* 2. 不能有连续的2个红色节点,即父节点和子节点不能同时为红色
* 所以最简单的情况就是:插入节点的父节点是黑色的。那么插入一个红节点后不会有任何影响。
* 3. 左旋操作有减少右子树高度的作用
* 4. 同理,右旋操作有减少左子树高度的作用
*/
void rb_insert_color(structrb_node *node, struct rb_root *root)
{
struct rb_node *parent, *gparent;
while ((parent = rb_parent(node))&& rb_is_red(parent))
{
gparent = rb_parent(parent);
/* parent 是 gparent的左子树时 */
if (parent == gparent->rb_left)
{
{
/* gparent的左右子树的黑色节点都增加一个,仍然平衡 */
register struct rb_node *uncle= gparent->rb_right;
if (uncle &&rb_is_red(uncle))
{
rb_set_black(uncle);
rb_set_black(parent);
rb_set_red(gparent);
node = gparent;
continue;
}
}
/* node为parent右子树时 */
if (parent->rb_right == node)
{
register struct rb_node *tmp;
/* 左旋后,parent的位置被node取代,然后再交换parent和node的位置,
* 相当于node是parent的左子树
* 由于node和parent都是红色(否则到不了这一步),parent左右子树的黑色节点数仍然是相等的
*/
__rb_rotate_left(parent, root);
tmp = parent;
parent = node;
node = tmp;
}
/* parent 红->黑,gparent左子树比右子树多一个黑色节点
* 右旋后,gparent左子树高度减一,减少的节点即parent,减少了一个黑色节点,parent变为新的gparent。
* 所以右旋后,新的gparent的左右子树的黑色节点数再次平衡了
*/
rb_set_black(parent);
rb_set_red(gparent);
__rb_rotate_right(gparent, root);
/* parent 是 gparent的右子树时,和上面的过程类似 */
} 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);
}
d. 删除一个节点,并且调整删除后各节点的颜色。其中调整节点颜色其实是另一个单独的函数。
/* 删除节点时,如果被删除的节点左子树==NULL或右子树==NULL或左右子树都==NULL
* 那么只要把被删除节点的左子树或右子树直接关联到被删节点的父节点上即可,剩下的就是调整各节点颜色。
* 只有被删节点是黑色才需要调整颜色,因为删除红色节点不影响红黑树的特性。
*
* 被删节点左右子树都存在的情况下,其实就是用中序遍历中被删节点的下一个节点来替代被删节点。
* 代码中的操作只是将各个指针指向新的位置而已。
*/
void rb_erase(structrb_node *node, struct rb_root *root)
{
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;
/* 寻找中序遍历中被删节点的下一个节点 */
node = node->rb_right;
while ((left = node->rb_left) !=NULL)
node = left;
/* 替换要删除的节点old */
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);
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;
color:
if (color == RB_BLACK)
__rb_erase_color(child, parent, root);
}
e. 删除一个黑色节点后,重新调整相关节点的颜色。
/* 这里的node就是上面函数中的child,所有node节点的左右子树肯定都是NULL
* 不满足红黑树规则的就是从parent节点开始的子树,只要给从parent开始的子树增加一个黑色节点就行
* 如果从parent节点开始的节点全是黑色,node和parent都继续向上移动
*/
static void__rb_erase_color(struct rb_node *node, struct rb_node *parent,
struct rb_root *root)
{
struct rb_node *other;
/* (node不为NULL 且 node是黑色的)或者 node == NULL */
while ((!node || rb_is_black(node))&& node != root->rb_node)
{
if (parent->rb_left == node)
{
other = parent->rb_right;
if (rb_is_red(other))
{
rb_set_black(other);
rb_set_red(parent);
__rb_rotate_left(parent, root);
other = parent->rb_right;
}
/* 如果从parent节点开始的节点全是黑色,node和parent都继续向上移动 */
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_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);
}
以上是关于Linux内核数据结构之红黑树的主要内容,如果未能解决你的问题,请参考以下文章