Understand the red-black tree by tree 2-3

I. Introduction

  Previous article we explained step by step "binary" "binary search tree" "AVL-balanced binary tree" , build on each other from left to right. In particular binary search tree gives us ideas how data is organized as a search tree, of course, there is the natural binary search tree problem - in extreme cases, degenerate into a linked list next time. Therefore it leads AVL- balanced binary tree, rebalancing i.e. by LL, LR, RR, RL four rotating operation maintains a balanced binary search tree. This section we continue to sort out a high-level tree structure that is: red-black tree. Surely we all know how to maintain the balance of red-black tree, color inversion how to make it difficult to understand, although many many books Bowen on the red-black tree has to explain, but want to master or be proficient in red-black tree so that we still daunting. In this paper, we refer to the "algorithm 4" analysis of the red-black tree, the tree from 2-3 to start to understand the red-black tree. Starting to understand as to why the red-black tree is a reason, just like Darwin's theory of evolution, any one species will not pop out from the stones from a tree as 2-3. Development of data structure also follows the theory of biological evolution. A red-black tree to tree is the tree of evolution from 2-3.

 

Second, the 2-3 tree

  Nature 2.1,2-3 tree

 

  2-3 is similar to a perfect binary tree (full binary tree), but is 2-3 tree allows a node has three children, as its name suggests. 2-3 tree in order to maintain such a perfect balance of vision. Comprising the following requirements:

1  2 Node has one and only two children nodes, and can contain only one data item.
2  3 must have three children nodes, and contain only two data items in ascending order from left to right
 3 can not be inserted into one of the nodes on the node is inserted into the empty node, the new node can only be generated by fission or fusion 
4 when 2-3 2 node tree only when it can only be a full binary tree (perfect binary tree)

 

  We graphically detailed understanding of what these three properties.

  Node must have two children nodes 2, and contains only one data item: shown in FIG. 1, only two nodes and children nodes only 2, node 5, and node 8. And 2 can contain one data item directly or 6;

  

  3 must three children nodes, and contain only two data items in ascending order from left to right: 2, 5 <6 <7 <8 <9;

  

 

  The node can not be inserted into the insertion node on an empty node, the new node can only be produced by fission or fusion: an explanation on this, we operate by inserting the following 2-3 trees a good experience.

 

2.2,2-3 tree insertion

 

  This is understood from the insertion section 2-3 tree "can not be inserted when the insertion node the node to an empty node, the new node can only be produced by fission or fusion" this sentence.

  Analysis text "binary search tree" of the front we found that when we inserted sequentially (1, 2, 3, 4, 5, 6, 7, 8 ...) consecutive nodes, the binary search tree list is degraded. This section 2-3 we see how the tree is kept balanced.

  

  As shown in Figure 3, we first insert (1,2,3,4,5) five elements.

  Insert elements 1, 2 to create a node (element 1).

  Temporarily insert elements forming a fused 2,1,2 element 3 nodes. Why can not generate a node element 2 as the right node of a child's element? Because "can not be inserted into an empty node when the node is inserted into the node, the new node can only be generated by fission or fusion."

  3,1,2,3 element temporarily insert elements form a fused 4 node.

  Split, because this is a 2-3 tree can not exist 4 node, the node 4 being formed to divide the intermediate elements as the root node, each about two elements about their child nodes. Then the visible form of a full binary tree.

  Insert elements 4, depending on the size relationship between the elements that will be found where the node element 3. Because the newly inserted node can not be inserted into an empty node, 4 elements will find the last node and its integration according to the nature of the search tree. I.e. 3,4 fused to a three-element node. 4 and the element 3 to the right side elements.

  Insert elements 5, with the insertion element 4, the element 5 to a three way to find where the element nodes 3, 4, fused to temporarily form a 4 node.

  Splitting, the node 4 as 3,4,5 element 4 is located above the node with the elements 2, 3 is formed, divide operations. Depending on the size relationship between the four elements will serve as the root node, 3, 5 elements in each of its child nodes around.

  融合,前面的分裂操作已经导致该2-3树不满足其第四条性质“当2-3树只有2节点的时候,其只能是一棵满二叉树(完美二叉树)”,所以该2-3树将要向上融合以满足2-3树的性质。我们只需要将4元素所在节点与其父节点即元素2所在的节点进行融合即可。这时,2,4元素就形成了一个3节点。

  

  继续插入,6,7元素,最终形成的2-3树如上图4所示。可见,2-3树维护平衡的机制是如此的神奇。整个过程我们是一次插入了(1,2,3,4,5,6,7)7个元素,如果是二分搜索树,该二分搜索树将会退化为一个链表,但是2-3树却神奇的生成了一个满二叉树。

 

三,红黑树

  前面恶补了2-3树的知识,接下来我们就进入正题。

  红黑树的定义:

1 每个节点或者是红色的,或者是黑色的;
2 根节点是黑色的;
3 每个叶子结点(红黑树中叶子节点为最后的空节点)是黑色的;
4 如果一个节点是红色的,那么他的孩子都是黑色的;
5 从任意一个节点到叶子节点经过的黑色节点是一样的。

  上面的5点定义是建立在红黑树是一个二分搜索树的基础上的。所以红黑树具备二分搜索树的所有性质。

  看着定义是不是感觉无法理解红黑树?大部分的教材或者博文中对红黑树的讲解都是生硬的根据这5条定义开始。真的是一头雾水,不知所以然。接下来我们根据2-3树的插入过程结合红黑树的性质,看看红黑树的旋转和变色过程。

 

 3.1、2-3树到红黑树的转换规则

  前面详细介绍了2-3树的分裂和融合过程,本小节,我们来看看2-3树向红黑树转换的过程。2-3树有两类节点,1节点和2节点。还有一个临时的节点3节点。下面看看2-3树的这三种节点对应于红黑树的节点情况。

 

 

  如上所示:

  1节点:对应于红黑树的黑色节点。

   2节点:对应于红黑树黑色的父节点和红色的左孩子节点。

  3节点:对应于红色的父节点和黑色的左右孩子节点。这里需要说一下,为什么是红色的父节点而不是黑色的呢?主要是因为2-3树的3节点需要将分裂后的父节点进行向上融合,红色的符合我们向红黑树中插入任何一个节点默认都是红色的实现方式(后面会介绍)。如果该父节点是红黑树的根节点的话,那他肯定需要变色,这一点就不属于2-3树向红黑树的变换规则了,而属于红黑树的性质。

 

3.2、红黑树的旋转,变色和颜色反转

  本节是本文的重点,前面介绍了这么多关于2-3树的知识,都是为本节做铺垫,如果屏幕前的你看到了这里请继续保持耐心。

  下面我们向2-3树和红黑树依次插入(1,2,3)三个元素来看看旋转,变色和颜色反转的过程。

  在此之前我们需要清楚红黑树的一个性质:根节点必须为黑色的。一个实现红黑树的规则:新插入的节点永远为红色。

  插入1:

  

 

   如上所示,插入元素1。2-3树就是一个1节点不需要做任何改变。根据红黑树添加的规则:新插入的节点为红色,所以1元素的节点为红色。根据红黑树的性质:根节点必须为黑色。1元素的节点需要进行变色。

  插入2:

  

  如上所示,插入元素2.2-3树会形成一个2节点。根据2-3树向红黑树变换的规则,需要变为2元素所在的节点为黑色的父节点,1元素所在的节点为红色,并为2元素的左孩子节点。

  左旋:但是根据二分搜索树的性质,插入的2元素会成为1元素的右孩子,这时我们需要对1元素进行左旋转,然后得到如上左旋后的结果。

  变色:这时再将2元素换成1元素的颜色,然后将1元素变为红色。这样的变色是有原因的,首先为了向上兼容,该子树的根节点需要始终保持原来的颜色,即将新的根节点2换成原来的根节点的颜色。其次根据2-3树的2节点向红黑树转换的规则,我们需要将1节点的颜色变为红色。

  关于左旋和变色代码实现如下

 1     //////////////////////////////////////////
 2     //   y                        x         //
 3     //  /  \     左旋转          /  \        //
 4     // T1   x   --------->     y   T3       //
 5     //     / \                /      \      //
 6     //    T2 T3              T1      T2     //
 7     //////////////////////////////////////////
 8     private Node leftRotate(Node y){
 9         Node x = y.right;
10         Node t2 = x.left;
11 
12         y.right = t2;
13         x.left = y;
14 
15         x.color = y.color;// 为了向上兼容,将新的根节点变成老根节点的颜色
16         y.color = RED; // 将被旋转的节点颜色置为红色。
17 
18         return x;
19     }

 

  对于上面变色的过程大家可能会想为什么变色过程是这样的而不是根据红黑树的性质,将2元素变为黑色?如果是这样,1,2都是黑色的。从根节点2触发,到所有叶子节点锁经过的黑色节点数就不对了。所以说,我们需要颜色交换而不是简单地将父节点变黑。

  插入3:

  

   插入3:

  如上所示,插入3元素,对于2-3树会形成一个临时的3节点,然后进行分裂。红黑树中新插入的节点都是红色的,所以,这时会形成一个根节点为2。左右孩子都是红色的节点。

  颜色反转:根据2-3树向红黑树变化的规则,并不满足。需要进行颜色反转,即将1,3元素变为黑色,2元素变为红色。至于为什么这样进行颜色反转,原因很简单,因为在2-3树中,三节点分裂后需要向上融合。

  变色:这时2元素为根节点,根据红黑树的性质,需要进行变色即可。假如2元素不是根节点,需要向上融合,如2.2章节描述。

  颜色翻转相关代码

 1     /**
 2      * 颜色翻转
 3      * @param node
 4      */
 5     /////////////////////////////////////////
 6     //      黑                    红        //
 7     //     /  \     ------>     /  \       //
 8     //   红    红              黑    黑     //
 9     ////////////////////////////////////////
10     private void flipColors(Node node){
11         node.color = RED; // 置为红色,为了向上融合,在2-3树中,3节点分裂后的根节点要向上融合
12         node.left.color = BLACK;
13         node.right.color = BLACK;
14     }

 

 

  上面我们详细介绍了依次插入(1,2,3)三个元素的过程,并且这个过程详细覆盖了红黑树的旋左转,变色,颜色反转的过程。这时就差一种右旋的操作,是因为我们插入的数据的问题没遇到右旋的情况,这时如果我们依次插入(1,2,3)就会遇到右旋的情况了。

  

  先插入3,2元素,会形成上面的红黑树。

  继续插入1元素,这个变化的过程如上所示,详细过程我们就不详细介绍了。我们简单说一下右旋的变色过程,旋转之前,该子树的根节点颜色为黑色,为了向上兼容需要将新的根节点即2元素的颜色换成原来的根节点3元素的颜色,然后将3元素的颜色变成红色。

  右旋的过程如下代码所示

 1     //////////////////////////////////////////
 2     //     y                       x        //
 3     //    / \     右旋转          /  \       //
 4     //   x  T2   ------->       z    y      //
 5     //  / \                         /  \    //
 6     // z  T1                       T1  T2   //
 7     //////////////////////////////////////////
 8     private Node rightRotate(Node y){
 9         Node x = y.left;
10         Node t1 = x.right;
11 
12         y.left = t1;
13         x.right = y;
14 
15         x.color = y.color;// 为了向上兼容,将新的根节点变成老根节点的颜色
16         y.color = RED;// 将被旋转的节点颜色置为红色。
17 
18         return x;
19     }

 

  总结一下上面依次插入(1,2,3)和(3,2,1)的过程,我们总结一个规律。

  右旋:当一个节点的左孩子节点和左孩子的左孩子节点都是红色的时候需要进行右旋。

  左旋:当一个节点的右孩子是红色节点并且左孩子不是红色,进行左旋。

  颜色反转:当一个节点的左右孩子节点都是红色的时候需要进行颜色反转。

  随之延伸出来的一个性质即,在我们的2-3树向红黑树变换的规则下,红色的节点只能出现在一个黑色节点的左孩子处。

  注意:以上我们总结的规律,只是建立在我们在2-3树向红黑树变换的过程中,为什么这么说呢?因为当你查阅的资料多了你会发现,红黑树的实现是多种多样的,只要能满足红黑树的5点性质即可是一棵符合要求的红黑树。当然本文我们介绍的变换规则是主流的规则。也是维持红黑树的平衡性最好的一种变换规则。

  如果你对左旋或者右旋具体是怎么旋转的请参阅我的另一篇博文《平衡二叉树》中关于旋转的过程,严格来说红黑树的旋转较于平衡二叉树只是多了一个颜色变化的过程,上面我们也有详细的描述。

  大家看代码一目了然。

  感兴趣的话,依次插入(1,2,3,4,5,6,7),手动画一下如果能得到最终结果如下,说明你对红黑树的理解就没什么问题了。

 

 

四、红黑树的实现

  关于代码实现,和平衡二叉树的实现思想是一样的,我们就不具体描述了。当然了如果你只是为了面试而阅读本文,恭喜你你可以跳过本章节,基本没有哪个公司会让你手写红黑树。但是手写平衡二叉树或者二分搜索树是有可能的。所以大家可以参阅笔者有关《二分搜索树》《平衡二叉树》的文章。

  如果出于研究的目的你想具体实现,我相信你肯定是具备了平衡二叉树的知识才来手撕红黑树的,所以当你具备了平衡二叉树的知识,以下代码应该是没啥难度的。

  1 /**
  2  * 描述:红黑树的实现
  3  *
  4  * @Author shf
  5  * @Date 2019/8/1 9:42
  6  * @Version V1.0
  7  **/
  8 public class RBTree<K extends Comparable<K> , V>{
  9     private static final boolean RED = true;
 10     private static final boolean BLACK = false;
 11     private class Node{
 12         public K key;
 13         public V value;
 14         public Node left, right;
 15         public boolean color;
 16         public Node(K key, V value){
 17             this.key = key;
 18             this.value = value;
 19             left = null;
 20             right = null;
 21             color = RED;
 22         }
 23         @Override
 24         public String toString(){
 25             return "key-->" + key + "== value-->" + value + "== color-->" + color;
 26         }
 27     }
 28     private Node root;
 29     private int size;
 30 
 31     public RBTree(){
 32         root = null;
 33         size = 0;
 34     }
 35     public int size(){
 36         return size;
 37     }
 38     public boolean isEmpty(){
 39         return size == 0;
 40     }
 41     private boolean isRed(Node node){
 42         if(node == null){
 43             return BLACK;
 44         }
 45         return node.color;
 46     }
 47     //////////////////////////////////////////
 48     //   y                        x         //
 49     //  /  \     左旋转          /  \        //
 50     // T1   x   --------->     y   T3       //
 51     //     / \                /      \      //
 52     //    T2 T3              T1      T2     //
 53     //////////////////////////////////////////
 54     private Node leftRotate(Node y){
 55         Node x = y.right;
 56         Node t2 = x.left;
 57 
 58         y.right = t2;
 59         x.left = y;
 60 
 61         x.color = y.color;
 62         y.color = RED;
 63 
 64         return x;
 65     }
 66     //////////////////////////////////////////
 67     //     y                       x        //
 68     //    / \     右旋转          /  \       //
 69     //   x  T2   ------->       z    y      //
 70     //  / \                         /  \    //
 71     // z  T1                       T1  T2   //
 72     //////////////////////////////////////////
 73     private Node rightRotate(Node y){
 74         Node x = y.left;
 75         Node t1 = x.right;
 76 
 77         y.left = t1;
 78         x.right = y;
 79 
 80         x.color = y.color;
 81         y.color = RED;
 82 
 83         return x;
 84     }
 85     private void flipColors(Node node){
 86         node.color = RED;
 87         node.left.color = BLACK;
 88         node.right.color = BLACK;
 89     }
 90     public void add(K key, V value){
 91         root = add(root, key, value);
 92         root.color = BLACK;
 93     }
 94     private Node add(Node node, K key, V value){
 95         if(node == null){
 96             size ++;
 97             return new Node(key, value);
 98         }
 99         if(key.compareTo(node.key) < 0){
100             node.left = add(node.left, key, value);
101         } else if(key.compareTo(node.key) > 0){
102             node.right = add(node.right, key, value);
103         } else {
104             node.value = value;
105         }
106         if(isRed(node.right) && !isRed(node.left)){
107             node = leftRotate(node);
108         }
109         if(isRed(node.left) && isRed(node.left.left)){
110             node = rightRotate(node);
111         }
112         if(isRed(node.left) && isRed(node.right)){
113             flipColors(node);
114         }
115         return node;
116     }
117     public void levelOrder(){
118         levelOrder(root);
119     }
120     private void levelOrder(Node node){
121         Queue<Node> queue = new LinkedList<>();
122         queue.add(node);
123         while(!queue.isEmpty()){
124             Node cur = queue.remove();
125             System.out.println(cur);
126             if(cur.left != null){
127                 queue.add(cur.left);
128             }
129             if(cur.right != null){
130                 queue.add(cur.right);
131             }
132         }
133     }
134 }

 

五、红黑树在java中的应用

  在java的众多集合类中,仔细研究大家可能会发现,TreeMap,TreeSet,都是红黑树的实现。本质上TreeMap是真正的红黑树的实现,TreeSet是对TreeMap的二次封装。还有一个重要的集合,HashMap,对是他是他就是他,几乎每次面试面试官都会死磕一遍HashMap,除此之外他也是我们日常开发工作中最常用的集合之一。感兴趣的可以研究一下HashMap。在HashMap中当一个索引位置维护的链表长度超过8即转换为红黑树,小于6从红黑树转换为链表。为什么是6,8而不是8,8主要是一个复杂度震荡的问题。

 

  

  爱国,敬业

  参考文献:

  《玩转数据结构-从入门到进阶-刘宇波》

  《算法4》

  如有错误的地方还请留言指正。

  原创不易,转载请注明原文地址:https://www.cnblogs.com/hello-shf/p/11364565.html 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  

 

Guess you like

Origin www.cnblogs.com/hello-shf/p/11364565.html