算法打基础——红黑树Ⅰ
前几天都在组织办活动,完全没有时间学术,深深地自责!再加上红黑树真难啃,现在的删除部分还是没能看懂~~~~(>_<)~~~~。所以,这一节主要介绍的是红黑树里面比较简单的基本性质,插入等操作,删除等看明白了再补上吧。
从上一节说到二叉查找树,我们可以知道,树的高度对于各种操作速度的关键。红黑树就是这样一种天生平衡的搜索树,可以使得树的高度是O(lgn).
实际上,这样的平衡树还有很多,如果以后我看得比较明白了,一定写到这个系列里面,主要有: AVL tree; 2-3 tree; 2-3-4 tree; B-tree; Red-black tree; 下面就具体的介绍红黑树及其性质
主要知识点是: 1. 红黑树的性质 2 红黑树插入操作
1. 红黑树的性质
红黑树主要就是在每个节点上增加了个颜色;它的性质主要有五点:
1.每个节点要么红要么黑
2.根节点一定要是黑色的
3.叶节点(NULL节点)一定要是黑色的
4. 如果一个节点是红色的,则它的父节点就一定是黑色的
(跟书稍有差别但等价,书:如果节点红,则其两个儿子都为黑)
5. 对每个节点,从它到其子孙节点的所有路径上包含同样数目的黑节点(黑高度相等)
红黑树的一个例子
关于为什么红黑树是平衡的,给出下面这个定理:
有n个键值的红黑树的高度 h≤2lg(n+1).
Proof: 这里给出一个直观的证明,实际上书本上是用数学归纳法证明的;
对于一颗红黑树,我们将其红色节点收缩到黑节点中
------>
显然变化后的树的高度是红黑树的黑高度bh,而根据性质,bh≥h/2; 另一方面左边的红黑树是一个满二叉树,满二叉树有性质: 其叶节点的数目=内部节点数目+1(n+1) .
我们可以看到收缩后的黑树,其每个节点可能会有2,3,4个孩子,即最少有2个孩子,所以我们有不等式:
n+1≥2^(bh);
bh≤lg(n+1) ----> h≤2lg(n+1)
下面再给出一个红黑树的基本操作——旋转,它是插入和删除操作的基础。
旋转分为左旋和右旋,执行时间因为只是一些指针的变换,所以是O(1). 下面给出左旋的伪代码,右旋是对称的。
LEFT-ROTATE(T, x)
1 y ← right[x] ⊳ Set y. 左旋是和右子树对调
2 right[x] ← left[y] ⊳ Turn y’s left subtree into x’s right subtree.
3 if left[y] = nil[T ] ⊳ 处理上面操作对应的父节点
4 then p[left[y]] ← x
5 p[y] ← p[x] ⊳ Link x’s parent to y.
6 if p[x] = nil[T ] ⊳ 根边界情况
7 then root[T ] ← y
8 else if x = left[p[x]]
9 then left[p[x]] ← y
10 else right[p[x]] ← y
11 left[y] ← x ⊳ Put x on y’s left.
12 p[x] ← y
注意这类连接变换操作都是分为两部分:父的孩子节点赋值&子的父节点赋值
2 红黑树插入操作
红黑树的插入操作和二叉树的插入操作开始时基本一样的,直接插入一个元素,并将其染成红色。这样,只有可能违反上面的性质4; 只需再增加一个修复的过程,维持性质4即可。下面给出一个例子,而实际过程也将是如此
插入节点15,染为红色,发生冲突
重新涂色,原则上是将冲突逐步向上移动,10,18冲突
18进行右旋转,将冲突变到一条线上;
7左旋转并染色,树归于平衡,没有冲突
实际上上面给的这个例子的后三幅图就是对应于插入操作的三种情况,下面将给出红黑色插入操作的伪代码。插入操作的伪代码将分为2部分,第一部分跟BST插入基本一致,第二部分是fix过程。
RB-INSERT(T, z)
1 y ← nil[T ]
2 x ← root[T ]
3 while x = nil[T ]
4 do y ← x
5 if key[z] < key[x]
6 then x ← left[x]
7 else x ← right[x]
8 p[z] ← y
9 if y = nil[T ]
10 then root[T ] ← z
11 else if key[z] < key[y]
12 then left[y] ← z
13 else right[y]← z
14 left[z]← nil[T ]
15 right[z]← nil[T ]
16 color[z]← RED
17 RB-INSERT-FIXUP(T, z)
RB-INSERT-FIXUP(T, z)
1 while color[p[z]] = RED
2 do if p[z] = left[p[p[z]]]
3 then y ← right[p[p[z]]]
4 if color[y] = RED
5 then color[p[z]] ← BLACK ✄ Case 1
6 color[y] ← BLACK ✄ Case 1
7 color[p[p[z]]] ← RED ✄ Case 1
8 z ← p[p[z]] ✄ Case 1
9 else if z = right[p[z]]
10 then z ← p[z] ✄ Case 2
11 LEFT-ROTATE(T, z) ✄ Case 2
12 color[p[z]] ← BLACK ✄ Case 3
13 color[p[p[z]]] ← RED ✄ Case 3
14 RIGHT-ROTATE(T, p[p[z]]) ✄ Case 3
15 else (same as then clause with “right” and “left” exchanged)
16 color[root[T ]]← BLACK
然后我们来分析一下插入过程的时间复杂度,首先我们应该注意while循环只有当color[y]=RED的时候才能循环下去,因为当进入rotate过程,两步之后就终止了。而染色每次会上升2层,故时间复杂度是O(lgn)