1. 概述
红⿊树(R-B Tree)是由Rudolf Bayer在1972发明。是⼀种⾃平衡⼆叉搜索树,其中每个结点都有⼀个额外的位,存储该结点的颜⾊(红⾊或⿊⾊)
1.1 特性
时间复杂度:插⼊、搜索和删除操作O(logn)
空间复杂度:O(n)
红⿊树还添加了⼀种特殊类型的结点 ,称为
NIL结点。NIL结点将做为红⿊树的伪叶⼦结点出现对于任意⼀个结点
n结点的颜⾊只能是红⾊或者⿊⾊
根结点是⿊⾊的
从根到任何叶结点的每条路径必须具有相同数量的⿊⾊结点
没有两个红⾊结点可以相连,即⼀个红⾊结点不能是另⼀个红⾊结点的⽗结点或⼦结点
NIL结点的颜⾊是⿊⾊如果结点的颜⾊是红⾊,则其⼦结点均为⿊⾊
具有
n个结点的红⿊树的⾼度为:h <= 2log(n + 1)红⿊树的⿊⾊⾼度是从根结点到叶结点的路径上的⿊⾊结点数。叶结点也算作⿊⾊结点。因此,⾼度为
h的红⿊树的⿊⾊⾼度 >=h / 2
在每次插⼊或删除后,如果任何结点的平衡因⼦不遵循平衡属性
左旋
右旋
1.2 优势
⼤多数
BST操作,例如:搜索、最⼤值、最⼩值、插⼊、删除,都需要O(h)时间,其中h是BST的⾼度如果我们确保每次插⼊和删除后树的⾼度始终为O(log n),那么就可以保证所有这些操作的上限为O(log
n)。红⿊树的⾼度始终为O(log n),其中n是树中的结点数
红⿊树主要⽤于实现频繁执⾏插⼊或删除
⼤多数⾃平衡
BST库函数,如C++中的map和set,或Java中的TreeSet和TreeMap都使⽤红⿊树MySQL还使⽤红⿊树作为表索引实现
Linux的CPU调度
1.3 对比AVL树
AVL树 VS 红⿊树:
红⿊树是不符合
AVL树的平衡条件的,即每个结点的左⼦树和右⼦树的⾼度最多差1的⼆叉查找树红⿊树是⽤⾮严格的平衡来换取增删结点时候旋转次数的降低,任何不平衡都会在三次旋转之内解决
AVL是严格平衡树,因此在增加或者删除结点的时候,根据不同情况,旋转的次数⽐红⿊树要多。所以红⿊树的插⼊效率更⾼AVL在查询频繁的系统⾥⽐红⿊树更⾼效,原因是AVL树的平均⾼度⽐红⿊树更⼩AVL树主要用于搜索,红⿊树主要用于插入和删除
2. 2-3-4树
红黑树是2-3-4树的一种转型,而2-3-4树是四阶的B树(Balance Tree)
结点存储
1,2或者3个key,并且拥有2,3或者4个孩⼦所有叶⼦结点都拥有相同的深度
1 / 2log(N + 1 ) ≤ height ≤ log(N + 1)
B树和平衡二叉树稍有不同,B树属于多叉树又名平衡多路查找树(查找路径不只两个),数据库索引技术里大量使用者B树和B+树的数据结构
B+树是B树的一个升级版,相对于B树来说,B+树更充分的利用了结点的空间,让查询速度更加稳定,其速度完全接近于二分法查找
2.1 形态
两个结点,和⼆叉树⼀样:
三个结点,有两个key和三个孩⼦:
四个结点,有三个key和四个孩⼦:
假设:2-3-4树中每个结点都拥有相同数量的孩⼦结点d:
整个
2-3-4树的搜索时间为O(log(d)N)如果
d = N¹⁄²,那么该树的⾼度为22-3-4树可以保证O(logN),如果每个结点只有2,3或者4个孩⼦
2.2 插⼊
插入元素g,结点从两个变为三个:
插⼊元素f,结点从三个变为四个:
插⼊元素e,每当我们到达⼀个4结点,把它分成两个2结点,并将中间元素向上移动进⽗结点
插⼊元素g,每当我们到达⼀个4结点,把它分成两个2结点,并将中间元素向上移动进⽗结点
2.3 特性
搜索的时间复杂度是O(log N)
插⼊的时间复杂度是O(log N)
删除的时间复杂度是O(log N)
优点:
平衡
O(log N)
缺点:
- 数据结构复杂
我们将2-3-4树的优点带⼊到⼆叉树中,就是红⿊树。它继承了2-3-4树的优点,将搜索、插入、删除的时间控制在O(log N)
3. 红黑树
2-3-4树的每一个结点都对应红黑树的一种结构,所以每一棵2-3-4树也都对应一棵红黑树
3.1 形态
2-3-4树与红黑树的对应形态:
两个结点,对应红黑树结构:
三个结点,对应红黑树结构:
a和b谁作为根结点,取决于它们的插入顺序,先插入的元素为根结点
四个结点,对应红黑树结构:
依次插入结点
a、b、c,a作为根结点,b为a的右孩子,c为b的右孩子,此时结构为一棵右斜树根据红黑树特性,根结点
a一定为黑色,而b和c作为新插入结点为红色由于红黑树不允许两个红⾊结点相连,故此进行左旋修复
修复后的红黑树
b为根结点,a、c分别为b的左、右孩子,同时树结构达到平衡
3.2 特性
N:结点总数
L:叶⼦数量 = N + 1
H:⾼度
B:⿊⾊结点的⾼度(从根结点到叶结点的路径上的⿊⾊结点数)
规律1:2 ^ B <= N + 1 <= 4 ^ B
规律2:1/2log(N + 1) <= B <= log(N + 1)
规律3:log(N + 1) <= H <= 2log(N + 1)
4. 结点插入
插入的基本流程:
标准⼆叉搜索树,搜索找到要添加的位置
执⾏替换,颜⾊设置为红⾊
新结点添加叶⼦结点(
NIL结点),颜⾊设置为⿊⾊如果该⽗结点也为红⾊,需要修复
如何修复取决于⽗结点的
sibling(兄弟结点)
4.1 思路分析
4.1.1 情况⼀(A)
新结点的⽗结点是⿊⾊结点,什么也不做
4.1.2 情况⼀(B)
新结点的父结点是红色,但它同时作为根结点,直接将其颜色修改为黑色
4.1.3 情况二(A)
新结点的⽗结点是红⾊结点,它的叔叔结点是⿊⾊结点,⽗结点处沿插⼊结点的相反⽅向旋转,此时是右旋。⽗结点重新设置为⿊⾊,祖父结点设置为红⾊
4.1.4 情况二(B)
新结点的⽗结点是红⾊结点,它的叔叔结点是⿊⾊结点,⽗结点处沿插⼊结点的相反⽅向旋转,此时是左旋。⽗结点重新设置为⿊⾊,祖父结点设置为红⾊
4.1.5 情况二(C)
新结点的⽗结点是红⾊结点,不在⼀边。它的叔叔结点是⿊⾊结点,先左旋,再右旋
4.1.6 情况二(D)
新结点的⽗结点是红⾊结点,不在⼀边。它的叔叔结点是⿊⾊结点,先右旋,再左旋
3.2.7 情况三
新结点的⽗结点是红⾊结点,它的叔叔结点是红⾊结点。将⽗结点和叔叔结点重新着⾊为⿊⾊,将祖⽗结点重新着⾊为红⾊。如果祖⽗结点为根结点,重新着⾊为黑⾊
如果祖⽗结点着⾊为红⾊,有可能还会面临红色相连的结点,例如:R、U
需要将祖⽗结点进行递归修复
4.2 代码实现
4.2.1 枚举定义
class RBTree<Element: Comparable> {enum RBColor : Int {case Red = 0case Black = 1}enum RBRotating : Int {case Left = 0case Right = 1}}
4.2.2 结点结构
class RBTree<Element: Comparable> {fileprivate class RBNode: Equatable {static func == (lhs: RBTree<Element>.RBNode, rhs: RBTree<Element>.RBNode) -> Bool {let obj1 = Unmanaged<AnyObject>.passUnretained(lhs).toOpaque();let obj2 = Unmanaged<AnyObject>.passUnretained(rhs).toOpaque();return obj1 == obj2;}static func != (lhs: RBTree<Element>.RBNode, rhs: RBTree<Element>.RBNode) -> Bool {let obj1 = Unmanaged<AnyObject>.passUnretained(lhs).toOpaque();let obj2 = Unmanaged<AnyObject>.passUnretained(rhs).toOpaque();return obj1 != obj2;}// 颜色var color : RBColor;// 元素var data : Element?;// 左孩子var leftChild : RBNode?;// 右孩子var rightChild : RBNode?;// 父结点var parent : RBNode?;// 初始化init(data: Element?, parent: RBNode?) {if(parent == nil) {// 根结点,默认黑色self.color = RBColor.Black;}else if(data == nil) {// NIL结点,默认黑色self.color = RBColor.Black;}else {// 其他新创建的结点,默认红色self.color = RBColor.Red;}self.data = data;self.parent = parent;if(data != nil){self.leftChild = RBNode(data: nil, parent: self);self.rightChild = RBNode(data: nil, parent: self);}}// 颜色格式化var colorFormat: String {if(self.color == RBColor.Red){return "red";}return "black";}// 当前结点是否为根结点var isRoot: Bool {if(self.parent == nil){return true;}return false;};// 当前结点是否为左孩子结点var isLeftChild: Bool {get{if(self.isRoot){return false;}if(self.parent!.leftChild == self) {return true;}return false;}};// 当前结点是否为右孩子结点var isRightChild: Bool {get{if(self.isRoot){return false;}if(self.parent!.rightChild == self) {return true;}return false;}};// 当前结点是否存在父结点var isExistParent: Bool {get{if(self.isRoot){return false;}return true;}};// 当前结点是否存在祖父结点var isExistGrandparent: Bool {get{if(!self.isExistParent){return false;}return self.parent!.isExistParent;}};// 获取祖父结点var grandparent: RBNode? {if(!self.isExistGrandparent){return nil;}return self.parent?.parent;};// 当前结点是否为叶子结点var isLeaf: Bool {get{if(self.leftChild == nil && self.rightChild == nil){return true;}return false;}};// 当前结点是否NIL结点var isNilLeaf: Bool {get{if(self.data == nil && self.color == RBColor.Black && self.isLeaf){return true;}return false;}};// 是否存在兄弟结点var isExistSibling: Bool {get{if(self.isRoot){return false;}return true;}};// 获取兄弟结点var sibling: RBNode? {get{if(!self.isExistSibling){return nil;}if(self.isLeftChild){return self.parent!.rightChild;}return self.parent!.leftChild;}};// 是否存在叔叔结点var isExistUncle: Bool {get{if(self.isRoot){return false;}if(self.parent!.isRoot){return false;}return true;}};// 获取叔叔结点var uncle: RBNode? {if(!self.isExistUncle){return nil;}return self.parent!.sibling;}}}
4.2.3 添加元素
class RBTree<Element: Comparable> {fileprivate var _root: RBNode?;// 添加元素func add(data: Element) {// 添加,返回新结点let node = addNode(data: data);// 新结点为空,添加失败,直接返回if(node == nil){return;}// 修复addFixup(node: node!);}// 添加结点fileprivate func addNode(data: Element) -> RBNode? {// 根结点为空if(_root == nil){// 创建根结点_root = RBNode(data: data, parent: nil);return _root!;}var node = _root!;while(!node.isNilLeaf) {if(data < node.data!){if(node.leftChild!.isNilLeaf) {node.leftChild = RBNode(data: data, parent: node);return node.leftChild!;}node = node.leftChild!;continue;}if(node.rightChild!.isNilLeaf) {node.rightChild = RBNode(data: data, parent: node);return node.rightChild!;}node = node.rightChild!;}return nil;}}
4.2.4 结点修复
class RBTree<Element: Comparable> {// 修复fileprivate func addFixup(node: RBNode) {// 当前结点为根结点if(node.isRoot) {// 着色为黑色node.color = RBColor.Black;// 直接返回return;}// 没有父结点,直接返回if(!node.isExistParent) {return;}// 获取父结点let parent = node.parent!;// 【情况⼀(A)】新结点的⽗结点是⿊⾊结点,什么也不做if(parent.color == RBColor.Black) {return;}// 【情况⼀(B)】新结点的父结点是红色,但它同时作为根结点,直接将其颜色修改为黑色if(parent.color == RBColor.Red && parent.isRoot){parent.color = RBColor.Black;return;}// 没有叔叔结点,或没有祖父结点,直接返回if(!node.isExistUncle || !node.isExistGrandparent) {return;}// 获取叔叔结点let uncle = node.uncle!;// 获取祖父结点let grandparent = node.grandparent!;// 【情况二】新结点的⽗结点是红⾊结点,它的叔叔结点是⿊⾊结点if(parent.color == RBColor.Red && uncle.color == RBColor.Black){// 保存当前结点var currNode = node;// 【情况二(C)】不在⼀边,先左旋if(parent.isLeftChild && currNode.isRightChild) {// 左旋rotating(node: parent, rbRotating: RBRotating.Left);// 旋转后当前结点改变,更新当前结点currNode = parent;}// 【情况二(D)】不在⼀边,先右旋else if(parent.isRightChild && currNode.isLeftChild) {// 右旋rotating(node: parent, rbRotating: RBRotating.Right);// 旋转后当前结点改变,更新当前结点currNode = parent;}// ⽗结点重新设置为⿊⾊currNode.parent!.color = RBColor.Black;// 祖父结点设置为红⾊currNode.grandparent!.color = RBColor.Red;// 【情况二(A)】父结点与新结点在同一边,⽗结点处沿插⼊结点的相反⽅向旋转,此时是右旋if(currNode.isLeftChild) {rotating(node: currNode.grandparent!, rbRotating: RBRotating.Right);}// 【情况二(B)】父结点与新结点在同一边,⽗结点处沿插⼊结点的相反⽅向旋转,此时是左旋else {rotating(node: currNode.grandparent!, rbRotating: RBRotating.Left);}return;}// 【情况三】新结点的⽗结点是红⾊结点,它的叔叔结点是红⾊结点。将⽗结点和叔结点重新着⾊为⿊⾊,将祖⽗结点重新着⾊为红⾊if(parent.color == RBColor.Red && uncle.color == RBColor.Red) {// 将⽗结点重新着⾊为⿊⾊parent.color = RBColor.Black;// 将叔叔结点重新着⾊为⿊⾊uncle.color = RBColor.Black;// 将祖⽗结点重新着⾊为红⾊grandparent.color = RBColor.Red;// 如果祖⽗结点着⾊为红⾊,有可能还会面临红色相连的结点,传入祖⽗结点进行递归修复addFixup(node: grandparent);}}}
4.2.5 结点旋转
class RBTree<Element: Comparable> {// 旋转fileprivate func rotating(node: RBNode, rbRotating: RBRotating) {// 左旋if(rbRotating == RBRotating.Left){// 获取当前结点的右孩子let rightNode = node.rightChild;// 当前结点为根结点if(node.isRoot){// 将根结点,赋值为当前结点的右孩子_root = rightNode;// 当前结点右孩子的父结点,赋值为nilrightNode?.parent = nil;}else {// 获取当前结点的父结点let parent = node.parent;// 判断当前结点是左孩子、还是右孩子if(node.isLeftChild){// 当前结点为左孩子,将父结点的左孩子,赋值为当前结点的右孩子parent?.leftChild = rightNode;}else{// 当前结点为右孩子,将父结点的右孩子,赋值为当前结点的右孩子parent?.rightChild = rightNode;}// 当前结点右孩子的父结点,赋值为父结点rightNode?.parent = parent;}// 获取当前结点右孩子的左孩子let rightLeftNode = rightNode?.leftChild;// 当前结点的右孩子,赋值为当前结点右孩子的左孩子node.rightChild = rightLeftNode;// 当前结点右孩子的左孩子的父结点,赋值为当前结点rightLeftNode?.parent = node;// 当前结点右孩子的左孩子,赋值为当前结点rightNode?.leftChild = node;// 当前结点的父结点,赋值为当前结点的右孩子node.parent = rightNode;}// 右旋else {// 获取当前结点的左孩子let leftNode = node.leftChild;// 当前结点为根结点if(node.isRoot){// 将根结点,赋值为当前结点的左孩子_root = leftNode;// 当前结点左孩子的父结点,赋值为nilleftNode?.parent = nil;}else {// 获取当前结点的父结点let parent = node.parent;// 判断当前结点是左孩子、还是右孩子if(node.isLeftChild){// 当前结点为左孩子,将父结点的左孩子,赋值为当前结点的左孩子parent?.leftChild = leftNode;}else{// 当前结点为右孩子,将父结点的右孩子,赋值为当前结点的左孩子parent?.rightChild = leftNode;}// 当前结点左孩子的父结点,赋值为父结点leftNode?.parent = parent;}// 获取当前结点左孩子的右孩子let leftRightNode = leftNode?.rightChild;// 当前结点的左孩子,赋值为当前结点左孩子的右孩子node.leftChild = leftRightNode;// 当前结点左孩子的右孩子的父结点,赋值为当前结点leftRightNode?.parent = node;// 当前结点左孩子的右孩子,赋值为当前结点leftNode?.rightChild = node;// 当前结点的父结点,赋值为当前结点的左孩子node.parent = leftNode;}}}
4.2.6 红黑树打印
class RBTree<Element: Comparable> {func traverse() {if(_root == nil){print("[]");return;}let str = traverse(node: _root!, top: "", root: "", bottom: "");print("\(str)");}fileprivate func traverse(node: RBNode, top: String, root: String, bottom: String) -> String {if (node.isNilLeaf) {return "\(root)nil【\(node.colorFormat)】\n";}if (node.isLeaf) {return "\(root)nil【\(node.colorFormat)】\n";}let ri = traverse(node: node.rightChild!, top: "\(top) ", root: "\(top)┌───", bottom: "\(top)│ ");let mi = "\(root)\(node.data!)【\(node.colorFormat)】\n";let li = traverse(node: node.leftChild!, top: "\(bottom)│ ", root: "\(bottom)└───", bottom: "\(bottom) ");return "\(ri)\(mi)\(li)";}}
5. 结点删除
普通二叉搜索树的删除操作:查找当前结点最近的前驱或者后继结点,进行替换
前驱结点:对⼀棵⼆叉树进⾏中序遍历,遍历后的顺序,当前结点的前⼀个结点为该节点的前驱结点。左⼦树的最右结点
后继节点:对⼀棵⼆叉树进⾏中序遍历,遍历后的顺序,当前结点的后⼀个节点为该结点的后继结点。右⼦树的最左结点
示例:删除结点7
中序遍历:
2,4,5,7,8,9选择最近的
前驱结点5进行替换
红黑树的删除操作:
删除结点为红⾊,正常删除,执行
BST的删除逻辑即可删除结点为⿊⾊,⼦结点为红⾊,正常删除,执行
BST的删除逻辑即可删除结点为⿊⾊,⼦结点也为⿊⾊,先执行
BST的删除逻辑,再将当前位置的结点变为double black,缩写DB
5.1 思路分析
DB结点可以理解为一个占位结点,针对DB结点进行不同情况的后续处理。它的主要工作,就是将这个double black转换为single black
5.1.1 情况一
如果DB结点为根结点,或者DB结点的颜色为红色
- 将
DB节点变为正常节点,把颜⾊变为⿊⾊
5.1.2 情况二(A)
如果DB兄弟是⿊⾊的,并且DB兄弟孩⼦中的最远⼀个为红⾊
交换
DB⽗节点的颜⾊与DB兄弟的颜⾊DB⽗节点向DB⽅向进⾏旋转将
DB节点变为正常节点,把颜⾊变为⿊⾊将
DB兄弟离DB最远的红⾊孩⼦节点变为⿊⾊
5.1.3 情况二(B)
如果兄弟是⿊⾊的,并且兄弟孩⼦中的最远⼀个为⿊⾊,最近的为红⾊
交换
DB兄弟节点和DB兄弟节点中红⾊孩⼦节点的颜⾊DB兄弟节点向DB反⽅向进⾏旋转此时
DB兄弟孩⼦中的最远⼀个为红⾊,按情况一(A)进行处理
5.1.4 情况二(C)
如果兄弟是⿊⾊的,并且兄弟孩⼦节点颜⾊为⿊⾊,DB⽗节点是红⾊
将
DB节点变为正常节点,把颜⾊变为⿊⾊将
DB的兄弟节点变为红⾊DB⽗节点是红⾊,将其变为⿊⾊
5.1.5 情况二(D)
如果兄弟是⿊⾊的,并且兄弟孩⼦节点颜⾊为⿊⾊,DB⽗节点是⿊⾊
将
DB节点变为正常节点,把颜⾊变为⿊⾊将
DB的兄弟节点变为红⾊如果
DB的⽗节点是⿊⾊,将其变为DB节点,继续进行祖⽗节点、兄弟结点、兄弟孩子结点的综合判断。满⾜哪种情况,进行相应的后续处理
5.1.6 情况三
如果DB兄弟是红⾊节点
交换
DB⽗节点与DB兄弟节点颜⾊DB⽗节点向DB⽅向旋转观察此时满⾜哪种情况,进行相应的后续处理
5.2 代码实现
5.2.1 获取兄远 & 兄近
class RBTree<Element: Comparable> {fileprivate class RBNode: Equatable {// 获取兄弟孩⼦中的最远⼀个var siblingChildFar: RBNode? {if(self.sibling!.isNilLeaf) {return nil;}if(self.isLeftChild) {return self.sibling!.rightChild;}return self.sibling!.leftChild;}// 获取兄弟孩⼦中的最近⼀个var siblingChildNear: RBNode? {if(self.sibling!.isNilLeaf) {return nil;}if(self.isLeftChild) {return self.sibling!.leftChild;}return self.sibling!.rightChild;}}}
5.2.2 查找元素
class RBTree<Element: Comparable> {// 找到指定元素所对应的结点fileprivate func search(data: Element) -> RBNode? {if(_root == nil) {return nil;}var node = _root!;while(!node.isNilLeaf) {if(node.data! == data) {return node;}if(data > node.data!) {node = node.rightChild!;continue;}node = node.leftChild!;}return nil;}}
5.2.3 删除元素
class RBTree<Element: Comparable> {// 删除元素func delete(data: Element) {// 寻找待删除元素let node = search(data: data);// 未找到if(node == nil) {// 直接返回return;}// 删除结点var delNode: RBNode?;// DB结点var dbNode: RBNode?;// 如果待删除结点的左、右孩子都是NIL结点,或者其中一边为NIL结点if(node!.leftChild!.isNilLeaf || node!.rightChild!.isNilLeaf) {// 当前为根结点,并且左、右孩子都是NIL结点if(node!.isRoot && node!.leftChild!.isNilLeaf && node!.rightChild!.isNilLeaf) {// 直接将根结点设置为空_root = nil;// 直接返回return;}// 寻找目标结点,如果左孩子是NIL结点,目标结点选择右孩子,否则选择左孩子let target = node!.leftChild!.isNilLeaf ? node!.rightChild! : node!.leftChild!;// 如果当前结点为根结点if(node!.isRoot) {// 将根结点赋值为目标结点_root = target;// 将目标结点的父节点,设置为空target.parent = nil;}else {// 获取待删除结点的父节点let parent = node!.parent!;// 判断当前结点为左孩子if(node!.isLeftChild) {// 将父节点的左孩子,赋值为目标结点parent.leftChild = target;}else {// 将父节点的右孩子,赋值为目标结点parent.rightChild = target;}// 将目标结点的父节点,设置为待删除结点的父节点target.parent = parent;}// 删除结点为传入结点delNode = node!;// DB结点为目标结点dbNode = target;}// 否则,左、右孩子都非NIL结点else {// 获取待删除结点的左子树var target = node!.leftChild!;// 找到直接前驱结点while(!target.rightChild!.isNilLeaf) {target = target.rightChild!;}// 将待删除结点与直接前驱结点的值进行替换node!.data = target.data;// 获取直接前驱结点的左孩子let targetLeft = target.leftChild!;// 如果直接前驱结点的父节点就是待删除结点if(target.parent == node) {// 将待删除结点的左孩子,赋值为直接前驱结点的左孩子node!.leftChild = targetLeft;// 将直接前驱结点左孩子的父节点,赋值为待删除结点targetLeft.parent = node;}else {// 获取直接前驱结点的父节点let targetParent = target.parent!;// 将父节点的右孩子,赋值为直接前驱结点的左孩子targetParent.rightChild = targetLeft;// 将直接前驱结点左孩子的父节点,赋值为父节点targetLeft.parent = targetParent;}// 删除结点为直接前驱结点delNode = target;// DB结点为直接前驱结点的左孩子dbNode = targetLeft;}// 删除结点为红⾊,正常删除,执行BST的删除逻辑即可if(delNode!.color == RBColor.Red) {return;}// 删除结点为⿊⾊,⼦结点为红⾊,正常删除,执行BST的删除逻辑即可if(delNode!.leftChild!.color == RBColor.Red && delNode!.rightChild!.color == RBColor.Red) {return;}// 删除结点为⿊⾊,⼦结点也为⿊⾊,先执行BST的删除逻辑,再将当前位置的结点变为double black,缩写DBdeleteFixup(dbNode: dbNode!);}}
5.2.4 结点修复
class RBTree<Element: Comparable> {// 修复fileprivate func deleteFixup(dbNode: RBNode) {// 情况一:如果DB结点为根结点,或者DB结点的颜色为红色if(dbNode.isRoot || dbNode.color == RBColor.Red) {// 将DB节点变为正常节点,把颜⾊变为⿊⾊dbNode.color = RBColor.Black;return;}if(!dbNode.isExistParent || !dbNode.isExistSibling){return;}// 情况三:如果DB兄弟是红⾊节点if(dbNode.sibling!.color == RBColor.Red) {// 交换DB⽗节点与DB兄弟节点颜⾊let tempColor = dbNode.parent!.color;dbNode.parent!.color = dbNode.sibling!.color;dbNode.sibling!.color = tempColor;// 获取父节点let parent = dbNode.parent!;// DB⽗节点向DB⽅向旋转if(dbNode.isLeftChild) {rotating(node: parent, rbRotating: RBRotating.Left);}else {rotating(node: parent, rbRotating: RBRotating.Right);}// 观察此时满⾜哪种情况,进行相应的后续处理deleteFixup(dbNode: dbNode);return;}if(dbNode.sibling!.isNilLeaf) {return;}// 情况二(A):如果DB兄弟是⿊⾊的,并且DB兄弟孩⼦中的最远⼀个为红⾊if(dbNode.siblingChildFar!.color == RBColor.Red) {// 交换DB⽗节点的颜⾊与DB兄弟的颜⾊let tempColor = dbNode.parent!.color;dbNode.parent!.color = dbNode.sibling!.color;dbNode.sibling!.color = tempColor;// 将DB节点变为正常节点,把颜⾊变为⿊⾊dbNode.color = RBColor.Black;// 将DB兄弟离DB最远的红⾊孩⼦节点变为⿊⾊dbNode.siblingChildFar!.color = RBColor.Black;// 获取父节点let parent = dbNode.parent!;// DB⽗节点向DB⽅向进⾏旋转if(dbNode.isLeftChild) {rotating(node: parent, rbRotating: RBRotating.Left);}else {rotating(node: parent, rbRotating: RBRotating.Right);}return;}// 情况二(B):如果兄弟是⿊⾊的,并且兄弟孩⼦中的最远⼀个为⿊⾊,最近的为红⾊if(dbNode.siblingChildNear!.color == RBColor.Red) {// 交换DB兄弟节点和DB兄弟节点中红⾊孩⼦节点的颜⾊let tempColor = dbNode.sibling!.color;dbNode.sibling!.color = dbNode.siblingChildNear!.color;dbNode.siblingChildNear!.color = tempColor;// 获取兄弟结点let sibling = dbNode.sibling!;// DB兄弟节点向DB反⽅向进⾏旋转if(dbNode.isLeftChild) {rotating(node: sibling, rbRotating: RBRotating.Right);}else {rotating(node: sibling, rbRotating: RBRotating.Left);}// 此时DB兄弟孩⼦中的最远⼀个为红⾊,按情况一(A)进行处理deleteFixup(dbNode: dbNode);return;}// 情况二(C):如果兄弟是⿊⾊的,并且兄弟孩⼦节点颜⾊为⿊⾊,DB⽗节点是红⾊if(dbNode.parent!.color == RBColor.Red) {// 将DB节点变为正常节点,把颜⾊变为⿊⾊dbNode.color = RBColor.Black;// 将DB的兄弟节点变为红⾊dbNode.sibling!.color = RBColor.Red;// DB⽗节点是红⾊,将其变为⿊⾊dbNode.parent!.color = RBColor.Black;return;}// 情况二(D):如果兄弟是⿊⾊的,并且兄弟孩⼦节点颜⾊为⿊⾊,DB⽗节点是⿊⾊// 将DB节点变为正常节点,把颜⾊变为⿊⾊dbNode.color = RBColor.Black;// 将DB的兄弟节点变为红⾊dbNode.sibling!.color = RBColor.Red;// 如果DB的⽗节点是⿊⾊,将其变为DB节点,继续进行祖⽗节点、兄弟结点、兄弟孩子结点的综合判断。满⾜哪种情况,进行相应的后续处理deleteFixup(dbNode: dbNode.parent!);}}
总结
概述:
红⿊树(
R-B Tree)是由Rudolf Bayer在1972发明。是⼀种⾃平衡⼆叉搜索树,其中每个结点都有⼀个额外的位,存储该结点的颜⾊(红⾊或⿊⾊)是
2-3-4树的变种,2-3-4树的每一个结点都对应红黑树的一种结构,所以每一棵2-3-4树也都对应一棵红黑树
结点插入:
标准⼆叉搜索树,搜索找到要添加的位置
执⾏替换,颜⾊设置为红⾊
新结点添加叶⼦结点(
NIL结点),颜⾊设置为⿊⾊如果该⽗结点也为红⾊,需要修复
如何修复取决于⽗结点的
sibling(兄弟结点)
结点删除:
删除结点为红⾊,正常删除,执行
BST的删除逻辑即可删除结点为⿊⾊,⼦结点为红⾊,正常删除,执行
BST的删除逻辑即可删除结点为⿊⾊,⼦结点也为⿊⾊,先执行
BST的删除逻辑,再将当前位置的结点变为double black,缩写DBDB结点可以理解为一个占位结点,针对DB结点进行不同情况的后续处理。它的主要工作,就是将这个double black转换为single black
学习资料:
数据结构可视化:https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
Swift算法俱乐部:https://aquarchitect.github.io/swift-algorithm-club/
