用户空间使用Linux数据结构系列之红黑树
红黑树由来:
他是在1972年 由Rudolf Bayer发明的,他称之为“对称二叉B树”,它现代的名字是Leo J. Guibas和 Robert Sedgewick 于1978年写的一篇论文中获得的。它是复杂的,但它的操作有着良好的最坏情况运行时间,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n 是树中元素的数目。
red-black tree(RB树)
是一种平衡二叉树,它主要用于存储或者说索引可排序的键值对数据。RB树(红黑树)与radix树和hash表都不同。radix树是一种比较适合用于存储稀疏的数据集而且将用一个大整数进行插入,删除,查找的操作基础。而hash表并不是以某种排序顺序进行存储,而且必须指定大小和hash函数。RB树与AVL树很相似,但是比AVL树有更好的插入和删除最坏情况的时间复杂度,以及O(log n)的最坏查找时间复杂度。
RB树定义
1.每个结点要么是红色要么是黑色;
2.根结点必须是黑色;
3.每个叶节点,即空节点(NULL)是黑色的。
4.红结点如果有孩子,其孩子必须都是黑色(红节点周边3个节点都是黑色);
5.从根结点到叶子的每条路径必须包含相同数目的黑结点。
红黑树是满足一定特征的二叉树,其本质还是二叉树。红黑树,一种二叉查找树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或
Black。
通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。既然是二叉查找树,那么它必满足二叉查找树的一般
性质。下面,在具体介绍红黑树之前,先来了解下 二叉查找树的一般性质:
1.在一棵二叉查找树上,执行查找、插入、删除等操作的时间复杂度为O(log
n)。因为,一棵由n个结点,随机构造的二叉查找树的高度为logn,所以顺理成章,一般操作的执行时间为O(log n)。至于n个结点的二叉树高度为O(log
n)的证明,可参考算法导论 第12章 二叉查找树
第12.4节。
2.但若是一棵具有n个结点的线性链,则此些操作最坏情况运行时间为O(n)。而红黑树,能保证在最坏情况下,基本的动态几何操作的时间均为
O(log n)。
引用:
在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。
相关资料:
Linux Weekly News article onred-black trees
http://lwn.net/Articles/184495/
Wikipedia entry on red-blacktrees
http://en.wikipedia.org/wiki/Red-black_tree
可见RB树(红黑树)在Linux内核中的重要性。
在Linux内核源代码中rb树的实现在
lib/rbtree.c
include/linux/rbtree.h//在使用rb的地方,可以通过#include "linux/rbtree.h"进行使用。
rbtree.txt //描述如何使用红黑树的说明文档
在Linux内核中的RB树实现与传统的实现方式有些不同。它对针对内核对速度的需要做了些优化。每一个rb_node节点是嵌入在用RB树进行组织的数据结构中,而不是用rb_node指针进行数据结构的组织。
如:
在RB树里的数据节点包含了一个rb_node数据结构节点。如下:
可以通过container_of宏取得包含了rb_node的数据结构。也可以通过
rb_entry(node,type,member)取得。
其实是#definerb_entry(node,type,member) container_of(node,type,member)
这里顺便说一下如何通过container_of(非常重要的一个宏)取得包含rb_node的数据结构指针:
在Linux内核代码里有这样的宏定义:
那么对于struct mytype,如何通过其成员node来取得走首地址呢:
我们肯定已经知道node的地址,假设为pnode;
struct mytype* x = rb_entry(pnode, structmytype, node);
意思是,取得pnode所被包含的数据结构对象的首地址,pnode是指向struct mytype的
成员node的指针。
宏替换完后就是这个样子:
typeof取得struct mytype中node成员的类型,用这个类型定义一个新的指针,把
pnode的值赋给它,在这里起到了类型检查的作用。
offsetof(type,member)是取得member在type对象中相对于此对象首地址的偏移量。
那么用__mptr减去这个偏移量就得到了type对象的起始地址。也就得到了把rb_node
作为嵌入的管理数据结构的对象起始地址。
向RB树中插入一个数据
在插入一个数据之前先要查找到适合插入的位置,然后将节点加入到树中并将树调整
到平衡状态。
删除一个数据节点
可以通过调用
void rb_replace_node(struct rb_node *old,struct rb_node *new,
struct rb_root *tree);
来替换一个节点,但是替换完成后并不会对RB树做任何调整,所以如果新节点的值与
被替换的值有所不同时,可能会出现问题。
下面将对rb-tree的主要数据结构进行分析(源码目录include/linux/rbtree.h)
先到include/linux/rbtree.h中看一下红黑树的一些定义,如下:
//节点描述
//根节点描述
对于rb_root只是rb_node*的一个封装 。
再来看看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 黑 )
红黑树的最主要特征,在于其颜色满足特定的性质。普通的节点添加,极有可能破坏红黑树的性质,所以在添加红黑树节点时,需要将整个红黑树的颜色进行调整。
在理解插入、删除操作之前要先理解两个函数,即红黑树的左旋和右旋。
左旋和右旋都是为了保证平衡二叉树的性质不变,即是通过左旋和右旋来保证红黑二叉树的第五条性质满足。红黑二叉树的添加跟普通的二叉树的添加类似。不过在添加节点后需要对节点的颜色进行调整,甚至对树的结构进行调整,来满足红黑树的性质。
1.5.1左旋
对照下图可以更好的理解下面的程序。将node设置为pivot,root为根。
//记住设置节点的左右孩子后,还要设置节点的parent
原文:http://www.cnblogs.com/darren-715/p/3770216.html