深入探索RB-tree数据结构
引子
部门在各个团队推广软件通用技能矩阵工具,希望通过度量找到能力薄弱点,引导团队进行改进。从我们团队的数据上看,团队在数据结构和算法上的短板明显,需要加强,这也是写这篇文章的背后的初衷。
数据结构和算法是程序员的基本技能,也是大牛程序员的试金石。Linus大神就曾说过:"bad programer care about code, good progamer care about data"。平时工作中,可能大家觉得用不到复杂的数据结构,数组、顶多链表就够用了,但是实际情况可能是因为不了解其他的数据结构以及各自的适用场景,只有数组或者链表可供选择而已。
作为负责Linux Kernel和Driver开发的团队,Linux Kernel中运用了大量的数据结构类型,这其中,以RB-tree(Red Block Tree,红黑树)的使用最为频繁,比如内存管理、文件系统、网络子系统等核心子系统都大量使用了RB-tree。RB-tree算法复杂,很值得研究学习,可惜的是,在我所能看到的书和文章中,还没看到真正能讲清楚的,这也是挑选RB-tree作为这次分享主题的主要动机。
RB-tree的产生背景
我研究问题有个习惯,喜欢探索什么样的的需求导致它的出现,然后经过哪些历史演变最终变成现在的样子。揣摩它背后的思路过程,比只看那繁杂的具体实现、一堆的规则定义要有趣的多,有意义的多。比如《数据结构和算法分析》(Mark Allen Weiss著)书中给的红黑树的4条规则:
- 每个节点或者成红色,或者着成黑色。
- 根是黑色的。
- 如果一个节点是红色的,那么它的子节点必须是黑色的。
- 从一个节点到一个NULL指针的每一条路径必须包含相同数目的黑色节点。
那么问题来了:为什么满足这4条规则就是RB-tree,什么的需求推导出这些规则的,这些规则都能带来什么价值?
在RB-tree(1972年)出现之前,已经有了AVL tree(1962年)和2-3-4 tree,2-3-4 tree的出现是为了获得比AVL tree的更快、更稳定的插入删除时间(最差情况),但是2-3-4 tree本身也有个问题:需要处理的场景多,节点模型不统一,实现复杂。RB-tree的出现就是为了解决2-3-4 tree的这些问题。本文不分析AVL tree和2-3-4 tree,如有需要请自行查阅wikipedia,具体比较如下。
列1 | AVL Tree | 2-3-4 Trees | Red Black Trees |
Goods | • Balanced • O(log N) search time |
• Balanced • O(log N) search time • faster real-time bounded insertion and deletion |
• Balanced • O(log N) search time • faster real-time bounded insertion and deletion • same node structures |
Bads | • slower real-time bounded insertion and deletion | • Different node structures • large number of special cases involved in operations on the tree |
• slightly slower search time |
如下图所示,RB-tree等价于2-3-4 tree,是2-3-4 tree实现的一种优化方案。
RB-tree的着色是为了构建2-3-4 tree中的3-node、4-node节点模型,RB-tree中一条红线相连的2个节点,等价于2-3-4 tree中的3-node节点;RB-tree中两条连续的红线相连的3个节点,等价于2-3-4 tree中的4-node节点。这就是RB-tree规则(1)的由来。
RB-tree规则(4)是2-3-4 tree的特征,那么自然也适用于RB-tree。
红色作为RB-tree 3-node、4-node节点的内部连接器,红色节点有个隐含含义:它还有父节点。所以根节点不需要着红色,这就是RB-tree规则(2)的由来。如果在变换过程中,根节点着成了红色,直接改成黑色即可。
可见,规则(1)、(2)、(4)都是RB-tree对2-3-4 tree的一种自然解释。目前为止,我们唯独没有分析的规则(3),是RB-tree区别于2-3-4 tree的重要规则,也是最核心的规则。这也是下一节分析的重点。
RB-tree的实现机制
在规则(1)、(2)、(4)下,如方案A,RB-tree对3-node有2种表达形式,对4-node有5种表达形式,加上2-node的1种表达形式,总共存在8个基本结构,这就代表在对RB-tree插入、删除操作时需要考虑8种场景,这么多的场景需要处理,势必会带来实现的复杂和效率的降低,这正是2-3-4 tree最大的问题,也是RB-tree首要解决的问题。
方案B的思路是通过把4-node的5种表达形式统一规约到4.5这个结构上,把4-node的基本结构减少到一个,减少处理场景,来精简实现提升效率。这正是规则(3)的目的,把4.1到4.4这4个结构排除在外,只留下4.5这一种结构。方案B中展示了结构4.1、4.2、4.3、4.4是如何通过旋转来转变为结构4.5。
方案C在方案B的基础上更近一步,将结构3.2通过左旋转变为结构3.1,将3-node结构规约为一种结构,这个方案又叫LLRB(left-leaning RB-tree),它的基本结构与2-3-4 tree一一对应,实现简单,而且算法统一。
下图演示了RB-tree的插入过程。RB-tree插入新节点时,保证插入到叶节点处,而且着色为红色。插入和删除操作可能会破坏规则(3),这就需要对数结构进行再平衡操作,2-3-4 tree的再平衡手段是节点分裂和合并,在RB tree中则是着色翻转(color flip)和旋转(rotation)。
着色翻转保证当出现5-node的情况时,把其分解成2个3-node,并将中间节点上送给父节点。
节点旋转分为左旋和右旋。节点P左旋即把P作为P的右子节点的左子节点,节点P右旋即把P作为P的左子节点的右子节点。
下图中分别按2-node,3-node,4-node情况,展示了插入新节点后的旋转和着色翻转步骤。方案B多一组(b)处理步骤,共1+2=3次旋转操作;方案C多一组(c)处理步骤,也是3次旋转操作。可见从实现效率上来看,方案B和方案C是相同的,但是方案C的基本结构少,算法统一,实现简单,更易于理解。
对照Linux Kernel中的RB-tree中的插入函数实现,可以发现和上图中的方案B插入实现是完全一致的。
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))
{
rb_set_black(uncle);
rb_set_black(parent);
rb_set_red(gparent);
node = gparent;
continue;
}
} if (parent->rb_right == node)
{
register struct rb_node *tmp;
__rb_rotate_left(parent, root);
tmp = parent;
parent = node;
node = tmp;
} 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);
}
--EOF--
深入探索RB-tree数据结构的更多相关文章
- 红黑树(R-B Tree)
R-B Tree简介 R-B Tree,全称是Red-Black Tree,又称为“红黑树”,它一种特殊的二叉查找树.红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black). ...
- R-B Tree
1.简介 R-B Tree,全称Red-Black Tree,又称为"红黑树",为一种自平衡二叉查找树(特殊的平衡二叉树,都是在插入和删除操作时通过特定操作保持二叉树的平衡,从而获 ...
- java数据结构——红黑树(R-B Tree)
红黑树相比平衡二叉树(AVL)是一种弱平衡树,且具有以下特性: 1.每个节点非红即黑; 2.根节点是黑的; 3.每个叶节点(叶节点即树尾端NULL指针或NULL节点)都是黑的; 4.如图所示,如果一个 ...
- c语言实现tree数据结构
该代码实现了tree的结构.依赖dyArray数据结构.有first一级文件夹.second二级文件夹. dyArray的c实现參考这里点击打开链接 hashTable的c实现參考这里点击打开链接 ...
- 关于红黑树(R-B tree)原理,看这篇如何
学过数据数据结构都知道二叉树的概念,而又有多种比较常见的二叉树类型,比如完全二叉树.满二叉树.二叉搜索树.均衡二叉树.完美二叉树等:今天我们要说的红黑树就是就是一颗非严格均衡的二叉树,均衡二叉树又是在 ...
- pat 甲级 1064 ( Complete Binary Search Tree ) (数据结构)
1064 Complete Binary Search Tree (30 分) A Binary Search Tree (BST) is recursively defined as a binar ...
- 树-红黑树(R-B Tree)
红黑树概念 特殊的二叉查找树,每个节点上都有存储位表示节点的颜色是红(Red)或黑(Black).时间复杂度是O(lgn),效率高. 特性: (1)每个节点或者是黑色,或者是红色. (2)根节点是黑色 ...
- 红黑树(RB Tree)
看到一篇很好的文章 文章来源:http://www.360doc.com/content/15/0730/00/14359545_488262776.shtml 红黑树是一种高效的索引树,多于用关联数 ...
- C++-POJ3321-Apple Tree[数据结构][树状数组]
树上的单点修改+子树查询 用dfn[u]和num[u]可以把任意子树表示成一段连续区间,此时结合树状数组就好了 #include <set> #include <map> #i ...
随机推荐
- JAVA输入输出流
概述: 各种流类型(类和抽象类)都位于位于java.io包中,各种流都分别继承一下四种抽象流中的一种: 类型 字节流 字符流 输入流 InputStream Reader 输出流 OutputStre ...
- 有时打开myeclipse,部署报错解决方案
1.首先关闭MyEclipse工作空间. 2.然后删除工作空间下的 "/.metadata/.plugins/org.eclipse.core.runtime/.settings/com.g ...
- 左右手坐标系转换时R和T的具体形式分析
本文介绍了在计算机视觉的一些应用中,左手坐标系和右手坐标系之间转换时,旋转矩阵R和平移向量T的具体表达形式有哪些变化.
- [转载]js中return的用法
一.返回控制与函数结果,语法为:return 表达式; 语句结束函数执行,返回调用函数,而且把表达式的值作为函数的结果 二.返回控制,无函数结果,语法为:return; 在大多数情况下,为事件处理函 ...
- find命令
http://www.jb51.net/os/RedHat/1307.html find 目录(.代表当前目录) -type d -name "..." f -name &q ...
- Angular2 组件通信
1. 组件通信 我们知道Angular2应用程序实际上是有很多父子组价组成的组件树,因此,了解组件之间如何通信,特别是父子组件之间,对编写Angular2应用程序具有十分重要的意义,通常来讲,组件之间 ...
- web前端页面性能优化
影响用户访问的最大部分是前端的页面.网站的划分一般为二:前端和后台.我们可以理解成后台是用来实现网站的功能的,比如:实现用户注册,用户能够为文章发表评论等等.而前端呢?其实应该是属于功能的表现. 而我 ...
- React学习笔记-5-初始化阶段介绍
初始化阶段可以使用的函数:getDefaultProps:只调用一次,实例之间共享引用.只有在组件的第一个实例被初始化的时候,才会调用他,然后react会把这个函数的返回结果保存起来,从第二个实例开始 ...
- asp.net Application、 Session、Cookie、ViewState、Cache、Hidden 的区别
这些对象都是用来保存信息的,包括用户信息,传递值的信息,全局信息等等.他们之间的区别: 1.Application对象 Application用于保存所有用户的公共的数据信息,如果使用Applicat ...
- 使用VBScript实现设置系统环境变量的小程序
本人有点桌面洁癖,桌面上只放很少的东西,很多软件都用快捷键调出.最近频繁用到一个软件,我又不想放个快捷方式在桌面,也不想附到开始菜单,于是乎想将其所在目录附加到系统环境变量Path上,以后直接在运行中 ...