红黑树:个人理解与Python实现
【基本事实1】
红黑树是一种平衡的二叉查找树,无论插入还是删除操作都可以在O(lg n)内实现,而一般的二叉查找树则在极端情况下会退化为线性结构。
红黑树之所以是平衡的二叉查找树,是因为每个节点都有表示其颜色的域值:红或黑,在插入和删除操作的时候依据节点的颜色向平衡的方向调整。根本原因当然是由红黑树定义所决定的:
如果一个二叉查找树满足如下条件,那么它就称作红黑树:
1.每个节点要么是红色,要么是黑色
2.根结点是黑色
3.每个叶节点(NIL)为黑色
4.如果一个节点是红色,其儿子节点一定是黑色
5.对于每个节点,从该节点到其子孙节点的所有路径上包含相同数目的黑节点
【个人理解1】
红黑树的定义中,我认为有这些地方要注意:
1.每个节点只有一种颜色(后面删除节点的操作时引入的“额外一重黑色”则不满足此条件,所以一直要性质1调整)
2.定义中的叶节点是指NIL,它们都是黑色,而且没有子节点。根结点的父节点也是NIL。比如只有一个根节点的红黑树,它有两个叶节点。NIL是没有值的,只是一种存在。(我在Python的实现中,把NIL值都设定为None)
3.如果一个节点是红色的,它一定不是根结点,而且一定有父节点(父节点也一定是黑色的);如果它有儿子节点则一定是黑色儿子节点(每个节点如果左右儿子都是NIL,我认为它没有儿子)
4.性质5说的就是黑高度了
【基本事实2】
红黑树的旋转
红黑树在INSERT和DELETE的过程中,会使用到旋转操作。红黑树有两种旋转:左旋和右旋
左旋x:从右图到左图的过程
右旋y:从左图到右图的过程
【个人理解2】
1.并非每个节点在INSERT或DELETE的过程中都需要旋转操作
2.左旋就是右儿子y取代父节点x,x作为y的做儿子,y原来的左儿子b成为x现在的右儿子
3.右旋就是左旋的逆向过程
【基本事实3】
红黑树的插入
INSERT一个值的过程,就是在二叉查找树的INSERT操作基础上,根据红黑树性质做必要的调整,比如颜色变化,比如旋转,也就是INSERT_FIXUP的过程
插入的节点一般设定为红色然后再调整
【个人理解3】
假设要插入的节点为z,INSERT操作就是把z放到树的最底层,然后用INSERT_FIXUP区调整。INSERT_FIXUP中要注意的是:
1.如果z的父节点是NIL(即:插入节点z之前红黑树为空),那么把z涂黑,并成为根结点(完成插入)
2.如果z的父节点是黑色的,不用调整(完成插入)
3.如果z的父节点是红色的:
3-0:如果z没有叔叔节点,那么:
3-0-0:如果z为右儿子,且z的父节点p为左儿子,则左旋z的父节点,成为3-0-1;如果z为左儿子,且z的父节点p为右儿子,则右旋z的父节点p,成为3-0-1;
3-0-1:如果z为左儿子,则“父涂黑,爷涂红”,然后如果父节点是左儿子,则“爷右旋”,否则“爷左旋”(完成插入)
3-1:如果z的叔叔节点为黑色,那么:
3-1-0:如果z是右儿子,且z的父节点p为左儿子,则左旋z的父节点,成为3-1-1;如果z为左儿子,且z的父节点p为右儿子,则右旋z的父节点p,成为3-1-1;
3-1-1:如果z是左儿子,那么“父涂黑,爷涂红”,然后如果父节点是左儿子,则“爷右旋”,否则“爷左旋”(完成插入)
3-2:如果z的叔叔节点为红色,那么“父涂黑,叔涂黑,爷涂红”,并对爷爷节点g调用INSERT_FIXUP过程
以上序号和《算法导论》中的对应关系:3-2对应case1,3-1-0对应case2,3-1-1对应case3
【基本事实4】
1.红黑树删除一个节点的操作比较复杂,但也是在二叉查查找树的基础上,调用DELETE_FIXUP过程来调整
2.如果要删除的节点z,它有两个儿子,那么让z的值设定为z的后继节点y的值,然后删除y
3.如果要删除的节点z只有一个儿子x,那就让z的节点p和x成为“父子”。如果z原来是红色的,则不必调用DELETE_FIXUP过程,否则要调用
4.如果要删除的节点z没有儿子:那就直接删除z好了
5.删除节点时引入了“额外的一层黑色”,《算法导论》中文第二版P173这样说:
“在RB—DELETE中,如果被删除的节点y是黑色的,则会产生三个问题。首先,如果y原来是根结点,而y的一个红色的孩子成为了新的根,这就问犯了性质2)。其次,如果x和p[y](现在也是p[x])都是红的,就违反了性质4)。第三,删除y将导致先前包含y的任何路径上黑节点个数少1。因此,性质5)被y的一个祖先破坏了。不久者恶问题的一个办法就是把结点x视为还有额外的一重黑色。也就是说,如果将人以包含结点x的路径上黑节点个数加1,则在这种假设下,性质5)成立。当将黑节点y删除时,将其黑色“下推”至其子节点。现在为题变为结点x可能既不是红,又不是黑,从而违反了性质1)。结点x是双重黑色或红黑,这就分别给包含x的路径上黑结点个数贡献2个或1个。x的color属性仍然是RED(如果x是红黑的)或BLACK(如果x是双重黑色)。换言之,一个结点额外的黑色反映在x指向它,而不是它的color属性。”
【个人理解4】
1.当你想举反例推翻某个“结论”时,请注意,你的反例中的红黑树可能并不是红黑树;或者,它满足红黑树的定义,但无法判断是否能通过“每次插入一个节点”的方式生成。
2.因为有“额外一重黑色”的存在,《算法导论》中关于红黑树删除的case1中,调整前后的两幅图虽然“看上去是镜面对称”,但前者不满足性质5,调整后满足性质5
3.《算法导论》中关于红黑树删除的case2中,x现在为黑色,且有额外的一重黑色(就像是背负着子孙们的希望。。。),此时将x和w都去掉一个黑色,然后使p(x)增加额外的一层黑色。由于w原本为黑色,则现在令w为红色即可。此时令new_x=p(x),若new_x原本为红色,置黑即可结束;否则,对new_x调用DELETE_FIXUP过程
4.DELETE_FIXUP过程,调整的是DELETE(z)过程中z的左/右儿子(当z只有一个儿子时),或者z的后继的右儿子
#coding:utf8 #author:HaxtraZ #description:红黑树,python实现 from random import randint RED = 'red' BLACK = 'black' class RBT: def __init__(self): # self.items = [] self.root = None self.zlist = [] def LEFT_ROTATE(self, x): # x是一个RBTnode y = x.right if y is None: # 右节点为空,不旋转 return else: beta = y.left x.right = beta if beta is not None: beta.parent = x p = x.parent y.parent = p if p is None: # x原来是root self.root = y elif x == p.left: p.left = y else: p.right = y y.left = x x.parent = y def RIGHT_ROTATE(self, y): # y是一个节点 x = y.left if x is None: # 右节点为空,不旋转 return else: beta = x.right y.left = beta if beta is not None: beta.parent = y p = y.parent x.parent = p if p is None: # y原来是root self.root = x elif y == p.left: p.left = x else: p.right = x x.right = y y.parent = x def INSERT(self, val): z = RBTnode(val) y = None x = self.root while x is not None: y = x if z.val < x.val: x = x.left else: x = x.right z.PAINT(RED) z.parent = y if y is None: # 插入z之前为空的RBT self.root = z self.INSERT_FIXUP(z) return if z.val < y.val: y.left = z else: y.right = z if y.color == RED: # z的父节点y为红色,需要fixup。 # 如果z的父节点y为黑色,则不用调整 self.INSERT_FIXUP(z) else: return def INSERT_FIXUP(self, z): # case 1:z为root节点 if z.parent is None: z.PAINT(BLACK) self.root = z return # case 2:z的父节点为黑色 if z.parent.color == BLACK: # 包括了z处于第二层的情况 # 这里感觉不必要啊。。似乎z.parent为黑色则不会进入fixup阶段 return # 下面的几种情况,都是z.parent.color == RED: # 节点y为z的uncle p = z.parent g = p.parent # g为x的grandpa if g is None: return # return 这里不能return的。。。 if g.right == p: y = g.left else: y = g.right # case 3-0:z没有叔叔。即:y为NIL节点 # 注意,此时z的父节点一定是RED if y == None: if z == p.right and p == p.parent.left: # 3-0-0:z为右儿子,且p为左儿子,则把p左旋 # 转化为3-0-1或3-0-2的情况 self.LEFT_ROTATE(p) p, z = z, p g = p.parent elif z == p.left and p == p.parent.right: self.RIGHT_ROTATE(p) p, z = z, p g.PAINT(RED) p.PAINT(BLACK) if p == g.left: # 3-0-1:p为g的左儿子 self.RIGHT_ROTATE(g) else: # 3-0-2:p为g的右儿子 self.LEFT_ROTATE(g) return # case 3-1:z有黑叔 elif y.color == BLACK: if p.right == z and p.parent.left == p: # 3-1-0:z为右儿子,且p为左儿子,则左旋p # 转化为3-1-1或3-1-2 self.LEFT_ROTATE(p) p, z = z, p elif p.left == z and p.parent.right == p: self.RIGHT_ROTATE(p) p, z = z, p p = z.parent g = p.parent p.PAINT(BLACK) g.PAINT(RED) if p == g.left: # 3-1-1:p为g的左儿子,则右旋g self.RIGHT_ROTATE(g) else: # 3-1-2:p为g的右儿子,则左旋g self.LEFT_ROTATE(g) return # case 3-2:z有红叔 # 则涂黑父和叔,涂红爷,g作为新的z,递归调用 else: y.PAINT(BLACK) p.PAINT(BLACK) g.PAINT(RED) new_z = g self.INSERT_FIXUP(new_z) def DELETE(self, val): curNode = self.root while curNode is not None: if val < curNode.val: curNode = curNode.left elif val > curNode.val: curNode = curNode.right else: # 找到了值为val的元素,正式开始删除 if curNode.left is None and curNode.right is None: # case1:curNode为叶子节点:直接删除即可 if curNode == self.root: self.root = None else: p = curNode.parent if curNode == p.left: p.left = None else: p.right = None elif curNode.left is not None and curNode.right is not None: sucNode = self.SUCCESOR(curNode) curNode.val, sucNode.val = sucNode.val, curNode.val self.DELETE(sucNode.val) else: p = curNode.parent if curNode.left is None: x = curNode.right else: x = curNode.left if curNode == p.left: p.left = x else: p.right = x x.parent = p if curNode.color == BLACK: self.DELETE_FIXUP(x) curNode = None return False def DELETE_FIXUP(self, x): p = x.parent # w:x的兄弟结点 if x == p.left: w = x.right else: w = x.left # case1:x的兄弟w是红色的 if w.color == RED: p.PAINT(RED) w.PAINT(BLACK) if w == p.right: self.LEFT_ROTATE(p) else: self.RIGHT_ROTATE(p) if w.color == BLACK: # case2:x的兄弟w是黑色的,而且w的两个孩子都是黑色的 if w.left.color == BLACK and w.right.color == BLACK: w.PAINT(RED) if p.color == BLACK: return else: p.color = BLACK self.DELETE_FIXUP(p) # case3:x的兄弟w是黑色的,而且w的左儿子是红色的,右儿子是黑色的 if w.left.color == RED and w.color == BLACK: w.left.PAINT(BLACK) w.PAINT(RED) self.RIGHT_ROTATE(w) # case4:x的兄弟w是黑色的,而且w的右儿子是红 if w.right.color == RED: p.PAINT(BLACK) w.PAINT(RED) if w == p.right: self.LEFT_ROTATE(p) else: self.RIGHT_ROTATE(p) def SHOW(self): self.DISPLAY1(self.root) return self.zlist def DISPLAY1(self, node): if node is None: return self.DISPLAY1(node.left) self.zlist.append(node.val) self.DISPLAY1(node.right) def DISPLAY2(self, node): if node is None: return self.DISPLAY2(node.left) print node.val, self.DISPLAY2(node.right) def DISPLAY3(self, node): if node is None: return self.DISPLAY3(node.left) self.DISPLAY3(node.right) print node.val, class RBTnode: '''红黑树的节点类型''' def __init__(self, val): self.val = val self.left = None self.right = None self.parent = None def PAINT(self, color): self.color = color def zuoxuan(b, c): a = b.parent a.left = c c.parent = a b.parent = c c.left = b if __name__ == '__main__': rbt=RBT() b = [] for i in range(100): m = randint(0, 500) rbt.INSERT(m) b.append(m) a = rbt.SHOW() b.sort() equal = True for i in range(100): if a[i] != b[i]: equal = False break if not equal: print 'wrong' else: print 'OK!'
PS:这篇文章刚写好的时候,代码中是有错误的,而且DELETE_FIXUP()也没有给出;分析中也有小错误。不过现在已经改正了,代码中通过随机生成的数字,用系统的排序和我写的红黑树的排序对比,发现是结果是一样的,所以可以认为前面的算法分析是正确的。不过,速度上其实还是有点慢的,比如我用红黑树去排序,用在一道codeforces的题目中取代系统的sort,结果就会超时。