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

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

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

  若是圖片打不開的話,就去看個人csdn博客:http://blog.csdn.net/arge129spa

  紅黑樹是一種二叉查找樹,那麼咱們可使用插入的方法來建立一棵紅黑樹,爲此,咱們先來介紹關於紅黑樹的一些基本操做。.net

  1. 旋轉指針

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

  以下圖所示,在x結點上作左旋時,咱們假設它的右孩子不是nil[T];x能夠是樹內任意右孩子不是nil[T]的結點。算法導論裏面講到「左旋以x到y之間的鏈爲支軸進行。」我沒太理解這句話,可是我是這麼想象的,以下圖中的曲線箭頭所示,左旋就是x下移,y上移,箭頭所示方向爲左,右旋就是x上移,y下移,箭頭所示方向爲右。htm

 

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

旋轉的僞代碼,我就不寫了,在算法導論裏面都有,下面我把我寫的旋轉代碼給貼過來吧,固然仍是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. 插入get

  既然紅黑樹是一棵二叉查找樹,那麼咱們就能夠像二叉查找樹那樣爲紅黑樹插入一個元素。咱們將二叉查找樹的插入算法作一個略微的修改,咱們將結點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:寫博客很累,轉載的朋友請註明出處,謝謝。

相關文章
相關標籤/搜索