From 52c93f95ce30f8a2e6004ed32e4db581ce8127f7 Mon Sep 17 00:00:00 2001 From: mbinary Date: Sat, 14 Jul 2018 17:55:01 +0800 Subject: [PATCH] :v:Build a red black tree successfully --- dataStructure/redBlackTree.py | 298 +++++++------- notes/red-black-tree.md | 721 ++++++++++++++++++++++++++++++++++ 2 files changed, 883 insertions(+), 136 deletions(-) create mode 100644 notes/red-black-tree.md diff --git a/dataStructure/redBlackTree.py b/dataStructure/redBlackTree.py index f669309..7caff91 100644 --- a/dataStructure/redBlackTree.py +++ b/dataStructure/redBlackTree.py @@ -22,7 +22,7 @@ class node: def __lt__(self,nd): return self.val < nd.val def __eq__(self,nd): - return nd is not None and self.val==nd.val + return nd is not None and self.val == nd.val def setChild(self,nd,isLeft = True): if isLeft: self.left = nd else: self.right = nd @@ -37,29 +37,11 @@ class node: def __repr__(self): return f'node({self.val},isBlack={self.isBlack})' class redBlackTree: - def __init__(self): + def __init__(self,unique=False): + '''if unique is True, all node'vals are unique, else there may be equal vals''' self.root = None - def getParent(self,val): - if isinstance(val,node):val = val.val - if self.root.val == val:return None - nd = self.root - while nd: - if nd.val>val and nd.left is not None: - if nd.left.val == val: return nd - else: nd = nd.left - elif nd.valval: - nd = nd.left - else: - nd = nd.right + self.unique = unique + @staticmethod def checkBlack(nd): return nd is None or nd.isBlack @@ -69,39 +51,85 @@ class redBlackTree: if isBlack is None or isBlack: nd.isBlack = True else:nd.isBlack = False - def insert(self,val): - if isinstance(val,node):val = val.val - def _insert(root,nd): - '''return parent''' - while root: - if root == nd:return None - elif root>nd: - if root.left : - root=root.left - else: - root.left = nd - return root - else: - if root.right: - root = root.right - else: - root.right = nd - return root - # insert part - nd = node(val) + def sort(self,reverse = False): + ''' return a generator of sorted data''' + def inOrder(root): + if root is None:return + if reverse: + yield from inOrder(root.right) + else: + yield from inOrder(root.left) + yield root + if reverse: + yield from inOrder(root.left) + else: + yield from inOrder(root.right) + yield from inOrder(self.root) + def getParent(self,chd): + '''note that use is to find real node when different nodes have euqiv val''' + if self.root is chd:return None + nd = self.root + while nd: + if nd>chd and nd.left is not None: + if nd.left is chd: return nd + else: nd = nd.left + elif ndval: + nd = nd.left + else: + nd = nd.right + def getSuccessor(self,nd): + if nd: + if nd.right: + nd = nd.right + while nd.left: + nd = nd.left + return nd + else:return self.getParent(nd) + def transferParent(self,origin,new): + if origin is self.root: + self.root = new + else: + prt = self.getParent(origin) + prt.setChild(new, prt.left is origin) + + def insert(self,nd): + if not isinstance(nd,node): + nd = node(nd) + elif nd.isBlack: nd.isBlack = False + if self.root is None: self.root = nd - self.setBlack(self.root,True) - return - parent = _insert(self.root,nd) - if parent is None: return - if not parent.isBlack: self.fixUpInsert(parent,nd) - + self.root.isBlack = True + else: + parent = self.root + while parent: + if parent == nd : return None + if parent>nd: + if parent.left : + parent = parent.left + else: + parent.left = nd + break + else: + if parent.right: + parent = parent.right + else: + parent.right = nd + break + self.fixUpInsert(parent,nd) def fixUpInsert(self,parent,nd): ''' adjust color and level, there are two red nodes: the new one and its parent''' while not self.checkBlack(parent): grand = self.getParent(parent) - isLeftPrt = grand.left == parent + isLeftPrt = grand.left is parent uncle = grand.getChild(not isLeftPrt) if not self.checkBlack(uncle): # case 1: new node's uncle is red @@ -112,7 +140,7 @@ class redBlackTree: parent = self.getParent(nd) else: # case 2: new node's uncle is black(including nil leaf) - isLeftNode = parent.left==nd + isLeftNode = parent.left is nd if isLeftNode ^ isLeftPrt: # case 2.1 the new node is inserted in left-right or right-left form # grand grand @@ -130,105 +158,92 @@ class redBlackTree: parent.setChild(grand,not isLeftPrt) self.setBlack(grand, False) self.setBlack(parent, True) + self.transferParent(grand,parent) self.setBlack(self.root,True) - def sort(self,reverse = False): - ''' return a generator of sorted data''' - def inOrder(root): - if root is None:return - if reverse: - yield from inOrder(root.right) - else: - yield from inOrder(root.left) - yield root - if reverse: - yield from inOrder(root.left) - else: - yield from inOrder(root.right) - yield from inOrder(self.root) - def getSuccessor(self,val): - if isinstance(val,node):val = val.val - def _inOrder(root): - if root is None:return - if root.val>= val:yield from _inOrder(root.left) - yield root - yield from _inOrder(root.right) - gen = _inOrder(self.root) - for i in gen: - if i.val==val: - try: return gen.__next__() - except:return None - def delete(self,val): - # delete node in a binary search tree - if isinstance(val,node):val = val.val - nd = self.find(val) + def copyNode(self,src,des): + '''when deleting a node which has two kids, + copy its succesor's data to his position + data exclude left, right , isBlack + ''' + des.val = src.val + def delete(self,nd): + '''delete node in a binary search tree''' + if not isinstance(nd,node): + nd = self.find(nd) if nd is None: return y = None if nd.left and nd.right: - y= self.getSuccessor(val) + y= self.getSuccessor(nd) else: y = nd - py = self.getParent(y.val) + py = self.getParent(y) x = y.left if y.left else y.right if py is None: self.root = x - elif y==py.left: + elif y is py.left: py.left = x else: py.right = x if y != nd: - nd.val = y.val + self.copyNode(y,nd) - # adjust colors and rotate if self.checkBlack(y): self.fixUpDel(py,x) + def fixUpDel(self,prt,chd): - if self.root == chd or not self.checkBlack(chd): - self.setBlack(chd, True) - return - isLeft = prt.left == chd - brother = prt.getChild(not isLeft) - if self.checkBlack(brother): - # case 1: brother is black - lb = self.checkBlack(brother.left) - rb = self.checkBlack(brother.right) - if lb and rb: - # case 1.1: brother is black and two kids are black + ''' adjust colors and rotate ''' + while self.root != chd and self.checkBlack(chd): + isLeft = prt.left is chd + brother = prt.getChild(not isLeft) + # brother is black + lb = self.checkBlack(brother.getChild(isLeft)) + rb = self.checkBlack(brother.getChild(not isLeft)) + if not self.checkBlack(brother): + # case 1: brother is red. converted to case 2,3,4 + # prt (isLeft) rotate + prt.setChild(brother.getChild(isLeft), not isLeft) + brother.setChild(prt, isLeft) + + self.setBlack(prt,False) + self.setBlack(brother,True) + + self.transferParent(prt,brother) + elif lb and rb: + # case 2: brother is black and two kids are black. + # conveted to the begin case self.setBlack(brother,False) chd = prt - elif lb or rb: - # case 1.2: brother is black and two kids's colors differ - if self.checkBlack(brother.getChild(not isLeft)): + prt = self.getParent(chd) + else: + if rb: + # case 3: brother is black and left kid is red and right child is black # uncle's son is nephew, and niece for uncle's daughter - nephew = brother.getChild(isLeft), - print(nephew) + nephew = brother.getChild(isLeft) self.setBlack(nephew,True) self.setBlack(brother,False) - # brother right rotate + # brother (not isLeft) rotate prt.setChild(nephew,not isLeft) + brother.setChild(nephew.getChild(not isLeft),isLeft) nephew.setChild(brother, not isLeft) - brother = prt.right + brother = nephew - # case 1.3: brother is black and two kids are red + # case 4: brother is black and right child is red brother.isBlack = prt.isBlack self.setBlack(prt,True) - self.setBlack(brother.right,True) + self.setBlack(brother.getChild(not isLeft),True) # prt left rotate prt.setChild(brother.getChild(isLeft),not isLeft) brother.setChild(prt,isLeft) + + self.transferParent(prt,brother) + chd = self.root - - - else: - # case 2: brother is red - prt.setChild(brother.getChild(isLeft), not isLeft) - brother.setChild(prt, isLeft) - self.setBlack(prt,False) - self.setBlack(brother,True) self.setBlack(chd,True) + def display(self): def getHeight(nd): if nd is None:return 0 @@ -238,25 +253,39 @@ class redBlackTree: lst = deque([root]) level = [] h = getHeight(root) - lv = 0 - ct = 0 - while lv<=h: + ct = lv = 0 + while 1: ct+=1 nd = lst.popleft() if ct >= 2**lv: lv+=1 + if lv>h:break level.append([]) level[-1].append(str(nd)) if nd is not None: - lst.append(nd.left) - lst.append(nd.right) + lst += [nd.left,nd.right] else: - lst.append(None) - lst.append(None) + lst +=[None,None] return level + def addBlank(lines): + width = 5 + sep = ' '*width + n = len(lines) + for i,oneline in enumerate(lines): + k = 2**(n-i) -1 + new = [sep*((k-1)//2)] + for s in oneline: + new.append(s.ljust(width)) + new.append(sep*k) + lines[i] = new + return lines + lines = levelVisit(self.root) - print('-'*5+ 'level visit' + '-'*5) - return '\n'.join([' '.join(line) for line in lines]) + lines = addBlank(lines) + li = [''.join(line) for line in lines] + li.insert(0,'red-black-tree'.rjust(48,'-') + '-'*33) + li.append('end'.rjust(42,'-')+'-'*39+'\n') + return '\n'.join(li) def __str__(self): return self.display() @@ -270,44 +299,41 @@ def genNum(n =10): if d not in nums: nums.append(d) break - #nums = [3,4,2,0,1,6] return nums def buildTree(n=10,nums=None,visitor=None): - if nums is None: nums = genNum(n) + if nums is None or nums ==[]: nums = genNum(n) rbtree = redBlackTree() print(f'build a red-black tree using {nums}') for i in nums: + rbtree.insert(i) if visitor: visitor(rbtree) - rbtree.insert(i) - return rbtree + return rbtree,nums def testInsert(): def visitor(t): print(t) - nums = [66, 14, 7, 2, 52, 96, 63, 51, 16, 53] - rbtree = buildTree(nums = nums,visitor = visitor) + rbtree,nums = buildTree(visitor = visitor) print('-'*5+ 'in-order visit' + '-'*5) for i,j in enumerate(rbtree.sort()): print(f'{i+1}: {j}') def testSuc(): - rbtree = buildTree() + rbtree,nums = buildTree() for i in rbtree.sort(): print(f'{i}\'s suc is {rbtree.getSuccessor(i)}') def testDelete(): - #nums = [56, 89, 31, 29, 24, 8, 62, 96, 20, 75] #tuple - nums = [66, 14, 7, 2, 52, 96, 63, 51, 16, 53] - rbtree = buildTree(nums = nums) + #nums = [2,3,3,2,6,7,2,1] + nums = None + rbtree,nums = buildTree(nums = nums) print(rbtree) - shuffle(nums) for i in nums: print(f'deleting {i}') rbtree.delete(i) print(rbtree) if __name__=='__main__': - testInsert() - #testDelete() #testSuc() + #testInsert() + testDelete() diff --git a/notes/red-black-tree.md b/notes/red-black-tree.md new file mode 100644 index 0000000..f9552f1 --- /dev/null +++ b/notes/red-black-tree.md @@ -0,0 +1,721 @@ +--- +title: 『数据结构』红黑树(red-black tree) +date: 2018-07-12 19:58 +categories: 数据结构与算法 +tags: [数据结构,红黑树] +keywords: +mathjax: true +description: + + +- [1. 定义与性质](#1-定义与性质) + - [1.1. 数据域](#11-数据域) + - [1.2. 红黑性质](#12-红黑性质) + - [1.3. 黑高度](#13-黑高度) +- [2. 旋转](#2-旋转) +- [3. 插入](#3-插入) + - [3.1. 二叉查找树的插入](#31-二叉查找树的插入) + - [3.2. 颜色调整与旋转](#32-颜色调整与旋转) + - [3.2.1. 问题](#321-问题) + - [3.2.2. 情况](#322-情况) + - [3.2.2.1. case1: x 的叔叔是红色的](#3221-case1--x-的叔叔是红色的) + - [3.2.2.2. case2: x 的叔叔是黑色, x,p(x), p(p(x)),方向为 left-right 或者 right-left](#3222-case2-x-的叔叔是黑色-xpx-ppx方向为-left-right-或者-right-left) + - [3.2.2.3. case3: x 的叔叔是黑色, x,p(x), p(p(x)),方向为 left-left 或者 right-right](#3223-case3-x-的叔叔是黑色-xpx-ppx方向为-left-left-或者-right-right) + - [3.2.3. 总体解决方案](#323-总体解决方案) +- [4. 删除](#4-删除) + - [4.1. 二叉查找树删除结点](#41-二叉查找树删除结点) + - [4.2. 调整颜色与旋转](#42-调整颜色与旋转) +- [5. 数据结构的扩张](#5-数据结构的扩张) + - [5.1. 平衡树的扩张](#51-平衡树的扩张) +- [6. python 代码](#6-python-代码) +- [7. 参考](#7-参考) + + + + + +# 1. 定义与性质 +红黑树是一种平衡的二叉查找树 + +## 1.1. 数据域 +每个结点有 5 个数据域 +* color: red or black +* key: keyword +* left: pointer to left child +* right:pointer to right child +* p: pointer to nil leaf + + +## 1.2. 红黑性质 +满足下面的 `红黑性质` 的二叉查找树就是红黑树: +* 每个结点或是红色或是黑色 +* 根是黑 +* nil leaf 是 黑 +* 红结点的孩子是黑 +* 从每个结点出发,通过子孙到达叶子结点的各条路径上 黑结点数相等 + +如,叶子结点 是 nil, 即不存储任何东西, 为了编程方便,相对的,存有数据的结点称为内结点 +![](https://upload-images.jianshu.io/upload_images/7130568-95927d3ca6cc524d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +为了节省空间, 可以如下实现, 只需要一个 nil 结点 +![nil leaf](https://upload-images.jianshu.io/upload_images/7130568-f8dbd241fbc55ee5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + + +## 1.3. 黑高度 +从某个结点 x 到叶结点的黑色结点数,称为此结点的黑高度, 记为 $h_b(x)$ +树的黑高度是根的黑高度 + +>1. 以 x 为 根的子树至少包含 $2^{h_b(x)}-1$个结点 +>2. 一颗有 n 个内结点的红黑树高度至多为$2lg(n+1)$ + +可用归纳法证明1 +证明 2: +设树高 h +由红黑性质4, 根结点到叶子路径上的黑结点数至少 $\frac{h}{2}$,即 $h_b(root)\geqslant \frac{h}{2}$ +再由1, +$$n \geqslant 2^{h_b(x)} -1 \geqslant 2^{\frac{h}{2}} -1$$ + +即 $ h\leqslant 2lg(n+1)$ + + +# 2. 旋转 +由于上面证明的红黑树高为 $O(logn)$,红黑树的 insert, delete, search 等操作都是, $O(logn)$. +进行了 insert, delete 后可能破坏红黑性质, 可以通过旋转来保持. + + +下面是对结点 x 进行 左旋与右旋. +注意进行左旋时, 右孩子不是 nil(要用来作为旋转后 x 的双亲), 同理 右旋的结点的左孩子不是nil +![左旋与右旋](https://upload-images.jianshu.io/upload_images/7130568-d31b65b547ff2e7c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +总结起来就是: 父亲旋转,顺时针就是右旋,逆时针就是左旋, 旋转的结果是儿子成为原来父亲的新父亲, 即旋转的结点下降一层, 它的一个儿子上升一层. + + +# 3. 插入 +插入的过程: +* 先同二叉查找树那样插入, 做为叶子(不为空) +* 然后将新结点的 左右孩子设为 nil , 颜色设为红色 +* 最后再进行颜色调整以及旋转(维持红黑性质) + +这是算法导论[^1]上的算法 +```python +RB-INSERT(T, z) + y ← nil[T] // 新建节点“y”,将y设为空节点。 + x ← root[T] // 设“红黑树T”的根节点为“x” + while x ≠ nil[T] // 找出要插入的节点“z”在二叉树T中的位置“y” + do y ← x + if key[z] < key[x] + then x ← left[x] + else x ← right[x] + p[z] ← y // 设置 “z的父亲” 为 “y” + if y = nil[T] + then root[T] ← z // 情况1:若y是空节点,则将z设为根 + else if key[z] < key[y] + then left[y] ← z // 情况2:若“z所包含的值” < “y所包含的值”,则将z设为“y的左孩子” + else right[y] ← z // 情况3:(“z所包含的值” >= “y所包含的值”)将z设为“y的右孩子” + left[z] ← nil[T] // z的左孩子设为空 + right[z] ← nil[T] // z的右孩子设为空。至此,已经完成将“节点z插入到二叉树”中了。 + color[z] ← RED // 将z着色为“红色” + RB-INSERT-FIXUP(T, z) // 通过RB-INSERT-FIXUP对红黑树的节点进行颜色修改以及旋转,让树T仍然是一颗红黑树 +``` + +## 3.1. 二叉查找树的插入 +可以用python 实现如下 +```python + def insert(self,nd): + if not isinstance(nd,node): + nd = node(nd) + elif nd.isBlack: nd.isBlack = False + + if self.root is None: + self.root = nd + self.root.isBlack = True + else: + parent = self.root + while parent: + if parent == nd : return None + if parent>nd: + if parent.left : + parent = parent.left + else: + parent.left = nd + break + else: + if parent.right: + parent = parent.right + else: + parent.right = nd + break + self.fixUpInsert(parent,nd) +``` + +## 3.2. 颜色调整与旋转 + +### 3.2.1. 问题 +在插入后,可以发现后破坏的红黑性质只有以下两条(且互斥) + +1. root 是红 (这可以直接将root 颜色设为黑调整) +2. 红结点的孩子是黑 + +所以下面介绍如何保持 红结点的孩子是黑 , 即插入结点的双亲结点是红的情况. + +下面记 结点 x 的 双亲为 p(x), 新插入的结点为 x, 记 uncle 结点 为 u(x) + +由于 p(x) 是红色, 而根结点是黑色, 所以 p(x)不是根, p(p(x))存在 + +### 3.2.2. 情况 + +有如下三种情况 + +![](https://upload-images.jianshu.io/upload_images/7130568-04e77807cb660277.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +每种情况的解决方案如下 + + +#### 3.2.2.1. case1: x 的叔叔是红色的 +这里只需改变颜色, 将 p(x)变为 黑, p(p(x))变为红, u(x) 变为黑色 (x为右孩子同样) +![](https://upload-images.jianshu.io/upload_images/7130568-a884903d8fed7e7b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + + + +#### 3.2.2.2. case2: x 的叔叔是黑色, x,p(x), p(p(x)),方向为 left-right 或者 right-left +即 x,p(x), p(p(x)) 成折线状 + +#### 3.2.2.3. case3: x 的叔叔是黑色, x,p(x), p(p(x)),方向为 left-left 或者 right-right +即 x,p(x), p(p(x)) 成直线状 + +![](https://upload-images.jianshu.io/upload_images/7130568-4b86ce66ddff0e08.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + + +当 x 为右孩子时, 通过旋转变成p(x) 的双亲, 然后相当于 新插入 p(x)作为左孩子, 再进行转换. + +即将新结点的双亲向上一层旋转,颜色变为黑色, 而新节点的祖父向下一层, 颜色变为红色 + + +### 3.2.3. 总体解决方案 +我最开始也没有弄清楚, 有点绕晕的感觉, 后来仔细读了书上伪代码, 然后才发现就是一个状态机, 画出来就一目了然了. + +![](https://upload-images.jianshu.io/upload_images/7130568-bd3a0ffca482eb73.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + + +现在算是知其然了, 那么怎样知其所以然呢? 即 为什么要分类这三个 case, 不重不漏了吗? + +其实也简单, 只是太繁琐. +就是将各种情况枚举出来, 一一分析即可. 我最开始试过, 但是太多,写在代码里很容易写着写着就混了. +而算法导论上分成这三个case , 很简洁, 只是归纳了一下而已. 如果想看看枚举情况的图与说明,可以参考[^2] . + + + +算法导论上的伪代码 +```python +RB-INSERT-FIXUP(T, z) +while color[p[z]] = RED // 若“当前节点(z)的父节点是红色”,则进行以下处理。 + do if p[z] = left[p[p[z]]] // 若“z的父节点”是“z的祖父节点的左孩子”,则进行以下处理。 + then y ← right[p[p[z]]] // 将y设置为“z的叔叔节点(z的祖父节点的右孩子)” + if color[y] = RED // Case 1条件:叔叔是红色 + then color[p[z]] ← BLACK ▹ Case 1 // (01) 将“父节点”设为黑色。 + color[y] ← BLACK ▹ Case 1 // (02) 将“叔叔节点”设为黑色。 + color[p[p[z]]] ← RED ▹ Case 1 // (03) 将“祖父节点”设为“红色”。 + z ← p[p[z]] ▹ Case 1 // (04) 将“祖父节点”设为“当前节点”(红色节点) + else if z = right[p[z]] // Case 2条件:叔叔是黑色,且当前节点是右孩子 + then z ← p[z] ▹ Case 2 // (01) 将“父节点”作为“新的当前节点”。 + LEFT-ROTATE(T, z) ▹ Case 2 // (02) 以“新的当前节点”为支点进行左旋。 + color[p[z]] ← BLACK ▹ Case 3 // Case 3条件:叔叔是黑色,且当前节点是左孩子。(01) 将“父节点”设为“黑色”。 + color[p[p[z]]] ← RED ▹ Case 3 // (02) 将“祖父节点”设为“红色”。 + RIGHT-ROTATE(T, p[p[z]]) ▹ Case 3 // (03) 以“祖父节点”为支点进行右旋。 + else (same as then clause with "right" and "left" exchanged) // 若“z的父节点”是“z的祖父节点的右孩子”,将上面的操作中“right”和“left”交换位置,然后依次执行。 +color[root[T]] ← BLACK +``` +我用python 实现如下. 由于左右方向不同, 如果向上面伪代码那样实现, fixup 代码就会有两份类似的(即 right left 互换), 为了减少代码冗余, 我就定义了 `setChild`, `getChild` 函数, 传递左或是右孩子这个方向的数据(代码中是isLeft), 所以下面的就是完整功能的 fixup, 可以减少一般的代码量, haha😄, +(下文 删除结点同理) + +其实阅读代码也简单, 可以直接当成 isLeft 取真值. +```python + def fixUpInsert(self,parent,nd): + ''' adjust color and level, there are two red nodes: the new one and its parent''' + while not self.checkBlack(parent): + grand = self.getParent(parent) + isLeftPrt = grand.left is parent + uncle = grand.getChild(not isLeftPrt) + if not self.checkBlack(uncle): + # case 1: new node's uncle is red + self.setBlack(grand, False) + self.setBlack(grand.left, True) + self.setBlack(grand.right, True) + nd = grand + parent = self.getParent(nd) + else: + # case 2: new node's uncle is black(including nil leaf) + isLeftNode = parent.left is nd + if isLeftNode ^ isLeftPrt: + # case 2.1 the new node is inserted in left-right or right-left form + # grand grand + # parent or parent + # nd nd + parent.setChild(nd.getChild(isLeftPrt),not isLeftPrt) + nd.setChild(parent,isLeftPrt) + grand.setChild(nd,isLeftPrt) + nd,parent = parent,nd + # case 2.2 the new node is inserted in left-left or right-right form + # grand grand + # parent or parent + # nd nd + grand.setChild(parent.getChild(not isLeftPrt),isLeftPrt) + parent.setChild(grand,not isLeftPrt) + self.setBlack(grand, False) + self.setBlack(parent, True) + self.transferParent(grand,parent) + self.setBlack(self.root,True) +``` + + + +# 4. 删除 + + +算法导论上的算法 + +写的很简练👍 +![rb-delete](https://upload-images.jianshu.io/upload_images/7130568-688842ec88c4a598.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + + +## 4.1. 二叉查找树删除结点 +下面 z 是要删除的结点, y 是 其后继或者是它自己, x 是 y 的一个孩子(如果 y 的孩子为 nil,则为 nli, 否则 y 只有一个非 nil 孩子, 为 x) + +* 当 z 孩子全是 nil (y==z): 直接让其双亲对应的孩子为 nil +* 当 z 只有一个非 nil 孩子 x (y==z): + 1. 如果 z 为根, 则让 x 为根. + 2. 让 y 的双亲连接到 x +* 当 z 有两个非nil孩子(y!=z): 复制其后继 y 的内容到 z (除了指针,颜色) , 将其后继 y 的孩子(最多只有一个 非 nil ,不然就不是后继了)连接到其后继的双亲, 删除 其后继y, + +即[^3] 如果要删除有两个孩子的结点 z , 则找到它的后继y(前趋同理), 可以推断 y 一定没有左孩子, 右孩子可能有,可能没有. 也就是最多一个孩子. +所以将 y 的值复制到 x 位置, 现在相当于删除 y 处的结点. +这样就化为 删除的结点最多一个孩子的情况. + + +![](http://upload-images.jianshu.io/upload_images/7130568-87ab28beaec30567?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + + + +## 4.2. 调整颜色与旋转 + 可以发现只有当 y 是黑色,才进行颜色调整以及旋转(维持红黑性质), 因为如果删除的是红色, 不会影响黑高度, 所有红黑性质都不会破坏 +伪代码如下, (我的python代码见文末) +![](https://upload-images.jianshu.io/upload_images/7130568-ed40ae4776709377.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +如果被删除的结点 y 是黑色的, 有三种破坏红黑性质的情况 +1. y是根, 则 y 的一个红色孩子成为新根 +2. 进行删除结点过程中, p(y) 的孩子有 x, 两者都是红色 +3. 删除 y 导致包含y 的路径上的黑结点 少 1个 + +修复3的思路: +如果可能,在兄弟一支,通过旋转,改变颜色修复 +否则, 将红结点一直向上推(因为当前路径上少了一个黑结点,向上推的过程中使红结点所在的子树都少一个黑结点), 直到到达树根, 那么全部路径都少一个黑结点, 3就修复了, 这时只需将根设为黑就修复了 1 + +代码中的 while 循环的目的是将额外的黑色沿树上移,直到 +* x 指向一个红黑结点 +* x 指向根,这时可以简单地消除额外的黑色 +* 颜色修改与旋转 + +在 while 中, x 总是指向具有双重黑色的那个非根结点, 在第 2 行中要判断 x 是其双亲的左右孩子 +w 表示 x 的相抵. w 不能为 nil(因为 x 是双重黑色) + +算法中的四种情况如图所示 +![](https://upload-images.jianshu.io/upload_images/7130568-f367bcb131c9719b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +即 +* x 的兄弟 w 是红色的 +![](https://upload-images.jianshu.io/upload_images/7130568-cd139202bdc5406f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) +* x 的兄弟 w 是黑色的, w的两个孩子都是黑色的 + +* x 的兄弟 w 是黑色的, w 的左孩子是红,右孩子是黑 +* x 的兄弟 w 是黑色的, w 的孩子是红色的 + +>>注意上面都是先考虑的左边, 右边可以对称地处理. + +同插入一样, 为了便于理解, 可以作出状态机. +而且这些情形都是归纳化简了的, 你也可以枚举列出基本的全部情形. +![](https://upload-images.jianshu.io/upload_images/7130568-d6e8a332afade8d5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + + +# 5. 数据结构的扩张 + +## 5.1. 平衡树的扩张 +通过在平衡树(如红黑树上的每个结点 加上 一个数据域 size (表示以此结点为根的子树的结点数.) 可以使`获得第 i 大的数` 的时间复杂度为 $O(logn)$ + +在 $O(n)$ 时间内建立, python代码如下 +```python +def setSize(root): + if root is None:return 0 + root.size = setSize(root.left) + setSize(root.right)+1 +``` +在$O(logn)$时间查找, +```python +def find(root,i): + r = root.left.size +1 + if r==i: + return root + if r > i: + return find(root.left,i) + else: + return find(root.right,i-r) +``` + + + + +# 6. python 代码 + +**[github地址](https://github.com/mbinary/algorithm-and-data-structure.git)** + +我用了 setChild, getChild 来简化代码量, 其他的基本上是按照算法导论上的伪代码提到的case 来实现的. 然后display 只是测试的时候,为了方便调试而层序遍历打印出来 + +效果如下 +![](https://upload-images.jianshu.io/upload_images/7130568-721e18cc44dec604.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) + +```python +''' +######################################################################### +# File : redBlackTree.py +# Author: mbinary +# Mail: zhuheqin1@gmail.com +# Blog: https://mbinary.coding.me +# Github: https://github.com/mbinary +# Created Time: 2018-07-12 20:34 +# Description: +######################################################################### +''' +from functools import total_ordering +from random import randint, shuffle + +@total_ordering +class node: + def __init__(self,val,left=None,right=None,isBlack=False): + self.val =val + self.left = left + self.right = right + self.isBlack = isBlack + def __lt__(self,nd): + return self.val < nd.val + def __eq__(self,nd): + return nd is not None and self.val == nd.val + def setChild(self,nd,isLeft = True): + if isLeft: self.left = nd + else: self.right = nd + def getChild(self,isLeft): + if isLeft: return self.left + else: return self.right + def __bool__(self): + return self.val is not None + def __str__(self): + color = 'B' if self.isBlack else 'R' + return f'{color}-{self.val:}' + def __repr__(self): + return f'node({self.val},isBlack={self.isBlack})' +class redBlackTree: + def __init__(self,unique=False): + '''if unique is True, all node'vals are unique, else there may be equal vals''' + self.root = None + self.unique = unique + + @staticmethod + def checkBlack(nd): + return nd is None or nd.isBlack + @staticmethod + def setBlack(nd,isBlack): + if nd is not None: + if isBlack is None or isBlack: + nd.isBlack = True + else:nd.isBlack = False + def sort(self,reverse = False): + ''' return a generator of sorted data''' + def inOrder(root): + if root is None:return + if reverse: + yield from inOrder(root.right) + else: + yield from inOrder(root.left) + yield root + if reverse: + yield from inOrder(root.left) + else: + yield from inOrder(root.right) + yield from inOrder(self.root) + def getParent(self,chd): + '''note that use is to find real node when different nodes have euqiv val''' + if self.root is chd:return None + nd = self.root + while nd: + if nd>chd and nd.left is not None: + if nd.left is chd: return nd + else: nd = nd.left + elif ndval: + nd = nd.left + else: + nd = nd.right + def getSuccessor(self,nd): + if nd: + if nd.right: + nd = nd.right + while nd.left: + nd = nd.left + return nd + else:return self.getParent(nd) + def transferParent(self,origin,new): + if origin is self.root: + self.root = new + else: + prt = self.getParent(origin) + prt.setChild(new, prt.left is origin) + + def insert(self,nd): + if not isinstance(nd,node): + nd = node(nd) + elif nd.isBlack: nd.isBlack = False + + if self.root is None: + self.root = nd + self.root.isBlack = True + else: + parent = self.root + while parent: + if parent == nd : return None + if parent>nd: + if parent.left : + parent = parent.left + else: + parent.left = nd + break + else: + if parent.right: + parent = parent.right + else: + parent.right = nd + break + self.fixUpInsert(parent,nd) + def fixUpInsert(self,parent,nd): + ''' adjust color and level, there are two red nodes: the new one and its parent''' + while not self.checkBlack(parent): + grand = self.getParent(parent) + isLeftPrt = grand.left is parent + uncle = grand.getChild(not isLeftPrt) + if not self.checkBlack(uncle): + # case 1: new node's uncle is red + self.setBlack(grand, False) + self.setBlack(grand.left, True) + self.setBlack(grand.right, True) + nd = grand + parent = self.getParent(nd) + else: + # case 2: new node's uncle is black(including nil leaf) + isLeftNode = parent.left is nd + if isLeftNode ^ isLeftPrt: + # case 2.1 the new node is inserted in left-right or right-left form + # grand grand + # parent or parent + # nd nd + parent.setChild(nd.getChild(isLeftPrt),not isLeftPrt) + nd.setChild(parent,isLeftPrt) + grand.setChild(nd,isLeftPrt) + nd,parent = parent,nd + # case 2.2 the new node is inserted in left-left or right-right form + # grand grand + # parent or parent + # nd nd + grand.setChild(parent.getChild(not isLeftPrt),isLeftPrt) + parent.setChild(grand,not isLeftPrt) + self.setBlack(grand, False) + self.setBlack(parent, True) + self.transferParent(grand,parent) + self.setBlack(self.root,True) + + def copyNode(self,src,des): + '''when deleting a node which has two kids, + copy its succesor's data to his position + data exclude left, right , isBlack + ''' + des.val = src.val + def delete(self,nd): + '''delete node in a binary search tree''' + if not isinstance(nd,node): + nd = self.find(nd) + if nd is None: return + y = None + if nd.left and nd.right: + y= self.getSuccessor(nd) + else: + y = nd + py = self.getParent(y) + x = y.left if y.left else y.right + if py is None: + self.root = x + elif y is py.left: + py.left = x + else: + py.right = x + if y != nd: + self.copyNode(y,nd) + + if self.checkBlack(y): self.fixUpDel(py,x) + + + def fixUpDel(self,prt,chd): + ''' adjust colors and rotate ''' + while self.root != chd and self.checkBlack(chd): + isLeft = prt.left is chd + brother = prt.getChild(not isLeft) + # brother is black + lb = self.checkBlack(brother.getChild(isLeft)) + rb = self.checkBlack(brother.getChild(not isLeft)) + if not self.checkBlack(brother): + # case 1: brother is red. converted to case 2,3,4 + # prt (isLeft) rotate + prt.setChild(brother.getChild(isLeft), not isLeft) + brother.setChild(prt, isLeft) + + self.setBlack(prt,False) + self.setBlack(brother,True) + + self.transferParent(prt,brother) + elif lb and rb: + # case 2: brother is black and two kids are black. + # conveted to the begin case + self.setBlack(brother,False) + chd = prt + prt = self.getParent(chd) + else: + if rb: + # case 3: brother is black and left kid is red and right child is black + # uncle's son is nephew, and niece for uncle's daughter + nephew = brother.getChild(isLeft) + self.setBlack(nephew,True) + self.setBlack(brother,False) + + # brother (not isLeft) rotate + prt.setChild(nephew,not isLeft) + brother.setChild(nephew.getChild(not isLeft),isLeft) + nephew.setChild(brother, not isLeft) + brother = nephew + + # case 4: brother is black and right child is red + brother.isBlack = prt.isBlack + self.setBlack(prt,True) + self.setBlack(brother.getChild(not isLeft),True) + + # prt left rotate + prt.setChild(brother.getChild(isLeft),not isLeft) + brother.setChild(prt,isLeft) + + self.transferParent(prt,brother) + + chd = self.root + self.setBlack(chd,True) + + + def display(self): + def getHeight(nd): + if nd is None:return 0 + return max(getHeight(nd.left),getHeight(nd.right)) +1 + def levelVisit(root): + from collections import deque + lst = deque([root]) + level = [] + h = getHeight(root) + ct = lv = 0 + while 1: + ct+=1 + nd = lst.popleft() + if ct >= 2**lv: + lv+=1 + if lv>h:break + level.append([]) + level[-1].append(str(nd)) + if nd is not None: + lst += [nd.left,nd.right] + else: + lst +=[None,None] + return level + def addBlank(lines): + width = 5 + sep = ' '*width + n = len(lines) + for i,oneline in enumerate(lines): + k = 2**(n-i) -1 + new = [sep*((k-1)//2)] + for s in oneline: + new.append(s.ljust(width)) + new.append(sep*k) + lines[i] = new + return lines + + lines = levelVisit(self.root) + lines = addBlank(lines) + li = [''.join(line) for line in lines] + li.insert(0,'red-black-tree'.rjust(48,'-') + '-'*33) + li.append('end'.rjust(42,'-')+'-'*39+'\n') + return '\n'.join(li) + + def __str__(self): + return self.display() +``` +测试代码 +```python +def genNum(n =10): + nums =[] + for i in range(n): + while 1: + d = randint(0,100) + if d not in nums: + nums.append(d) + break + return nums + +def buildTree(n=10,nums=None,visitor=None): + if nums is None or nums ==[]: nums = genNum(n) + rbtree = redBlackTree() + print(f'build a red-black tree using {nums}') + for i in nums: + rbtree.insert(i) + if visitor: + visitor(rbtree) + return rbtree,nums +def testInsert(): + def visitor(t): + print(t) + rbtree,nums = buildTree(visitor = visitor) + print('-'*5+ 'in-order visit' + '-'*5) + for i,j in enumerate(rbtree.sort()): + print(f'{i+1}: {j}') + +def testSuc(): + rbtree,nums = buildTree() + for i in rbtree.sort(): + print(f'{i}\'s suc is {rbtree.getSuccessor(i)}') + +def testDelete(): + #nums = [2,3,3,2,6,7,2,1] + nums = None + rbtree,nums = buildTree(nums = nums) + print(rbtree) + for i in nums: + print(f'deleting {i}') + rbtree.delete(i) + print(rbtree) + +if __name__=='__main__': + #testSuc() + #testInsert() + testDelete() +``` + + +# 7. 参考 +[^1]: 算法导论 + +[^2]: https://www.jianshu.com/p/a5514510f5b9?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation +[^3]: https://www.jianshu.com/p/0b68b992f688?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation