Python3实现红黑树[下篇]
生活随笔
收集整理的這篇文章主要介紹了
Python3实现红黑树[下篇]
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
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()結論正確。
另外,再強調這兩篇文章所提到的“左旋”和“右旋”:
總結
以上是生活随笔為你收集整理的Python3实现红黑树[下篇]的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 酷狗概念版怎么填写邀请码(酷狗音乐软件官
- 下一篇: Python3实现快速排序 通俗易懂