Python3实现红黑树[下篇]
我写的红黑树的上篇在这里:https://blog.csdn.net/qq_18138105/article/details/105190887
这是我近期看的文章 https://www.cnblogs.com/gcheeze/p/11186806.html
我看了很多关于红黑树删除的文章和博客,介绍得是相当相当的复杂,那么大猪猪就带大家好好捋一捋。
我们用一个简单的方式去处理,我们找到要删除一个元素,我们用指针d指向它,然后进行判断:
1. 若d指向的是叶子节点,则结束循环
2. 若d指向的节点有右子树,则找到它的直接后继,交换它和直接后继的数据,d指向它的直接后,重复步骤1
3. 若d指向的节点有左子树,则找到它的直接前驱,交换它和直接前驱的数据,d指向它的直接前驱,重复步骤1
这里或许同学们会有疑问,什么是直接后继,什么是直接前驱。
借用一下度娘的图吧,如图,F的直接后继是H,F的直接前驱是D。再补充远侄和近侄的概念,如图C的远侄是G,C的近侄是H,这个概念接下来会用到!
回到正题,d最终指向的节点,就是我们要删除的节点。红黑树删除的问题就大大简化了,这样处理的就是叶子节点的删除了!
假设d指向的节点是D,定义D的父节点是P,D的兄弟节点是S。开始进行判断:
- D是红色,直接删除(因为:删除它不会影响红黑树的黑色高度属性)
- D是黑色,分2种大情况:
- S是红色
- 若D是P的左孩子,则S左旋,重新开始判断
- 若D是P的右孩子,则S右旋,重新开始判断
- S是黑色,分3种小情况:
- D的远侄为红:
- 若D是P的左孩子,S左旋,远侄变黑,删除D
- 若D是P的右孩子,S右旋,远侄变黑,删除D
- D的远侄为黑,近侄为红:
- 若D是P的左孩子,近侄记为SL,SL右旋,SL左旋,S变黑,删除D
- 若D是P的右孩子,近侄记为SR,SR左旋,SR右旋,S变黑,删除D
- D的远侄和近侄都是黑:
- 若P是红色,则S变红,P变黑,删除D
- 若P是黑色,则S变红,删除D。但是!经过P的路径上的黑色节点数会少1,因此要继续向上进行平衡操作,然后d指向P,重新开始判断(只不过后续步骤不再需要删除节点了),一直向上判断,直到指向根节点为止。
- D的远侄为红:
- S是红色
代码实现:
# 红黑树节点
class RBN(object):def __init__(self, data):self.data = data # 数据域self.color = 0 # 0红 1黑self.left = Noneself.right = Noneself.parent = None# 红黑树
class RBT(object):def __init__(self):self.root = Nonedef treePrint(self):print('红黑树: start')self.midTraverse(self.root)print('红黑树: end')# 中序遍历def midTraverse(self, x):if x == None:returnself.midTraverse(x.left)colorStr = '黑' if x.color == 1 else '红'parentStr = '父=' + ('nil' if x.parent == None else str(x.parent.data))print(x.data, colorStr, parentStr)self.midTraverse(x.right)# 添加一个节点def add(self, x):# 如果没有根节点 作为根节点if self.root == None:self.root = xx.color = 1 # 根节点为黑色return# 寻找合适的插入位置p = self.rootwhile p != None:if x.data < p.data:if p.left == None:p.left = xx.parent = pself.addFix(x)breakp = p.leftelif x.data > p.data:if p.right == None:p.right = xx.parent = pself.addFix(x)breakp = p.rightelse:return# 调整红黑树def addFix(self, x):while True:if x == self.root: # 如果处理到根节点了 则着色为黑x.color = 1returnp = x.parent # 爸爸if p.color == 1 or x.color == 1: # 自己和爸爸只要有一个是黑的 就构不成双红 则返回return# 接下来分析红爸爸情况g = p.parent # 爷爷 红爸爸肯定有爸爸,因为红色绝不是根节点u = g.left if p == g.right else g.right # 叔叔 叔叔可能为空节点if u != None and u.color == 0: # 红叔叔 则着色 然后从爷爷开始向上继续调整u.color = p.color = 1 # 叔叔和爸爸都变黑色g.color = 0 # 爷爷变红色x = g # x指向爷爷,然后继续循环continue# 接下来分析黑叔叔得情况 有四种情况 左左,左右,右左,右右if p == g.left and x == p.left: # 左左# 以爸爸为支点右旋爷爷self.rotateRight(p)elif p == g.left and x == p.right: # 左右# 以x为支点左旋爸爸self.rotateLeft(x)# 以x为支点右旋爷爷(上面的旋转把爷爷变成了新爸爸)self.rotateRight(x)elif p == g.right and x == p.right: # 右右 其实就是 左左的镜像# 以爸爸为支点左旋爷爷self.rotateLeft(p)elif p == g.right and x == p.left: # 右左 其实就是 左右的镜像# 以x为支点右旋爸爸self.rotateRight(x)# 以x为支点左旋爷爷(上面的旋转把爷爷变成了新爸爸)self.rotateLeft(x)## 关于红黑树的旋转,一直是个难搞的点# 这里我提供一个口诀:# 右旋: 支点占旋点原位,支点的右给旋点作为左,旋点作为支点的右# 左旋: 支点占旋点原位,支点的左给旋点作为右,旋点作为支点的左## 右旋 p支点def rotateRight(self, p):g = p.parent # 支点的父节点就是旋点# 右旋gif g == self.root: # 若g是根节点 则p升为根节点self.root = pp.parent = Noneelse: # 若g不是根节点 那么必然存在g.parent p占据g的位置gp = g.parentp.parent = gpif g == gp.left:gp.left = pelse:gp.right = pg.left = p.rightif p.right != None:p.right.parent = gp.right = gg.parent = p# g和p颜色交换p.color, g.color = g.color, p.color# 左旋 p 支点def rotateLeft(self, p):g = p.parent # 支点的父节点就是旋点# 左旋gif g == self.root: # 若g是根节点 则p升为根节点self.root = pp.parent = Noneelse: # 若g不是根节点 那么必然存在g.parent p占据g的位置gp = g.parentp.parent = gpif g == gp.left:gp.left = pelse:gp.right = pg.right = p.leftif p.left != None:p.left.parent = gp.left = gg.parent = p# g和p颜色交换p.color, g.color = g.color, p.color# 删除一个节点def delete(self, x):# 查找要删除的节点d = self.rootwhile d != None:if x.data < d.data:d = d.leftelif x.data > d.data:d = d.rightelse:breakif d == None:print('要删除的', x.data, '已经不存在了')return# 如果要删除的节点不是叶子节点,我们需要做d的指向转移,直到d指向的是叶子节点就结束循环while d.left != None or d.right != None:# 如果存在右子树 则找直接后继(也就是右子树的最左后代),交换数据, d指向这个后继,重复循环if d.right != None:nextNode = self.getMostLeft(d.right)d.data, nextNode.data = nextNode.data, d.datad = nextNodecontinue# 如果存在左子树 则找到直接前驱(也就是左子树的最右后代),交换数据,d指向这个前驱,重复循环elif d.left != None:preNode = self.getMostRight(d.left)d.data, preNode.data = preNode.data, d.datad = preNodecontinue# print('要删除的是', d.data);# 接下来处理要删除的节点是叶子节点的情况吧 #needDelete = Truewhile True:# 如果d是根节点,直接删除if self.root == d:if needDelete:self.deleteDirectly(d)needDelete = Falsereturn# 如果d的是红色,那么直接删除if self.isRed(d):if needDelete:self.deleteDirectly(d)needDelete = Falsereturn# 如果d是黑色,设s指向兄弟节点(根据红黑树左右子树黑色高度相等的性质,s指向的节点必定存在),分好几种情况p = d.parents = p.right if d == p.left else p.left# print(d.data, d.color, p.data)# 1 如果s是红色 if self.isRed(s):if d == p.left:self.rotateLeft(s)else:self.rotateRight(s)continue # 旋转后 再重新判断sl = s.leftsr = s.right# 2 如果s是黑色,又分好几种情况if d == p.left: # d是p的左孩子# 2.1 如果d的远侄为红(有远侄的话必定为红,否则不满足红黑树的黑色高度性质)if self.isRed(sr):self.rotateLeft(s)sr.color = 1if needDelete:self.deleteDirectly(d)needDelete = Falsereturn# 2.2 如果d的远侄为黑,近侄为红(有近侄的话必定为红,否则不满足红黑树的黑色高度性质)if not self.isRed(sr) and self.isRed(sl):self.rotateRight(sl)self.rotateLeft(sl)s.color = 1if needDelete:self.deleteDirectly(d)needDelete = Falsereturn# 2.3 如果d的远侄和近侄都是黑,也就是d是叶子节点,分2种情况# 2.3.1 如果p是红色 s变红 p变黑 删除dif p.color == 0:s.color = 0p.color = 1if needDelete:self.deleteDirectly(d)needDelete = Falsereturn# 2.3.1 如果p是黑色 s变红 删除d 然后d指向p重复判断步骤s.color = 0if needDelete:self.deleteDirectly(d)needDelete = Falsed = pcontinueelse: # d是p的右孩子# 2.1 如果d的远侄为红(有远侄的话必定为红,否则不满足红黑树的黑色高度性质)if self.isRed(sl):self.rotateRight(s)sl.color = 1if needDelete:self.deleteDirectly(d)needDelete = Falsereturn# 2.2 如果d的远侄为黑,近侄为红(有近侄的话必定为红,否则不满足红黑树的黑色高度性质)if not self.isRed(sl) and self.isRed(sr):self.rotateLeft(sr)self.rotateRight(sr)s.color = 1if needDelete:self.deleteDirectly(d)needDelete = Falsereturn# 2.3 如果d的远侄和近侄都是黑,也就是d是叶子节点,分2种情况# 2.3.1 如果p是红色 s变红 p变黑 删除dif p.color == 0:s.color = 0p.color = 1if needDelete:self.deleteDirectly(d)needDelete = Falsereturn# 2.3.1 如果p是黑色 s变红 删除d 然后d指向p重复判断步骤s.color = 0if needDelete:self.deleteDirectly(d)needDelete = Falsed = pcontinue# 是否是红色节点def isRed(self, x):return x != None and x.color == 0# 找到x的最左的后代def getMostLeft(self, x):while x.left != None:x = x.leftreturn x# 找到y的最右后代def getMostRight(self, x):while x.right != None:x = x.rightreturn x# 直接删除x节点def deleteDirectly(self, x):if x == self.root:self.root = Noneelse:p = x.parentif x == p.left:p.left = Noneelse:p.right = Noneif __name__ == '__main__':rbt = RBT()# datas = [10, 20, 30, 15]# datas = [11, 2, 14, 1, 7, 15, 5, 8, 4]datas = [12, 1, 9, 2, 0, 11, 7, 19, 4, 15,18, 5, 14, 13, 10, 16, 6, 3, 8, 17]for x in datas:rbt.add(RBN(x))print('=====================================================')rbt.treePrint()print('=====================================================')rbt.delete(RBN(12))rbt.delete(RBN(1))rbt.delete(RBN(9))rbt.delete(RBN(2))rbt.delete(RBN(0))rbt.delete(RBN(11))rbt.delete(RBN(7))rbt.delete(RBN(19))rbt.delete(RBN(4))rbt.delete(RBN(15))rbt.delete(RBN(18))# rbt.delete(RBN(5))# rbt.delete(RBN(14))# rbt.delete(RBN(13))# rbt.delete(RBN(10))# rbt.delete(RBN(16))# rbt.delete(RBN(6))# rbt.delete(RBN(3))# rbt.delete(RBN(8))# rbt.delete(RBN(17))rbt.treePrint()
结论正确。
另外,再强调这两篇文章所提到的“左旋”和“右旋”:
右旋: 支点占旋点原位,支点的右给旋点作为左,旋点作为支点的右,交换支点和旋点的颜色
左旋: 支点占旋点原位,支点的左给旋点作为右,旋点作为支点的左,交换支点和旋点的颜色