之前有补充过二叉搜索树的相关内容,传送门。我们知道一棵高度为h的二叉搜索树,它可以支持任何一种基本动态集合操作,其时间复杂度均为O(h)。因此,如果搜索树的高度较高时,这些集合操作可能并不比链表上执行得快。红黑树(red-black tree)是许多“平衡”搜索树的一种,可以保证最坏情况下基本动态集合操作的时间复杂度为O(lgn)。
红黑树是一棵二叉搜索树,它在每个结点上增加了一个存储位来表示结点的颜色,可以是Red或者Black。通过对任何一条从根到叶子的简单路径上各个结点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出2倍,因而是近似平衡的。
红黑树种的每个结点包含5个属性:color,key,left,right和p。如果一个结点没有子结点或父结点,则该结点相应指针属性的值为Null,我们把这些Null视为指向二叉搜索树的叶结点(外部结点)的指针,而把带关键字的结点视为树的内部结点。一棵红黑树是满足下面红黑性质的二叉搜索树:
- 每个结点要么是红色的,要么是黑色的。
- 根结点是黑色的。
- 每个叶结点是黑色的。
- 如果一个结点是红色的,则它的两个子结点都是黑色的。
- 对每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。
从某个结点x出发(不含该结点)到达一个叶结点的任意一条简单路径上黑色结点的个数称为该结点的黑高。记为bh(x),根据上面的性质5,黑高的概念定义是明确的,因为从该结点出发的所有下降到其叶结点的简单路径的黑结点个数都相同。于是定义红黑树的黑高为其根结点的黑高。
一棵有n个内部结点的红黑树的高度至少为2lg(n+1)
旋转
搜索树操作TREE-INSERT和TREE-DELETE在含n个关键字的红黑树上,运行花费时间为O(lgn)。由于这个两操作对树做了修改,结果可能违反红黑树的性质。为了维护这种性质,必须要改变树种某些结点的颜色以及指针结构。其中指针结构的修改是通过旋转来完成的,这是一种能保持二叉搜索树性质的搜索树局部操作。
如上图所示给出的两种旋转方式所示:当在某个结点x上做左旋时,假设它的右孩子为y而不是null;x可以是右孩子不是null的树T内的任意结点。左旋以x到y的链为“轴”进行。它使y成为该子树新的根结点,x成为y的左孩子,y的左孩子成为x的右孩子。
同样是以左旋操作举例,用笔者的理解来解释是: 所谓左旋,按我们常规理解来说其实是一个逆时针方向转动的过程。即结点逆时针调整位置的过程。因此,原来子树的根结点x应当成为新的根结点的左孩子,原来的子树的根结点x的右孩子由于逆时针转动,成为了新的子树的根结点。但这里有一个需要额外考虑的地方,即x本身是有右孩子的,并且y本身是有左孩子的(可能为null)。经过上面的选择过程,x原本的右孩子y成为了根几点,不再是x的右孩子。而y本身的左孩子β则没有了父结点。其实这时应该把β的父节点指向x,把x的右孩子指向β。
明白了思路接下来我们来看一下伪代码实现
1 LEFT-ROTATE(T,x) 2 { 3 y = x.right; 4 x.right = y.left; 5 6 if(y.left != null) 7 { 8 y.left.p = x; 9 } 10 11 if(x.p == null) 12 { 13 T.root = y; 14 } 15 else if(x == x.p.left) 16 { 17 x.p.left = y; 18 } 19 else 20 { 21 x.p.right = y; 22 } 23 24 y.left = x; 25 x.p = y; 26 }
左旋和右旋操作都在O(1)的时间内完成。在旋转操作中只有指针改变,其他所有属性保持不变。
待续。。。