深入探索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 ...
随机推荐
- Linux学习之六--unZip/Zip的安装及使用
Linux系统没有自带的压缩解压工具:需要我们自己安装:当用到zip或者unzip如果没有安装就会出现unzip: Command Not Found 或 zip: Command Not Found ...
- [BZOJ1251]序列终结者
[BZOJ1251]序列终结者 试题描述 网上有许多题,就是给定一个序列,要你支持几种操作:A.B.C.D.一看另一道题,又是一个序列 要支持几种操作:D.C.B.A.尤其是我们这里的某人,出模拟试题 ...
- Ajax深入解析
AJAX:Asynchronous JavaScript And Xml(异步的JS和XML) 同步:客户端发起请求>服务端的处理和响应>客户端重新载入页面(循环) 异步:客户端实时请求& ...
- Ajax发送POST请求SpringMVC页面跳转失败
问题描述:因为使用的是SpringMVC框架,所以想使用ModelAndView进行页面跳转.思路是发送POST请求,然后controller层中直接返回相应ModelAndView,但是这种方法不可 ...
- C语言 独木舟问题
n个人,已知每个人体重,独木舟承重固定,每只独木舟最多坐两个人,可以坐一个人或者两个人.显然要求总重量不超过独木舟承重,假设每个人体重也不超过独木舟承重,问最少需要几只独木舟? 分析:贪心算法,抽象化 ...
- 前后端分离中,Gulp实现头尾等公共页面的复用
前言 通常我们所做的一些页面,我们可以从设计图里面看出有一些地方是相同的.例如:头部,底部,侧边栏等等.如果前后端分离时,制作静态页面的同学,对于这些重复的部分只能够通过复制粘贴到新的页面来,如果页面 ...
- -bash: fork: retry: Resource temporarily unavailable
登陆不了服务器The server refused to start a shell. 登陆服务器后执行ls命令报错: 1 2 $ls -bash: fork: retry: Resource t ...
- 我的CPG插件 (什么是CPG,就是跟号称全球唯一C++编写的魔镜是一样的格式的)
- js中子页面父页面方法 变量相互调用
(1)子页面调用父页面的方法或者变量: window.parent.方法()或者变量名window.parent相当于定位到父页面 之后的操作和在父页面中写代码一样写 window.parent.a ...
- windows下的mysql忘记root密码的解决方法
1.首先,需要关闭MySQL Server服务.在"运行"窗口,输入"services.msc",进入"服务"窗口. 2. 在服务窗口,可以 ...