Tree--RedBlackTree詳解(2 - 3 - 4Tree)(紅黑樹)

  前言

  最近看到好多紅黑樹的東西,英文好的童鞋能夠直接點擊http://www.cs.princeton.edu/~rs/talks/LLRB/RedBlack.pdf這裏查看我以前學習的材料,對理解下面講的東西確定也有點幫助(可是不徹底同樣),英文通常的同窗就直接看個人文采飛揚把哈哈。還有你們能夠去coursera上學習一些國外比較好的資料。感受比國內一些學習網站作的好不少。html

  前面一篇隨筆寫的binarysearchtree(http://www.cnblogs.com/robsann/p/7567596.html)說了有一個缺點就是不平衡,意思就是插入已經排好序的對象的時候會變成一條鏈表,鏈表固然比二叉樹要慢不少啦,隨機插入的話二叉樹的各類方法須要的時間和LgN的成正比。因此紅黑樹實際上是在解決二叉樹不平衡的問題的。java

  度娘(這裏稍微看一下)

  紅黑樹是每一個節點都帶有顏色屬性的二叉查找樹,顏色或紅色或黑色。在二叉查找樹強制通常要求之外,對於任何有效的紅黑樹咱們增長了以下的額外要求:
  性質1. 節點是紅色或黑色。
  性質2. 根節點是黑色。
  性質3 每一個葉節點(NIL節點,空節點)是黑色的。
  性質4 每一個紅色節點的兩個子節點都是黑色。(從每一個葉子到根的全部路徑上不能有兩個連續的紅色節點)
  性質5. 從任一節點到其每一個葉子的全部路徑都包含相同數目的黑色節點。
  這些約束強制了紅黑樹的關鍵性質: 從根到葉子的最長的可能路徑很少於最短的可能路徑的兩倍長。結果是這個樹大體上是平衡的。由於操做好比插入、刪除和查找某個值的最壞狀況時間都要求與樹的高度成比例,這個在高度上的理論上限容許紅黑樹在最壞狀況下都是高效的,而不一樣於普通的二叉查找樹。

 正文 

  2 - 3 - 4 Tree

  2 - 3 - 4 Tree 我以爲算是一種模型,一種樹模型保證了樹是平衡的,所謂平衡就是樹不會一個枝頭長得很高,另一個枝頭長得很矮,那保證平衡有什麼用?平衡的狀況下,全部操做須要的時間都是和LgN成正比的,你說膩害不膩害。node

  2 - 3 - 4 樹,容許一個節點是 2-nodes 或者是 3 nodes 或者是 4 nodes, 具體的意思就是說git

   這是一個2-nodes, 有2個觸手,可以指向不一樣的2個子元素,左邊的子元素小於A,右邊的子元素大於Agithub

  3-nodes, 3 分叉,最左邊的子元素小於C,中間的子元素between c和e, 最右邊的子元素大於Eide

    4-nodes, 3分叉。同上學習

  看看2-3-4tree是如何保證平衡的
網站

  假設有 10 7 6 3 8 11 15 要插入 2 -3 4 樹中ui

  

  

  

  

  

  

  這裏插入還有另一種選擇叫Bottom-up solution,之下而上的一種解法(能夠忽略)。就是先找到這個節點會被插入的位置,若是插入後變成了5-nodes,就把其中一個節點往父節點拋出,讓父節點和其中的一個子節點結合。this

  刪除同理,爲了要保證樹的平衡,當刪除的時候,若是被刪除的節點只有一個元素的話,必需要把父親元素拉下來造成一個3-nodes而後刪除後變成2-nodes(後面還會說)

  左傾紅黑樹(LeftLeaningRedBlackTree)

  紅黑樹和2-3-4樹的關係,紅黑樹是2-3-4樹的一種實現方式。2-3-4樹只是一個模型。

  先介紹一下節點對象Node,看代碼,以後會用到這個對象

    private class Node {
        private K k; // 這個節點的key 
        private V v; //節點的value
        private Node left, right;  //左右節點
        private int size;  //節點爲根的樹的大小
        private boolean color; //節點的顏色,分黑和紅
        
        Node(K k, V v, int size, boolean color) {
            this.k = k;
            this.v = v;
            this.size = size;
            this.color = color;
        }
        
        @Override
        public String toString() {
            if (color) return "red " + v;
            else return "black " + v;
        }
        
    }
Node(節點對象)

 

  左傾紅黑樹和紅黑樹的區別以下

  這是通常的紅黑樹,3-node 的時候紅節點能夠左傾或者右傾

  左邊是2-3-4樹的模型,右邊是紅黑樹的實現

  

  這是左傾紅黑樹, 3-nodes的時候紅色的節點必須在左邊。

  

  用常量來表示紅黑

  

  紅黑樹的修正

  首先左傾紅黑樹的性質必需要獲得保證(就是上圖中說的Require that 3 -node be left leaning),可是不少時候可能由於插入或者刪除的操做破壞了這個性質,因此咱們必需要修正。

  這裏介紹3個修正的方法

  第一個方法的使用場景是這樣子的,(爲了避免破壞平衡,每次插入的都是紅節點),下圖插入的節點在右邊,破壞了左傾的性質,因此必須rotateLeft。rotateLeft是指把紅色節點左移

    

  方法以下

    //右樹是紅link的時候,turn this red link to left
    private Node rotateLeft(Node  h) {
        assert(isRed(h.right));
        
        Node  x = h.right;  //change the pointers
        h.right = x.left;
        x.left = h;
        
        x.color = h.color; //change the colors
        h.color = RED;
        
        x.size = h.size; //change the sizes
        h.size = size(h.left) + size(h.right) + 1;
        return x;
    }
rotateLeft(仔細看一邊)

  

  另一種狀況是最新插入的節點在最左邊,把中間節點rotateRight,從新平衡

    

  方法以下

    //左樹是紅link的時候,turn this red link to left
    private Node rotateRight(Node  h) {
        assert(isRed(h.left));
        
        Node x = h.left;
        h.left = x.right;
        x.right = h;
        
        x.color = h.color;
        h.color = RED;
        
        x.size = h.size; //size is the same
        h.size = size(h.left) + size(h.right) + 1;
        
        return x;
    }
rotateRight

   這張圖是破壞了性質以後,修正的辦法

   

  還有一個方法是flipColors(),代碼以下,就是可能插入的時候須要知足當前節點不是4-nodes,可能就會使用這個方法

  

  有了上面這些輔助的方法後就能夠開始下面的學習了

  

  左傾紅黑樹的put  

  在2-3-4tree中的介紹中也知道了,put的時候,當前節點若是是4-nodes的話就沒有位置留給須要插入的對象了。

  因此咱們在put的時候,必定要保證當前的節點(currentNode)之後用cn來表示。cn必須不是4-nodes, 若是是4-nodes的話就用flipColor把4-node變成3個2-node

  假設咱們要插入10  7  6  3 這4個對象, 大片動態圖,燃燒的經費。

  

  附上代碼

    public void put(K k, V v) {
        root = put(root, k, v);
        root.color = BLACK;
    }
    
    private Node put(Node cn, K k, V v) {
        if (cn == null) return new Node(k, v, 1, RED);
        if(isRed(cn.left) && isRed(cn.right)) split4Node(cn);//是4節點的話 就split
        
        int cmp = k.compareTo(cn.k);
        if (cmp > 0) cn.right = put(cn.right, k, v); // k > node.k go right
        else if (cmp < 0) cn.left = put(cn.left, k, v);
        else cn.v = v; //hit
        
        //following code is to fix the tree on the way up
        if (isRed(cn.right) && !isRed(cn.left)) cn = rotateLeft(cn); // right leaning 3nodes的時候   須要變成 left leaning
        if (isRed(cn.left) && isRed(cn.left.left)) cn = rotateRight(cn);  //變成了一個4節點
        
        cn.size = size(cn.left) + size(cn.right) + 1;
        return cn;
    }
put

  

  左傾紅黑樹的get

  樹的get方法其實很簡單,就是判斷key是否是相等,若是相等就return 這個值。

  

    public V get(K k) {
        return get(root, k);
    }
    
    //cn means currentNode
    private V get(Node cn, K k) {
        if (cn == null) return null; // not find the key
        
        int cmp = k.compareTo(cn.k);
        if(cmp > 0) return get(cn.right, k);
        else if (cmp < 0) return get(cn.left, k);
        else return cn.v; // hit
    }
get

  左傾紅黑樹的刪除

  刪除能夠說是最難的了把,基本的思想就是,cn節點(當前節點)不會是2-node,(若是root節點是2-nodes,咱們須要把root節點變成紅節點)

  保證其中一個子節點不是2節點(這個保證須要看刪除的節點位於當前節點的哪裏,好比刪除的節點比cn節點小,因此接下來咱們會往left走,因此要保證left節點不是2-node)。由於2節點若是刪除了的話就不會平衡。因此必需要把紅節點從root一步一步carry下去。

  先實現一個deleteMin方法,咱們要把紅節點帶向左邊。想想,紅節點帶向左邊後,若是左邊有節點刪除了可能沒辦法保持平衡,紅節點能夠變成黑節點,代替剛纔被刪除的節點。經過這樣子能夠保證左邊的樹是必定會平衡的。

  deleteMin的幾種狀況

  1. , 如今能夠直接刪除掉。也不會影響平衡。刪除了後3節點變成了2節點。

  

  2., 須要繼續向左走,可是左子節點是2-node,必需要想辦法變成不是2-node。因此須要把父親節點和兄弟節點和cn節點。整合在一塊兒變成4-node

  

  3. , 須要繼續向左走,可是左子節點是2-node,必需要想辦法變成不是2-node。發現兄弟節點是否是2-nodes。因此把兄弟節點借一個node過來

  

  

  2和3這2種狀況總結在一塊兒就是moveRedLeft的代碼

  

    public void deleteMin() {
        //保證了root節點不是2nodes
        if (!isRed(root.left) && !isRed(root.right))
            root.color = RED;
        
        root = deleteMin(root);
        root.color = BLACK;
    }
    
    
    public Node deleteMin(Node cn) {
        if (cn.left == null) return null;
        
        if (!isRed(cn.left) && !isRed(cn.left.left))  //判斷左邊子節點是否是2node,是的話就須要把Red帶下去
            cn = moveRedLeft(cn);
        
        cn.left = deleteMin(cn.left);
        
        return fixup(cn);
    }

    private Node fixup(Node h) {
        
        if (isRed(h.right) && !isRed(h.left)) h = rotateLeft(h); //右傾
        if (isRed(h.left) && isRed(h.left.left)) h = rotateRight(h);
        h.size = size(h.left) + size(h.right) + 1; //right the size
        return h;
    }
deleteMin

   隨意的delete方法。按照下面的圖說一下,基本的思想。

  首先刪除D。從H開始,H不是2-node(右邊有一個紅節點),D小於H,左往H的左邊找

  找到了D。D不是2-node且是紅節點。找到了D,處理辦法是找到右樹中最小的值,發現是E, 把最小的值賦值給當前的node

  因此咱們要往右邊走。可是發現右邊節點F是2-node。因此咱們要把紅色的鏈接往右邊帶。

  flipColor(D), B D 和 F就變成了一個4-node(能夠本身作一下圖看看是否是這樣子的)。 這時候紅連接也往右邊帶了。

  如今到了F 節點。發現F 的左節點是 2-node。把紅色的連接往左邊帶。

  flipColor(F),這個時候F 和 E 和 G ,變成了一個4-node。 瓜熟蒂落刪除左邊的節點。沒有影響平衡。

  接着原路返回,修復節點。

  

  

  

    public void delete(K k) {
        if (k == null) throw new IllegalArgumentException("argument to delete() is null");
        if (!contains(k)) return;
        
        if (!isRed(root.left) && !isRed(root.right))
            root.color = RED;
        root = delete(root, k);
        if (root != null)
            root.color = BLACK;
    }
    
    public boolean contains(K k) {
        return get(k) != null;
    }

    private Node delete(Node cn, K k) {
        
        if (cn == null) return null;
        
        int cmp = k.compareTo(cn.k);
        
        if (cmp < 0) { // k < node.k go left
            if (!isRed(cn.left) && !isRed(cn.left.left)) //保證了下一個左元素不是2nodes
                cn = moveRedLeft(cn);
            cn.left = delete(cn.left, k);
        } else if (cmp > 0) { // k > node.k go right
            if (isRed(cn.left) && !isRed(cn.right)) //若是是3節點的話須要 rotate 把red轉到右邊
                cn = rotateRight(cn);
            if (!isRed(cn.right) && !isRed(cn.right.left)) //保證下一個右節點不是2nodes
                cn = moveRedRight(cn);
            cn.right = delete(cn.right, k);
        } else { //hit
            
            if (isRed(cn.left) && !isRed(cn.right)) 
                cn = rotateRight(cn);
            
            if (k.compareTo(cn.k) == 0 && (cn.right == null)) //find null just return null
                return null;
            
            if (!isRed(cn.right) && !isRed(cn.right.left)) //保證下一個右節點不是2nodes
                cn = moveRedRight(cn);
            
            if (k.compareTo(cn.k) == 0) {
                 Node x = min(cn.right);
                 cn.k = x.k;
                 cn.v = x.v;
                 cn.right = deleteMin(cn.right);
            } else cn.right = delete(cn.right, k);
        }
        return fixup(cn);
    }
delete

 

  總結 

  具體的實現能夠參考一下https://github.com/Cheemion/algorithms/blob/master/src/com/algorithms/tree/LeftLeaningRedBlackTree.java

  可能有地方說的不清楚哈。見諒,能夠留言我有不清楚的地方

相關文章
相關標籤/搜索