红黑树系列一:旋转
红黑树一棵在每个结点上增加了一个存储位来表示结点颜色(红色RED或黑色BLACK)的二叉搜索树。
二叉搜索树简单的说就是:对树中任何结点x,其左子树中的关键字最大不超过x.key,即对左子树中任一结点y,有y.key<x.key;其右子树中的关键字最小不低于x.key,即对右子树中任一结点y,有y.key>x.key。
红黑树中每个结点包含5个属性:color、key、left、right和p。如果一个结点没有子结点或父结点,则该结点相应属性值为NIL。如图1所示:
图1 红黑树的叶结点
这些NIL被视为指向二叉搜索树的叶结点(外部结点)的指针,而把带关键字的结点视为树的内部结点。
为了便于处理红黑树代码中的边界条件,使用一个哨兵T.nil来代表所有的NIL:所有的叶结点和根结点的父结点。如图2所示:
图2 红黑树的T.nil属性
因为在对红黑树的实际操作中重点关注的是红黑树存储了关键字的值的内部结点,所以在后续的图示中都将省略叶结点部分,如图3所示:
图3 红黑树
从某个结点x出发(不含该结点)到达一个叶结点的任意一条简单路径上的黑色结点个数称为该结点的黑高(black-height),记为bh(x)。红黑树的黑高为其根结点的黑高。
一棵红黑树是满足下面红黑性质的二叉搜索树:
(1)每个结点或是红色的,或是黑色的;
(2)根结点是黑色的;
(3)每个叶结点(NIL)是黑色的;
(4)如果一个结点是红色的,则它的两个子节点都是黑色的;
(5)对每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。
旋转:红黑树的旋转是一种能保持二叉搜索树性质的搜索树局部操作。有左旋和右旋两种旋转,通过改变树中某些结点的颜色以及指针结构来保持对红黑树进行插入和删除操作后的红黑性质。
左旋:对某个结点x做左旋操作时,假设其右孩子为y而不是T.nil:以x到y的链为“支轴”进行。使y成为该子树新的根结点,x成为y的左孩子,y的左孩子成为x的右孩子。如下左旋伪代码所示:
代码一:左旋转
LEFT-ROTATE(T,x)
y = x.right
x.right = y.left
if y.left != T.nil
y.left.p = x
y.p = x.p
if x.p == T.nil
T.root = y
else if x == x.p.left
x.p.left = y
else x.p.right = y
y.left = x
x.p = y
伪代码解析:
(1)第2行:定位结点x,y的位置关系,即y为x的右孩子;
(2)第3~5行:修改指针使结点y的左子树成为结点x的右子树;
(3)第6~11行:修改指针使结点y成为结点x的父节点的新的子结点,即替换结点x的位置;
(4)第12~13行:修改指针使结点x成为结点y的左孩子结点。
图4给出了一个左旋操作修改二叉搜索树的例子。由于此处只讨论旋转之后的结点位置变化,暂不讨论旋转之后结点颜色的修改处理操作,故此处的结点颜色均已灰色代替。
图4 左旋转
右旋:对某个结点x做右旋操作时,假设其左孩子为y而不是T.nil:以x到y的链为“支轴”进行。使y成为该子树新的根结点,x成为y的右孩子,y的右孩子成为x的左孩子。如下右旋伪代码所示:
代码二:右旋转
RIGHT-ROTATE(T,x)
y = x.left
x.left = y.right
if y.right != T.nil
y.right.p = x
y.p = x.p
if x.p == T.nil
T.root = y
else if x == x.p.left
x.p.left = y
else x.p.right = y
y.right = x
x.p = y
右旋与左旋操作使对称的,故此处不做具体解析。
图5给出了一个右旋操作修改二叉搜索树的例子:
图5 右旋转