一、理论基础
红黑树是每个节点都带有颜色属性的二叉查找树,颜色为红色或黑色。在二叉查找树强制一般要求以外,对于任何有效的红黑树我们增加了如下的额外要求:
性质1. 节点是红色或黑色。
性质2. 根是黑色。
性质3. 所有叶子都是黑色(叶子是NIL节点)。
性质4. 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
性质5. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。
rbtree with the following properties.
1. Every node has a value.
2. The value of any node is greater than the value of its left child and less than the value of its right child.
3. Every node is colored either red or black.
4. Every red node that is not a leaf has only black children.
5. Every path from the root to a leaf contains the same number of black nodes.
6. The root node is black.
红黑树的每个节点上的属性除了有一个key、3个指针:parent、left、right以外,还多了一个属性:color,如果相应的指针域没有,则设为NIL。
我们真正关心的是key域,之所以要构建rbtree,就是为了尽量减少查找key域的时间,保证为O(logn)。
当然牺牲了些插入,删除的时间,为了保证rbtree的五大特性,每次插入或者删除一个node都必须
保证五大性质依然存在,因此要多出一些额外的操作。如图:
图中key域是数字,大小当然好比较,任意节点的左子树上key一定小于右子树上key。
二、rbtree在linux中的实现
代码位置
kernel/lib/rbtree.c
kernel/include/linux/rbtree.h
kernel/Documentation/rbtree.tx //描述如何使用红黑树的说明文档
typedef struct st_rb_node {
/**
* 本结构体四字节对齐,因而其地址中低位两个bit永远是0。//为什么?
* Linux内核开发人员非常爱惜内存,他们用其中一个空闲的位来存储颜色信息。
* parent_color成员实际上包含了父节点指针和自己的颜色信息。
*/
unsigned long parent_color;//保存父节点的指针值(父节点的地址)同时保存节点的color
#define RB_RED 0
#define RB_BLACK 1
structst_rb_node *left, *right;
}__attribute__((aligned(sizeof(long))))rb_node;
但在linux的实现中没有key域,这已经是linux数据结构的一大特色,就是结构不包括数据,而是由数据和基本结构被包括在同一个struct中。(就像list_head中没有data域,需要用链表的struct中要包含list_head域一样)由结构体获取数据信息是通过CONTAINER_OF这个宏来实现的,它利用了一些编译器的特性,有兴趣的可以参考Linux的链表源码。
rb_node结构体,被一个”__attribute__((aligned(sizeof(long))))”所包装(非常重要,技巧就在这!),
”__attribute__((aligned(sizeof(long))))“的意思是把结构体的地址按“sizeof(long)”对齐,
对于32位机,sizeof(long)为4 (即结构体内的数据类型的地址为4的倍数)。对于64位机,sizeof(long)为8(结构体内的数据类型的地址为8的倍数).这个地方需要补一下字节对齐的知识:
|
所以以4(或8)为倍数的地址以二进制表示的特点是:以4为倍数(字节对齐)的地址(32位机器)最后两位肯定为零(看好了是存储变量的地址,而不是变量),对于64位机器是后三位肯定为零。
对于rb-tree 中每一个节点,都需要标记一个颜色(只有两种选择:红 vs 黑),而这里的技巧就在“__attribute__((aligned(sizeof(long))))”,因为红黑树的每个节点都用rb_node结构来表示,利用字节对齐技巧,任何rb_node结构体的地址的低两位肯定都是零,与其空着不用,还不如用它们表示颜 色,反正颜色就两种,其实一位就已经够了。unsigned long rb_parent_color变量有两个作用(见名知义):
1.存储父节点的地址(注意IA32父节点的地址为4的倍数,地址后面的2位没有用上 ,IA64则父节点的地址为8的倍数,地址后面的3位没有用上)。
2.用后2位,标识此节点的color(:红 vs 黑 )
include/linux/rbtree.h中有几个与rb_parent_color相关的宏
#definerb_parent(r) ((struct rb_node*)((r)->rb_parent_color & ~3))
//取 r节点的父节点的地址,即把r节点的后两位清零后与r节点存储的父节点进行与操作。
//假设r->rb_parent_color 的内容为 0x20000001(表示父节点的地址为0x20000000,节点颜色为黑),那么执行//此操作后,取得父节点的地址为 0x20000000
#definerb_color(r) ((r)->rb_parent_color& 1)
// 设置r节点的颜色为黑 ,只要看最后一位即可.
#definerb_is_red(r) (!rb_color(r))
// 测试r节点是否为红
#definerb_is_black(r) rb_color(r)
//测试r节点是否为黑
#definerb_set_red(r) do {(r)->rb_parent_color &= ~1; } while (0)
//设置r节点是为红
#definerb_set_black(r) do {(r)->rb_parent_color |= 1; } while (0)
//设置r节点是为黑
//内联函数,设置节点的父节点
static inline voidrb_set_parent(struct rb_node *rb, struct rb_node *p)
{
rb->rb_parent_color =(rb->rb_parent_color & 3) | (unsigned long)p;
}
//内联函数,设置节点的color
static inline voidrb_set_color(struct rb_node *rb, int color)
{
rb->rb_parent_color =(rb->rb_parent_color & ~1) | color;
}
这样,提取parent指针只要把rb_parent_color成员的低两位清零即可:
#define rb_parent(r) ((struct rb_node*)((r)->rb_parent_color & ~3))
取颜色只要看最后一位即可:
#define rb_color(r) ((r)->rb_parent_color &1)
测试颜色和设置颜色也是水到渠成的事了。需要特别指出的是下面的一个内联函数:
static inline void rb_link_node(struct rb_node *node, struct rb_node * parent, struct rb_node ** rb_link);
它把parent设为node的父结点,并且让rb_link指向node。
两个基本操作:左旋与右旋
__rb_rotate_left是把以root为根的树中的node结点进行左旋,__rb_rotate_right是进行右旋。这两个函数是为后面的插入和删除服务,而不是为外部提供接口。
其余的几个接口就比较简单了
struct rb_node *rb_first(struct rb_root *root);
在以root为根的树中找出并返回最小的那个结点,只要从根结点一直向左走就是了。如图中的3
struct rb_node *rb_last(struct rb_root *root);
是找出并返回最大的那个,一直向右走。如图中的47
struct rb_node *rb_next(struct rb_node *node);//注意这里next的概念,不是根据节点的位置,而是由其key域决定的,
//rbtree本身是有序的,所以可以有前序,中序,后续遍历方式
返回node在树中的后继,这个稍微复杂一点。如果node的右孩子不为空,它只要返回node的右子树中最小的结点即可(如图中17的next是19);如果为空,它要向上查找,找到迭带结点是其父亲的左孩子的结点,返回父结点(如图中16的next是17)。如果一直上述到了根结点,返回NULL。
struct rb_node *rb_prev(struct rb_node *node);
返回node的前驱,和rb_next中的操作对称。
void rb_replace_node(struct rb_node *victim, structrb_node *new, struct rb_root *root);
用new替换以root为根的树中的victim结点。
三、key域为字符串如何构建rbtree?
当key域为字符串时,其可以构建rbtree的基础是字符串也是可以比较大小的。
先从strcmp函数说起, strcmp函数是比较两个字符串的大小,返回比较的结果。一般形式是:
i=strcmp(字符串1,字符串2);
其中,字符串1、字符串2均可为字符串常量或变量;i 是用于存放比较结果的整型变量。比较结果是这样规定的:
①字符串1小于字符串2,strcmp函数返回一个负值;
②字符串1等于字符串2,strcmp函数返回零;
③字符串1大于字符串2,strcmp函数返回一个正值;
那么,字符中的大小是如何比较的呢?来看一个例子。
实际上,字符串的比较是比较字符串中各对字符的ASCII码。首先比较两个串的第一个字符,若不相等,则停止比较并得出大于或小于的结果;如果相等就接着 比较第二个字符然后第三个字符等等。如果两上字符串前面的字符一直相等,像"disk"和"disks" 那样, 前四个字符都一样, 然后比较第五个字符, 前一个字符串"disk"只剩下结束符'/0',后一个字符串"disks"剩下's','/0'的ASCII码小于's'的ASCII 码,所以得出了结果。因此无论两个字符串是什么样,strcmp函数最多比较到其中一个字符串遇到结束符'/0'为止,就能得出结果。
注意:字符串是数组类型而非简单类型,不能用关系运算进行大小比较。
if("ABC">"DEF") /*错误的字符串比较*/
if(strcmp("ABC","DEF") /*正确的字符串比较*/
正是有了这个理论基础,我们才可以构建key域为string类型的rbtree,其构建方法见
./kernel/Documentation/rbtree.txt ,摘录如下
Creating a new rbtree
---------------------
Data nodes in an rbtree tree are structurescontaining a struct rb_node member:
struct mytype
{
struct rb_node node;
char *keystring;
};
When dealing with a pointer to the embeddedstruct rb_node, the containing data structure may be accessed with the standardcontainer_of() macro. In addition,individual members may be accessed directly via rb_entry(node, type, member).
At the root of each rbtree is an rb_rootstructure, which is initialized to be empty via:
struct rb_root mytree = RB_ROOT;
Searching for a value in an rbtree
----------------------------------
Writing a search function for your tree isfairly straightforward: start at the root, compare each value, and follow theleft or right branch as necessary.
Example:
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;
}
Inserting data into an rbtree
-----------------------------
Inserting data in the tree involves firstsearching for the place to insert the new node, then inserting the node andrebalancing ("recoloring") the tree.
The search for insertion differs from theprevious search by finding the location of the pointer on which to graft thenew node. The new node also needs a link to its parent node for rebalancingpurposes.
Example:
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)
{
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;
}
Removing or replacing existing data in anrbtree
------------------------------------------------
To remove an existing node from a tree,call:
void rb_erase(struct rb_node *victim, struct rb_root *tree);
Example:
struct mytype *data = mysearch(mytree, "walrus");
if(data)
{
rb_erase(data->node, mytree);
myfree(data);
}
To replace an existing node in a tree with anew one with the same key, call:
voidrb_replace_node(struct rb_node *old, struct rb_node *new, struct rb_root*tree);
Replacing a node this way does not re-sortthe tree: If the new node doesn't have the same key as the old node, the rbtreewill probably become corrupted.
Iterating through the elements stored in anrbtree (in sort order)
在Linux中有很多地方用到了RD树。anticipatory, deadline, 和CFQ I/O调度都使用的是RB树进行请求跟踪,还有CD/DVD驱动的包管理也是如此。高精度计时器(high-resolutiontimer)使用RB树组织定时请求。EXT3文件系统也使用RB树来管理目录。虚拟存储管理系统也是有RB树进行VMAs(Virtual Memory Areas)的管理。当然还有文件描述符,密码钥匙,“等级令牌桶”调度的网络数据包都是用RB数据进行组织和管理的。另外C++ STL中,关联式容器 set 和 map 的底层实现是基于 RB-tree。
android中使用rbtree的地方
1)/kernel/kernel/power/userwakelock.c //管理用户空间的wakelock
这个文件是用rbtree的精简版,仅仅使用几个接口就实现了,这个也是key域为字符串,其实是wake_lock的name.
用rbtree来连接了所有从用户空间申请的wakelock。
可以通过这个例子,梳理一下rbtree的使用:
首先,为什么要使用rbtree?
当用户空间申请了成百上千的wakelock时,由rbtree这一数据结构仅仅通过name就可以快速的找到此lock
其次,如何使用rbtree?
构建struct user_wake_lock
由name--------> rbtree_node -------->user_wake_lock -------->wake_lock
可以想象各个结构在内存中的布局:通过 rbtree_node联系起来的树,每个节点又是另一个struct的成员
2)binder
这个还没有看。。。