白話紅黑樹系列之二——紅黑樹的構建


  上一篇寫了關於紅黑樹基本性質的東西,這篇來說一說如何創建一棵紅黑樹吧。

  如果對紅黑樹的基本性質還有疑問,請先查看一下我的前一篇:http://www.cnblogs.com/unpolishedgem/archive/2012/05/16/2504311.html

  如果圖片打不開的話,就去看我的csdn博客:http://blog.csdn.net/arge129

  紅黑樹是一種二叉查找樹,那么我們可以使用插入的方法來創建一棵紅黑樹,為此,我們先來介紹關於紅黑樹的一些基本操作。

  1. 旋轉

  旋轉是一種能保持二叉查找樹性質的查找樹局部操作,包括左旋右旋兩種操作。

  如下圖所示,在x結點上做左旋時,我們假設它的右孩子不是nil[T];x可以是樹內任意右孩子不是nil[T]的結點。算法導論里面講到“左旋以x到y之間的鏈為支軸進行。”我沒太理解這句話,但是我是這么想象的,如下圖中的曲線箭頭所示,左旋就是x下移,y上移,箭頭所示方向為左,右旋就是x上移,y下移,箭頭所示方向為右。

 

  值得注意的是,在旋轉過程中,只會有指針結構的變化,不會有顏色的變化,因此在上面的圖中,我沒有畫出結點的顏色。

旋轉的偽代碼,我就不寫了,在算法導論里面都有,下面我把我寫的旋轉代碼給貼過來吧,當然還是Java版的。

 1 /**
 2      * 左旋
 3      * @author Alfred
 4      * @param x 輸入結點
 5      */
 6     private void leftRotated(RBTreeNode x){
 7         RBTreeNode y = x.getRight();
 8         //x的右孩子y不能是NIL_T,如果是的話,直接返回。
 9         if(y == NIL_T){
10             return;
11         }
12         //將y的左子樹變為x的右子樹
13         //設置x的右子樹
14         x.setRight(y.getLeft());
15         //設置y的右子樹的父結點為x
16         if(y.getLeft() != NIL_T){
17             y.getLeft().setParent(x);
18         }
19         //將x的父結點設置為y的父結點
20         y.setParent(x.getParent());
21         //如果x是根結點,則更換根結點
22         if(x.getParent() == NIL_T){
23             rootNode = y;
24         }else if(x == x.getParent().getLeft()){
25             //如果x是其父結點的左孩子,則將y設為其父結點的左孩子
26             x.getParent().setLeft(y);
27         }else{
28             //如果x是其父結點的右孩子,則將y設為其父結點的右孩子
29             x.getParent().setRight(y);
30         }
31         //y的左孩子為x
32         y.setLeft(x);
33         //x的父結點為y
34         x.setParent(y);
35     }
36     /**
37      * 右旋
38      * @author Alfred
39      * @param y 輸入結點
40      */
41     private void rightRotated(RBTreeNode y){
42         RBTreeNode x = y.getLeft();
43         //y的左孩子x不能是NIL_T,如果是的話,直接返回。
44         if(x == NIL_T){
45             return;
46         }
47         //將x的右子樹變為y的左子樹
48         //設置y的左子樹
49         y.setLeft(x.getRight());
50         //設置x的右子樹的父結點為y
51         if(x.getRight() != NIL_T){
52             x.getRight().setParent(y);
53         }
54         //將y的父結點設置為x的父結點
55         x.setParent(y.getParent());
56         //如果y是根結點,則更換根結點
57         if(y.getParent() == NIL_T){
58             rootNode = x;
59         }else if(y == y.getParent().getLeft()){
60             //如果y是其父結點的左孩子,則將x設為其父結點的左孩子
61             y.getParent().setLeft(x);
62         }else{
63             //如果y是其父結點的右孩子,則將x設為其父結點的右孩子
64             y.getParent().setRight(x);
65         }
66         //x的右孩子為y
67         x.setRight(y);
68         //y的父結點為x
69         y.setParent(x);
70     }

  2. 插入

  既然紅黑樹是一棵二叉查找樹,那么我們就可以像二叉查找樹那樣為紅黑樹插入一個元素。我們將二叉查找樹的插入算法做一個略微的修改,我們將結點z插入到樹中,就像樹T是一棵普通的二叉查找樹一樣,然后將z着為紅色,為保持紅黑樹的性質,我們需要對樹中的結點進行重新着色並旋轉。如果對二叉查找樹的插入操作不熟悉,請閱讀我之前寫過的博客:http://www.cnblogs.com/unpolishedgem/archive/2012/05/10/2494403.html

  我們來分析一下,在插入過程中可能違反的性質有哪幾個。為此,我把紅黑樹的性質再抄寫一次。

  一棵二叉查找樹如果滿足下面的紅黑性質,則為一棵紅黑樹:

  1) 每個結點是或是紅的,或是黑的。

  2) 根結點是黑的。

  3) 每個葉結點(nil[T])是黑的。

  4) 如果一個結點是紅的,那么它的兩個兒子是黑的。

  5) 對每個結點,從該結點到其子孫結點的所有路徑上包含相同數目的黑結點。

  首先,我們插入的結點是紅色的,因此不會違反性質1)和性質5),性質3)自然成立。唯一可能被破壞的是2)和4)。而且,2)和4)至多有一個性質被破壞。性質2)被破壞時的修復很簡單,只需要將根結點重新着色為黑色即可。而性質4)被破壞的修復則要復雜一些,具體分為三種請況。

  情況1):z的叔叔y是紅色的。

  如下圖所示,如果z的叔叔y是紅色的,將z的父結點和y着色為黑色,然后將z的祖父結點着色為紅色,最后將z的祖父結點作為新的z結點進行迭代檢查,因為z的祖父結點原來是紅色的,被着色為黑色的時候,有可能會引起紅黑樹性質的破壞。

 

  情況2):z的叔叔y是黑色的,而且z是右孩子。

  情況3):z的叔叔y是黑色的,而且z是左孩子。

  如下圖所示,如果是情況2),我們可以立即使用一個左旋變成情況3)。情況3)中,首先交換了B和C的顏色,然后通過一個右旋來使整個樹達到了滿足性質4)。

  從這三種情況來看,可以發現一個非常有趣的事情,那就是該過程所做的旋轉從不超過兩次,因為只有情況1)會繼續將z上移進行紅黑性質檢查,而一旦進入了情況2)或者情況3),就不會再進行檢查了。

  同樣,偽代碼就不寫了,算法導論上都有,在此只寫Java實現代碼。

 1 /**
 2      * 插入操作
 3      * @author Alfred
 4      * @param k
 5      */
 6     public void treeInsert(int k){
 7         RBTreeNode z = new RBTreeNode(k, NodeColor.RED);
 8         RBTreeNode y = NIL_T;
 9         RBTreeNode x = rootNode;
10         //與二叉查找樹的插入過程類似
11         while(x != NIL_T){
12             y = x;
13             if(z.getKey() < x.getKey()){
14                 x = x.getLeft();
15             }else{
16                 x = x.getRight();
17             }
18         }
19         z.setParent(y);
20         if(y == NIL_T){
21             rootNode = z;
22         }else if(z.getKey() < y.getKey()){
23             y.setLeft(z);
24         }else{
25             y.setRight(z);
26         }
27         z.setLeft(NIL_T);
28         z.setRight(NIL_T);
29         //進行修復
30         rbInsertFixUp(z);
31     }
32     /**
33      * 修復插入操作引起的不滿足的紅黑性質
34      * @author Alfred
35      * @param z 要修復的結點
36      */
37     private void rbInsertFixUp(RBTreeNode z){
38         RBTreeNode y = null;
39         while(z.getParent().getColor() == NodeColor.RED){
40             //如果z的父結點是z的祖父結點的左孩子
41             if(z.getParent() == z.getParent().getParent().getLeft()){
42                 y = z.getParent().getParent().getRight();
43                 //情況1),z的叔叔y的顏色是紅色的。
44                 if(y.getColor() == NodeColor.RED){
45                     z.getParent().setColor(NodeColor.BLACK);
46                     y.setColor(NodeColor.BLACK);
47                     z.getParent().getParent().setColor(NodeColor.RED);
48                     z = z.getParent().getParent();
49                 }else if(z == z.getParent().getRight()){
50                     //情況2),z的叔叔y的顏色是黑色的,且z是其父結點的右孩子
51                     z = z.getParent();
52                     leftRotated(z);
53                     //情況2)經過左旋之后變為情況3),z的叔叔y的顏色是黑色的,且z是其父結點的左孩子
54                     z.getParent().setColor(NodeColor.BLACK);
55                     z.getParent().getParent().setColor(NodeColor.RED);
56                     rightRotated(z.getParent().getParent());
57                 }
58             }else{
59                 //與上面情況類似。
60                 y = z.getParent().getParent().getLeft();
61                 if(y.getColor() == NodeColor.RED){
62                     z.getParent().setColor(NodeColor.BLACK);
63                     y.setColor(NodeColor.BLACK);
64                     z.getParent().getParent().setColor(NodeColor.RED);
65                     z = z.getParent().getParent();
66                 }else if(z == z.getParent().getLeft()){
67                     z = z.getParent();
68                     rightRotated(z);
69                     z.getParent().setColor(NodeColor.BLACK);
70                     z.getParent().getParent().setColor(NodeColor.RED);
71                     leftRotated(z.getParent().getParent());
72                 }
73                 
74             }
75         }
76         //修復性質2)
77         rootNode.setColor(NodeColor.BLACK);
78     }

ps:寫博客很累,轉載的朋友請注明出處,謝謝。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM