△Hollis, 一个对Coding有着独特追求的人△

这是Hollis的第 244篇原创分享

作者 l shanhm1991

来源 l Hollis(ID:hollischuang)

红黑树是一种很经典的数据结构,它可以在O(log n)时间内做查找,插入和删除。所以倍受关注。但是一直以来很多Java程序员对他都不是很重视,直到在JDK 1.8中,HashMap会将其链表转换成红黑树,此后,很多人就开始重新学习红黑树的有关知识。

作者在学习红黑树时,查阅了很多资料都没有找到解释的特别清楚的,于是自己总结了这一篇文章,总字数近7k,而且绘制了很多图,希望可以让大家更容易理解。

1. 定义

红黑树是Avl树的一个变种,它也是在二叉查找树的基础上添加平衡条件,只是它对平衡条件的描述不像AVl树那样直接,而是转化成对节点颜色规则的描述。

颜色规则:

  1. 对于任意节点,要么是红色,要么是黑色;

  2. 根节点是黑色的;

  3. 如果一个节点是红色的,那么它的子节点必须是黑色的(即不能有两个连续的红色节点);

  4. 任意节点到其下面各个空结点(后面称为nil节点,并约定其颜色为黑色)的路径上都包含相同数目的黑色节点(称为黑高);

通过对任何一条从根到空节点的路径上各个结点的颜色进行约束,红黑树可以确保没有一条路径会比其他路径长出2倍,因而红黑树是近似平衡的。

2. 分析实现

为了便于处理红黑树代码中的边界条件,使用两个标记节点header和nil来充当哨兵,它们与树中的普通节点有相同的属性,颜色设为黑色。

将header的值设为负无穷,因此它的右子节点就是真正的根,在对树进行遍历时我们可以用header作为起点。

nil的值可以为任意值,将它作为所有空节点的统一引用, 即让所有树叶的左右子节点都指向nil,那么在遍历时就可以将nil视为结束的标志。

红黑树也是一颗查找二叉树,所以它的基本操作与查找二叉树一致,区别也在于插入和删除后的平衡恢复。只是实现平衡时的目标就是满足它对颜色规则的定义,至于树的平衡与否已经由规则本身去保证。

2.1. 插入

与二叉查找树一样,插入节点其实就是添加一个树叶,主要问题是在添加结束时可能要做一些调整来保证红黑树的规则不被破坏。

不过,根据规则4, 可以知道,在插入之前任意节点到其下面各个nil的路径上都包含相同数目的黑节点数,如果能保证在插入节点前后父节点parent或者祖父节点grand到其下面nil的路径上的黑节点数不变, 即保证parent或者grand的黑高不变,那么就可以保证红黑树的规则不被破坏,这样就可以将平衡操作时需要考虑的范围缩小到grand。

由于插入红色节点不会影响路径上的黑节点数,因此就先将待插入的节点涂成红色,如果parent是黑色,那么插入直接完成。如果parent是红色,那么插入之后将违反规则3, 这时再做一些调整。

先分析一下插入之前的可能情形,根据规则3,如果parent是红色,那么可以断定grand以及parent的另一个子节点一定是黑色。不仅如此,因为插入的是树叶节点在之前一定是nil, 那么parent的另一个子节点必然也是nil,否则插入之前parent就违反了规则4。

再看parent的兄弟节点,如果它是黑色的,那么它一定也是nil节点;如果它是红色的,那么它的两个子节点一定都是nil节点,否则插入之前grand就违反了规则4。

所以,如果parent是红色,那么我们可以完整地画出插入之前的所有可能情况,一共有4种,其中待插入节点的位置就是图中parent下面的两个nil之一。

可以看出,1和2,3和4分别是对称的两种情况,区别在于parent与grand的大小关系,其实可以概括为parent的兄弟节点uncle是黑色还是红色的两种情况。

1. 如果parent为红色,uncle为黑色

以情况1为例(即parent小于grand):如果current小于parent,那么插入之后,可以将parent和grand的颜色互换,然后将grand右旋,这样就能保证原来以grand为根的这个子树的黑高保持不变,也就保证了整棵树不被破坏。

如果current大于parent,则可以先将parent左旋,然后将current视为parent,再与上面进行相同的操作,即可恢复平衡。

2. 如果parent为红色,uncle为红色

这时无论怎么旋转都已经解决不了问题,不过可以知道,在插入之前grand到下面各个nil的黑节点数为1,因此,只要保证grand的黑高仍然为1就行, 那么,通过将grand与其左右子节点的颜色进行翻转便可达到目标。
以情况3为例:

但是,翻转会使grand变成红色,这时如果曾祖父也是红色,那么将违反了规则3。因此可能需要朝着树根的方向进行逐层分析,直到不再有两个相连的红色节点或者到达树根为止,但这显然会很麻烦。

事实上,由于颜色翻转的等价性,我们可以在查找插入节点位置的过程中,通过颜色翻转来避免掉uncle为红色的情况,即将颜色翻转的事情提前做了。

在自上而下的查找过程中,如果看到一个节点X的两个子节点都为红色,可以使X变成红色,而两个子节点变成黑色(如果X是根,则可以在翻转之后再将其置为黑色),结果依然满足红黑树的规则。

只有当X的parent为红色时,才会破坏红黑树的规则。不过这时可以断定X的兄弟节点,X的grand,以及X的uncle都是黑色的(因为如果X的parent和uncle都为红色, 那么一定会在自上而下的查找和翻转过程中被过滤掉)。

那么,可以将X视为current,然后与上面parent为红色,uncle为黑色的情形进行相同的处理。下面通过画图来说明一种情形(对称情形类似处理):
先对X与其子节点进行颜色翻转

然后,将X视为之前的current,对parent和grand进行换色和旋转

完成之后,将新的根(即原本grand的位置)视为新的current,然后重新向下遍历。由于X的孙子节点之前一定为黑色,而X的儿子节点在翻转之后也为黑色,因此在调整之后, 可以保证在X的下面将有连续两层不会出现红节点。

所以,尽管旋转调整会使得gand的位置变得错误,但在接下来的遍历中,又会立即将grand-parent-current恢复成正确的位置关系。

总结节点插入问题:首先根据二叉查找树的性质,可以知道插入节点就是添加树叶,其次对于红黑树而言,插入红色节点不会影响路径上的黑节点数。

因此, 我们将插入的节点视为红色的树叶,这时只要考虑插入的红色树叶的parent是红色还是黑色,如果是黑色问题直接解决,如果是红色,再看uncle是红色还是黑色。如果uncle是黑色,可以通过变色旋转解决;如果是红色,那么通过颜色翻转也能达到目的,但这会使grand变成红色,需要进一步考虑曾祖父的颜色问题,将问题放大了。于是考虑在一开始向下的遍历过程中, 提前翻转颜色来避免掉parent和unlce同时为红色的情况。

因此,最终在插入节点时,实际需要考虑旋转调整的情形只有一种,即parent为红色,uncle为黑色。

2.2. 删除

对于节点删除,大概思路也是先将问题转化成删除树叶来简化问题,如果是红色树叶,则可以直接删除,如果是黑色树叶,则再分情形进行讨论。

先看下如何将删除目标转化成对树叶的删除,根据二叉查找树的性质,在删除节点时可以用右子树的最小节点或左子树的最大节点来替换要删除的节点,然后删除子树中用来替换的那个节点。

不过在红黑树中,为了保证节点的颜色规则,替换时将只进行值的替换。其实,根据红黑树的规则可以得出一个简单的推论:如果一个树叶没有兄弟节点,那么它一定为红色,或者说如果一个树叶为黑色, 则它必然有兄弟节点。

另外,还可以断定:对于任意节点,其右子树的最小(即最左)节点最多存在一个红色右子节点,其左子树的最大(即最右)节点最多存在一个红色左子节点。因此,当左子树的最大节点或右子树的最小节点不是树叶时,可以继续用同样的思路来转换要删除的目标,并且这时可以断定最后删除的树叶一定是红色。

再来讨论删除黑色树叶的情形,由于黑色树叶必然会存在兄弟节点(树根除外),因此在下面的讨论中首先假设要删除的黑色树叶总是在左边(如果在右边则对称处理), 这时可以根据其兄弟节点brother和父亲节点parent的颜色来将删除情形分为3种。下面分别进行画图分析(图中省略掉空节点nil):

1.parent为红色,brother为黑色
由于current是树叶,那么可以断定brother下面不会再有黑色的后代节点,至多只能有红色的儿子节点,否则parent在删除前将违反规则4。

1.1 先看brother没有儿子的情况,此时看上去比较简单,直接对parent进行左旋即可解决问题,能够保证在删除调整前后以原parent为根的子树的黑高不变。

1.2 但是,如果brother有一个红色左儿子(右儿子没有影响,因此忽略),那么直接对parent进行左旋,将会造成两个连续的红色的节点而违反规则3, 这时需要先将brother右旋,然后再对parent进行换色并左旋来解决。

因此,需要根据brother是否有红色左儿子将情形1再分为2种子情形来进行处理。

2.parent为黑色,brother为红色
同样由于current是树叶,可以断定brother的两个黑色的儿子节点下面至多只能有一层红色子节点,下面根据brother左儿子的子节点情况(右儿子的子节点没有影响,因此忽略)来分情形讨论。

2.1 还是先看brother的左儿子没有子节点的情况,这时可以将brother与其左儿子进行换色,然后将parent进行左旋。

2.2 如果brother的左儿子有一个红色右子节点,这时需要先将brother左儿子的右子节点涂成黑色,并将brother右旋,然后再将parent左旋。

2.3 如果brother的左儿子没有红色右子节点,但有一个红色左子节点。这时可以先将brother左儿子的左子节点涂成黑色,并将brother的左儿子右旋,这样将情形转化成2.2, 然后依次将brother右旋,parent左旋。

因此,需要根据brother左儿子的子节点情况将情形2再分为3种子情形来处理。

3.parent为黑色,brother为黑色
还是由于current是树叶,可以断定brother下面至多只能有一层红色的儿子节点,否则parent在删除前就违反了规则4。

3.1 还是先看brother没有儿子的情况,此时通过parent为根的子树自身已经解决不了问题,想法是将brother变成红色来降低parent子树的黑高,然后将parent子树整个视为一个current进行处理, 下面会再详细说明。

3.2 如果brother有一个红色右儿子,这时可以将brother的右儿子变成黑色,然后将parent左旋。

3.3 如果brother没有红色右儿子,但是存在一个红色左儿子,这时可以将brother的左儿子变成黑色,然后将brother右旋,再将parent左旋。

因此,需要根据brother的子节点情况将情形3再分为3种子情形来处理。

小结一下,上面一共将删除了黑色树叶的情形分成了8种,除了3.1,其它都可以在parent子树内部解决问题。为了进一步分析3.1的问题,需要扩展下思维, 可以想象一下,将current是树叶这个前提去掉,然后将current视为根为黑色的子树。假设这时删除的黑色树叶落在current子树下面, 并且遇到了情形3.1,那么可以看成将current子树整体的黑高降1,然后再根据brother子树和parent的情况来进行处理。

如果依然解决不了问题, 那么可以将parent树视为新的current子树, 再将祖父节点视为新的parent,这样逐层向上分析,直到能够在新的parent为根的树内部解决问题或者到达树根为止。

下面通过画图再对上面的情形进行一遍说明,对于空节点nil可以使用黑色节点代替。另外,这时有一个前提假设:即在删除之前current子树的黑高一定大于1, 那么brother子树的黑高必然也至少为2,也就是说brother到nil的路径上必然还有黑色节点。

为了更好的说明,图中用x标出树根, 并用a,b,c,d,e,f,g,h标出树根下面的一些相对位置(可以将这些位置看成nil,只是图中做了省略,没有画出current和brother到这些nil的完整路径)。所有图的最终目标的都是保证:如果current子树的黑高降1,那么通过调整保证x到a,b,c,d,e,f,g,h这些位置的相对黑高不变。

对于1.1,如果current子树黑高降1,与之前一样,直接将parent左旋,可以保证在调整前后,x到a,b,c,d,e的相对黑高保持不变,如图:

对于1.2,如果current子树黑高降1,与之前一样,先将brother右旋,然后将parent变成黑色并左旋,也能保证x到a,b,c,d,e的相对黑高保持不变,如图:

对于2.1,如果current子树黑高降1,与之前一样,将brother与左儿子换色,然后对parent左旋,可以保证x到a,b,c,d,e,f,g,h的相对黑高保持不变,如图:

对于2.2,如果current子树黑高降1,与之前一样,将brother的左儿子的右子节点涂成黑色,然后对brother右旋,再对parent左旋,也可以保证x到a,b,c,d,e,f,g,h的相对黑高保持不变, 如图(A涂成灰色表示其颜色可以红或者黑,没有影响):

对于2.3,如果current子树黑高降1,与之前一样,先将brother的左儿子的左子节点涂成黑色,并对将brother的左儿子右旋,这样将情形转化成2.2,然后再进行同样的旋转调整, 以保证x到a,b,c,d,e,f,g,h的相对黑高保持不变,如图:

对于3.1,如果current子树黑高降1,与之前一样,依然只能降低brother的黑高,使x到a,b,c,d,e,f的相对黑高同时降1,然后将parent视为新的current子树继续向树根追溯,如图:

对于3.2,如果current子树黑高降1,与之前一样,将brother的右儿子涂黑,然后对parent左旋,也能保证x到a,b,c,d,e,f的相对黑高保持不变, 如图(图中将left涂成灰色表示其颜色可以红或者黑,没有影响):

对于3.3,如果current子树黑高降1,与之前一样,先将brother的左儿子涂黑,然后对parent右旋,再对parent左旋,也能保证x到a,b,c,d,e,f的相对黑高保持不变,如图:

总结节点删除问题:首先将问题转化为对树叶的删除,如果树叶是红色则直接删除,如果是黑色,则根据兄弟节点和父节点的颜色情况来对情形进行分类。想法是尽量在子树中解决, 如果实在解决不了,就降低子树的高度,并将子树视为整体然后向树根进行追溯,直到问题解决。

上面的分析中,对于删除黑色树叶的删除,分成了8种情形,其中3.1需要向树根追溯, 直到转化成其它7种情形或到树根为止才能解决。其次,2.3也需要转化成2.2来解决,因此,对于黑色树叶的删除,实际上最后直接解决的情形可以归结有6种。

3. java实现

继承之前二叉查找树的实现,重写下插入和删除,分别实现SetMultiTreeMap

3.1. Set

public class RBTreeSet<E extends Comparable<? super E>> extends BinarySearchTreeSet<E> {private RBNode<E> header;private RBNode<E> nil;public RBTreeSet() {nil = new RBNode<>(null);nil.left = nil;nil.right = nil;header = new RBNode<>(null, nil, nil);}@Overrideprotected Node<E> getRoot() {return header.right;}@Overrideprotected boolean isEmptyNode(Node<E> node) {return nil == node;}@Overridepublic void clear() {header.right = nil;size = 0;}@Overridepublic boolean add(E e) {RBNode<E> current = header;RBNode<E> parent = header;//先将nil的值设为enil.element = e;//然后从header开始自上而下寻找值为e的节点awhile(compare(e, current) != 0){parent = current;if(compare(e, current) < 0){current = getLeft(current);}else{current = getRight(current);}//如果左右子节点都为红色,则进行颜色翻转,避免插入时出现父亲的兄弟节点为红色的情况if(getLeft(current).red && getRight(current).red){current = handleReorient(current);}}//如果发现了值相同的重复节点,直接覆盖if(current != nil){current.element = e;return true;}//新建一个数据节点代替nil,并将其左右子树指向nilcurrent = new RBNode<>(e, nil, nil);size++;//当while结束时,必然可以明确待插入节点的parent节点,这里使parent节点链接到新节点current.parent = parent;if(compare(e, parent) < 0){parent.left = current;}else{parent.right = current;}//将新数据节点设成红色,如果父亲节点为红色则需要旋转调整handleReorient(current);return true;}private RBNode<E> getLeft(Node<E> node){return (RBNode<E>)node.left;}private RBNode<E> getRight(Node<E> node){return (RBNode<E>)node.right;}private int compare(E e, Node<E> node) {if(node == header){return 1;}else{return e.compareTo(node.element);}}private RBNode<E> handleReorient(RBNode<E> current) {getLeft(current).red = false;  getRight(current).red = false; current.red = true;RBNode<E> subRoot = current;//翻转之后发现parent也是红色,进行换色和旋转RBNode<E> parent = current.parent;if(parent.red){RBNode<E> grand = parent.parent;grand.red = true;//旋转parent, 向外旋转到同一侧 if(compare(current.element, grand) != compare(current.element, parent)) {if(compare(current.element, parent) > 0){rotateLeft(parent);}else{rotateRight(parent);}}//旋转grandif(compare(current.element, grand) < 0){subRoot = getLeft(grand);subRoot.red = false;rotateRight(grand);}else{subRoot = getRight(grand);subRoot.red = false;rotateLeft(grand);}}//直接将根节点置为黑色 getRight(header).red = false;return subRoot;}private void rotateRight(RBNode<E> node) {RBNode<E> parent = node.parent;RBNode<E> left = getLeft(node); RBNode<E> leftRight = getRight(left);left.right = node;node.parent = left;node.left = leftRight;leftRight.parent = node;left.parent = parent;if(parent.right == node){parent.right = left;}else{parent.left = left;}}private void rotateLeft(RBNode<E> node) {RBNode<E> parent = node.parent;RBNode<E> right = getRight(node); RBNode<E> rightLeft = getLeft(right);right.left = node;node.parent = right;node.right = rightLeft;rightLeft.parent = node;right.parent = parent;if(parent.right == node){parent.right = right;}else{parent.left = right;}}@Overridepublic boolean remove(Object o) {RBNode<E> current = header;RBNode<E> parent = header;@SuppressWarnings("unchecked")E e = (E)o;nil.element = e;while(compare(e, current) != 0){parent = current;if(compare(e, current) < 0){current = getLeft(current);}else{current = getRight(current);}}if(current == nil){ //没有找到值为e的数据节点return true;}size--;RBNode<E> node = current;if(current.right != nil){ //替换右子树最小节点parent = current;current = getRight(current);while(current.left != nil){parent = current;current = getLeft(current);}node.element = current.element;//右侧最小节点不是叶子,则继续向下遍历一层,这时可以断定树叶是红色if(current.right != nil){parent = current;current = getRight(current);parent.element = current.element;parent.right = nil;return true;}}else if(current.left != nil){ //替换左子树最大节点parent = current;current = getLeft(node);while(current.right != nil){parent = current;current = getRight(current);}node.element = current.element;//左侧最大节点不是叶子,则继续向下遍历一层,这时可以断定树叶是红色if(current.left != nil){parent = current;current = getLeft(current);parent.element = current.element;parent.left = nil;return true;}}//先调整再删除,因为后面还需要依赖current与parent的位置关系判断if(!current.red){fixRemove(current);}//删除树叶leafif(current == parent.left){parent.left = nil;}else{parent.right = nil;}return true;}private void fixRemove(RBNode<E> current){RBNode<E> parent = current.parent;if(parent == header){return;}if(current == parent.left){RBNode<E> brother = getRight(parent);if(parent.red){ // 1.parent为红色if(getLeft(brother).red){ parent.red = false;rotateRight(brother);}}else if(brother.red){ // 2.brother为红色if(!getLeft(brother.left).red && !getRight(brother.left).red){brother.red = false;getLeft(brother).red = true;}else if(getRight(brother.left).red){getRight(brother.left).red = false;rotateRight(brother);}else{getLeft(brother.left).red = false;rotateRight(getLeft(brother));rotateRight(brother);}}else{ // 3. parent和brother都为黑色if(!getLeft(brother).red && !getRight(brother).red){ brother.red = true;fixRemove(parent);return;}else if(getRight(brother).red){getRight(brother).red = false;}else{getLeft(brother).red = false;rotateRight(brother);}}//最后一步的调整都是parent左旋rotateLeft(parent);}else{ // 对称情形RBNode<E> brother = getLeft(parent);   if(parent.red){ if(getRight(brother).red){ parent.red = false;rotateLeft(brother);}}else if(brother.red){    if(!getLeft(brother.right).red && !getRight(brother.right).red){brother.red = false;getRight(brother).red = true;}else if(getLeft(brother.right).red){getLeft(brother.right).red = false;rotateLeft(brother);}else{getRight(brother.right).red = false;rotateLeft(getRight(brother));rotateLeft(brother);}}else{ if(!getLeft(brother).red && !getRight(brother).red){brother.red = true;fixRemove(parent);return;}else if(getLeft(brother).red){getLeft(brother).red = false;}else{getRight(brother).red = false;rotateLeft(brother);}}rotateRight(parent);}}static class RBNode<E> extends Node<E> {RBNode<E> parent;boolean red;RBNode(E e) {super(e);}RBNode(E e, RBNode<E> left, RBNode<E> right) {super(e);this.left = left;this.right = right;}@Overridepublic String toString() {return red ? element.toString() + "(red)" : element.toString();}}
}

3.2. MultiTreeMap

public class MultiRBTreeMap<K extends Comparable<? super K>, V> extends MultiBinarySearchTreeMap<K, V> {public MultiRBTreeMap() {}public MultiRBTreeMap(boolean deduplication, boolean asc){
super(deduplication, asc);}@Overrideprotected void init() {
nil = new RBNode<K, V>(null, null, false, null, null);
nil.left = nil;
nil.right = nil;header = new RBNode<K, V>(null, null, false, nil, nil);header.next = nil;
nil.prev = header;}public V put(K key, V value){
nil.key = key;
RBNode<K, V> current = (RBNode<K, V>)header;
RBNode<K, V> parent = (RBNode<K, V>)header;
while(compare(key, current) != 0){parent = current;
if(compare(key, current) < 0){current = getLeft(current);}else{current = getRight(current);}
if(getLeft(current).isRed && getRight(current).isRed){current = handleReorient(current);}}if(current != nil){
if(deduplication){
V old = current.value;current.key = key;current.value = value;
return old;}else{
while((current = getNext(current)).isRepeat);
V old = current.prev.value;linkIn(new RBNode<>(key, value, true, nil, nil), current, false);
return old;}}current = new RBNode<>(key, value, false, nil, nil);current.parent = parent;
if(compare(key, parent) < 0){parent.left = current;linkIn(current, parent, false);}else{parent.right = current;
while((parent = getNext(parent)).isRepeat);linkIn(current, parent, false);}
//将新数据节点设成红色,如果父亲节点为红色则需要旋转调整handleReorient(current);
return null;}private RBNode<K, V> handleReorient(RBNode<K, V> current) {getLeft(current).isRed = false;  getRight(current).isRed = false; current.isRed = true;RBNode<K, V> subRoot = current;
RBNode<K, V> parent = getParent(current);
if(parent.isRed){
RBNode<K, V> grand = getParent(parent);grand.isRed = true;
//旋转parent, 向外旋转到同一侧
if(compare(current.getKey(), grand) != compare(current.getKey(), parent)) {
if(compare(current.getKey(), parent) > 0){rotateLeft(parent);}else{rotateRight(parent);}}
//旋转grand
if(compare(current.getKey(), grand) < 0){subRoot = getLeft(grand);subRoot.isRed = false;rotateRight(grand);}else{subRoot = getRight(grand);subRoot.isRed = false;rotateLeft(grand);}}
//直接将根节点置为黑色 getRight(header).isRed = false;
return subRoot;}private void rotateRight(Node<K, V> node) {
Node<K, V> parent = node.parent;
Node<K, V> left = node.left;
Node<K, V> leftRight = left.right;
left.right = node;node.parent = left;node.left = leftRight;leftRight.parent = node;
left.parent = parent;
if(parent.right == node){parent.right = left;}else{parent.left = left;}}private void rotateLeft(Node<K, V> node) {
Node<K, V> parent = node.parent;
Node<K, V> right = node.right;
Node<K, V> rightLeft = right.left;
right.left = node;node.parent = right;node.right = rightLeft;rightLeft.parent = node;
right.parent = parent;
if(parent.right == node){parent.right = right;}else{parent.left = right;}}private int compare(K key, Entry<K, V> node) {
if(node == header){
return 1;}else{
return key.compareTo(node.getKey());}}private RBNode<K, V> getParent(Node<K, V> node){
return (RBNode<K, V>)node.parent;}private RBNode<K, V> getNext(Node<K, V> node){
return (RBNode<K, V>)node.next;}private RBNode<K, V> getLeft(Node<K, V> node){
return (RBNode<K, V>)node.left;}private RBNode<K, V> getRight(Node<K, V> node){
return (RBNode<K, V>)node.right;}@Overrideprotected void remove(Node<K, V> node){
RBNode<K, V> rbnode = (RBNode<K, V>)node;
RBNode<K, V> current = rbnode;
RBNode<K, V> parent = getParent(current);
if(current.right != nil){current = getRight(current);
while(current.left != nil){parent = current;current = getLeft(current);}locationExchange(rbnode, current);
if(rbnode.right != nil){//node.right is redparent = rbnode;rbnode = getRight(rbnode);locationExchange(parent, rbnode);rbnode.right = nil; //交换过位置
return;}}else if(current.left != nil){current = getLeft(current);
while(current.right != nil){parent = current;current = getRight(current);}locationExchange(rbnode, current);
if(rbnode.left != nil){//node.left is redparent = rbnode;rbnode = getLeft(rbnode);locationExchange(parent, rbnode);rbnode.left = nil;
return;}}if(!rbnode.isRed){fixRemove(rbnode);}else if(rbnode == parent.left){parent.left = nil;}else{parent.right = nil;}}//这里不能简单的交换节点的值,因为要保证链表和树中删除的是同一个节点实例即node
private void locationExchange(RBNode<K, V> node, RBNode<K, V> current){
RBNode<K, V> parent = getParent(node);
RBNode<K, V> left = getLeft(node);
RBNode<K, V> right = getRight(node);boolean isRed = node.isRed;replaceChild(current.parent, current, node);node.left = current.left;node.left.parent = node;node.right = current.right;node.right.parent = node;node.isRed = current.isRed;replaceChild(parent, node, current);current.left = left;
left.parent = current;current.right = right;
right.parent = current;current.isRed = isRed;}private void fixRemove(RBNode<K, V> node){
RBNode<K, V> parent = getParent(node);
if(parent == header){
return;}if(node == parent.left){parent.left = nil;
RBNode<K, V> brother = getRight(parent);
if(parent.isRed){ // 1.parent为红色
if(getLeft(brother).isRed){ parent.isRed = false;rotateRight(brother);}}else if(brother.isRed){ // 2.brother为红色
if(!getLeft(brother.left).isRed && !getRight(brother.left).isRed){brother.isRed = false;getLeft(brother).isRed = true;}else if(getRight(brother.left).isRed){getRight(brother.left).isRed = false;rotateRight(brother);}else{getLeft(brother.left).isRed = false;rotateRight(brother.left);rotateRight(brother);}}else{ // 3. parent和brother都为黑色
if(!getLeft(brother).isRed && !getRight(brother).isRed){ brother.isRed = true;fixRemove(parent);
return;}else if(getRight(brother).isRed){getRight(brother).isRed = false;}else{getLeft(brother).isRed = false;rotateRight(brother);}}
//最后一步的调整都是parent左旋rotateLeft(parent);}else{ // 对称情形parent.right = nil;
RBNode<K, V> brother = getLeft(parent);
if(parent.isRed){
if(getRight(brother).isRed){ parent.isRed = false;rotateLeft(brother);}}else if(brother.isRed){
if(!getLeft(brother.right).isRed && !getRight(brother.right).isRed){brother.isRed = false;getRight(brother).isRed = true;}else if(getLeft(brother.right).isRed){getLeft(brother.right).isRed = false;rotateLeft(brother);}else{getRight(brother.right).isRed = false;rotateLeft(brother.right);rotateLeft(brother);}}else{
if(!getLeft(brother).isRed && !getRight(brother).isRed){brother.isRed = true;fixRemove(parent);
return;}else if(getLeft(brother).isRed){getLeft(brother).isRed = false;}else{getRight(brother).isRed = false;rotateLeft(brother);}}rotateRight(parent);}}static class RBNode<K, V> extends Node<K, V> {boolean isRed;public RBNode(K key, V value) {
super(key, value);}RBNode(K key, V value, boolean isRepeat, Node<K, V> left, Node<K, V> right) {
super(key, value, isRepeat, left, right);}@Override
public String toString() {
String str = super.toString();
return isRed ? str + "(red)" : str;}}
}

3.3. MultiTreeSet

有了Map之后,就可以很容易的依赖它实现一个性质相似的Set:

public class MultiRBTreeSet<E extends Comparable<? super E>> implements Set<E>{private static final Object V = new Object();private MultiRBTreeMap<E, Object> map;public MultiRBTreeSet(){map = new MultiRBTreeMap<>();}@Override
public int size() {
return map.size();}@Override
public boolean isEmpty() {
return map.isEmpty();}@Override
public boolean contains(Object o) {
return map.containsKey(o);}@Override
public Iterator<E> iterator() {
return map.keySet().iterator();}public Iterator<E> iterator(E from, boolean fromInclusive, E to, boolean toInclusive) {
return map.keySet(from, fromInclusive, to, toInclusive, false).iterator();}@Override
public boolean add(E e) {map.put(e, V);
return true;}@Override
public boolean remove(Object o) {map.removeList(o);
return true;}@Override
public boolean containsAll(Collection<?> c) {
return map.keySet().containsAll(c);}@Override
public void clear() {map.clear();}@Override
public boolean addAll(Collection<? extends E> collection) {
for(E e : collection){map.put(e, V);}
return true;}@Override
public boolean removeAll(Collection<?> collection) {
for(Object o : collection){map.removeList(o);}
return true;}@Override
public boolean retainAll(Collection<?> c) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException();}@Override
public Object[] toArray() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException();}@Override
public <T> T[] toArray(T[] a) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException();}
}

4. 红黑树的高度

根据规则4,任意节点到其下面各个空节点的黑高相等。那么,记根为x的红黑树的黑高为h(x),那么红黑树中至少包含2^h(x) - 1个黑色节点。

根据规则3,在高度为H的红黑树中,任何一条路径至多有一半的红色节点,即至少有一半的黑色节点数,也就是说黑高至少为H/2。

如果红黑树的节点数为N,那么有:N >= 2^(H/2) - 1, 即H <= 2lg(N+1)。

既然有了Avl树,为什么还要红黑树?

因为红黑树的综合性能要由于Avl树,主要原因树红黑树恢复平衡时需要的旋转次数比Avl树更少,这个网上有很多,但从之前对Avl树的讨论中可以看出,即便在最差的情形中, 最多也只要4次旋转便可恢复平衡。

因此,如果说Avl的旋转次数多不应该是指单次删除或者添加中发生的旋转次数,而是很多次插入删除后的一个统计值。另外在Avl树中,即便只需要一次旋转调整,也免不了要依次检查到根节点,这个过程是自下而上的,即从叶节点追溯到根节点,而插入和删除首先都是自上而下的。

因此在实现中如果不使用递归的方式将发生两次遍历过程,这也是之前介绍Avl树不实现MultiTreeMap的原因。而在红黑树中,则可以将调整问题缩小在父节点为根的子树中, 这些问题都可以解决,代价只是稍微牺牲了一些平衡性。

参阅:

  1. 《算法导论》

  2. 《数据结构与算法分析》

本文来自作者投稿,原作者:shanhm1991,他目前正在连载数据结构系列文章,原创不易,欢迎转发支持。

福利时间

GIFT TIME

相信大家一定看过一本关于Java虚拟机的书——《深入理解Java虚拟机》,这本书自2011年上市以来,前两个版本累计印刷36次,销量超过30万册。

最近刚刚出了第三版,我第一时间找到机械工业出版社给大家申请了一些,作为福利送给大家。

本次送书不需要评论,也不需要转发,采用最公平的抽奖方式。

Hollis创建了一个适合5 年以内的Java开发者的公众号,主要定位于深入讲解Java的原理知识——Java之道

大家可以扫描以上二维码,关注后在对话回复:JVM。即可参与抽奖。中奖者即可获得《深入理解Java虚拟机(第三版)》一本。开奖时间:2019-12-26 10:00

- MORE | 更多精彩文章 -

  • 一条来自面试官的忠告:一定要非常熟悉你负责的业务

  • 微服务必备的调用链分析,2w字长文解析技术细节!

  • CTO被裁,离职前给组了的高级开发们8个建议。

  • 删库不必跑路,谈数据库删除设计

如果你喜欢本文,

请长按二维码,关注 Hollis.

转发至朋友圈,是对我最大的支持。

好文章,我在看❤️

他一口气写出了这7k字的红黑树总结!看过的都说好!!|文末送书相关推荐

  1. 【文末送书】豆瓣9.3,这部神作终于出新版了!

    不久前,自称"业余up主"的AI大神李沐开源了一个剪辑神器 ,再圈一波技术粉! 图1:该项目在github上的页面展示 事实上,这位up主可并不业余,反而是妥妥的AI界卓越先驱.对 ...

  2. Python自动化操作Word15000字超强总结,帮你解放双手!(文末送书 )

    作者:超级大洋葱806 https://tangxing.blog.csdn.net/article/details/108418066 大家好,我是小z 老有同学问关于自动化办公的内容,今天就给大家 ...

  3. 2019全球PostgreSQL生态报告出炉,PG为何从RDBMS中脱颖而出?丨文末送书

    墨墨导读:本文是近期ScaleGrid发布的2019 PG趋势报告,从不同的角度解读了PostgreSQL如何在众多优秀的RDBMS中脱颖而出,原文:https://scalegrid.io/blog ...

  4. 学弟啊,你代码能不能不要写得这么烂?!--文末送书

    点击上方[全栈开发者社区]→右上角[...]→[设为星标⭐] 作者:沙漠之鹰 www.cnblogs.com/buptzym/archive/2013/05/25/3099389.html 今日封面图 ...

  5. Android 系统到底提供了哪些东西,供我们可以开发出优秀的应用程序(文末送书)

    Android 系统到底提供了哪些东西,供我们可以开发出优秀的应用程序. 作者:坚果 公众号:"大前端之旅" 华为云享专家,InfoQ签约作者,阿里云专家博主,51CTO博客首席体 ...

  6. 俩人同样写代码,我拿七千他咋五万八...(文末送书)

    敲黑板!本次民工哥技术之路公众号联合机械工业华章图书为大家带来赠书福利(Java核心技术卷II),拼手气的时候到了,看看谁才是锦鲤? 点击下方名片回复关键字 Java核心技术 参与抽奖 规则: 1.民 ...

  7. php教程7天速成,练字速成法一周写出一手漂亮的字!【7天练字速成】

    练字速成法有吗?成年人练字速成法到底有没有?目前很多的朋友都希望能够快速的写出一手漂亮的字,目前要步入社会的大学生们.以及已经步入社会中的工作者对于写字的美观比较重视,因为大家都深知能够写出一手好字绝 ...

  8. 为什么PRD写的Low?你看图文、支付、数据、登录、按钮……藏了多少猫腻!(文末送44页淘宝PRD)...

    ‍ ‍ 全文3.1千字 文末送44页淘宝PRD实例 产品界面设计往往都是所见即所有,比葫芦画瓢即可. 但是产品经理很快就把原型画出完了,却在UI设计或开发的时候就常常被拉去澄清细节. "甩手 ...

  9. 2021全球程序员收入报告出炉!字节高级码农年薪274万元排第5【文末送5本书】...

      视学算法报道   编辑:桃子 David 文末包邮送5本价值百元的高质量机器学习技术书籍 [新智元导读]2021年的余额不多了,你的「余额」还有多少?近日,Levels.fyi发布了2021全球程 ...

最新文章

  1. python中import os_python import os
  2. Silverlight与WPF中BeginInvoke的差异
  3. Oracle优化02-锁和阻塞
  4. javascript技术教程蔡敏_程序员都必掌握的前端教程之JavaScript基础教程(上)
  5. Redis源码剖析(九)对象系统概述
  6. 2018年中国研究生数学建模竞赛--经验贴
  7. 计算机学英文是什么,“计算机科学与技术专业本科课程”的英文翻译是什么?...
  8. google浏览器 隐藏功能开启
  9. 计算payload长度c语言,C语言0长度数组(可变数组/柔性数组)详解
  10. Mysql 添加用户 授权等操作
  11. C++ 杂七杂八的东西
  12. Android Camera数据流分析全程记录(overlay方式二)
  13. 基于机器学习和深度学习的推荐系统实战【图书推荐、电影推荐、音乐推荐】
  14. c#餐饮系统打印机_C# 实现打印机功能
  15. 图论 —— 最短路 —— Johnson 算法
  16. nfc卡模式与标准模式_干货丨NFC最全解析
  17. 交通强国试点方案密集落地!智慧交通成为关键词
  18. 二级路由添加网络打印机的方法
  19. 阿里云服务器ECS上部署简单的SSM项目
  20. SAP 后台表查询方法及消息报错定位方法

热门文章

  1. html5 注册协议弹出层,js制作带有遮罩弹出层实现登录注册表单特效代码分享
  2. linux系统chmod缩写,文件属性控制命令chmod
  3. rtklib 天线相位中心_(原文链接错,重发)用于WiMAX和WLAN的紧凑型共面波导馈电双频线极化和圆极化单极天线(原载于微波杂志19年7/8月号)...
  4. python sklearn 梯度下降法_科学网—Python_机器学习_总结4:随机梯度下降算法 - 李军的博文...
  5. 前端基础-html-段落标签
  6. 计算机网络之传输层:4、TCP连接管理
  7. (软件工程复习核心重点)第十二章软件项目管理-第一节:软件项目管理综述、估算软件规模和工作量估算
  8. (数据库系统概论|王珊)第十章数据库恢复技术-第四、五、六、七节:数据库恢复技术和数据库镜像
  9. Libevent源码分析-----连接监听器evconnlistener
  10. 获取进程或线程的ID以及句柄信息