上一篇寫了關於紅黑樹基本性質的東西,這篇來講一說如何建立一棵紅黑樹吧。html
若是對紅黑樹的基本性質還有疑問,請先查看一下個人前一篇:http://www.cnblogs.com/unpolishedgem/archive/2012/05/16/2504311.html。算法
若是圖片打不開的話,就去看個人csdn博客:http://blog.csdn.net/arge129。spa
紅黑樹是一種二叉查找樹,那麼咱們可使用插入的方法來建立一棵紅黑樹,爲此,咱們先來介紹關於紅黑樹的一些基本操做。.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:寫博客很累,轉載的朋友請註明出處,謝謝。