1. 程式人生 > >資料結構---紅黑樹

資料結構---紅黑樹

紅黑樹怎麼來?歷史緣由

先說二叉樹,父節點劈叉出兩個節點,就兩個,不可以多。這樣一層一層下去。

再說二叉查詢樹(基於二分查詢),同樣有上面的劈叉屬性,但是呢左中右有大小順序,左邊小於中間,中間小於右邊。

接著就是紅黑樹,幹嘛上面下來就紅黑,二叉樹如果不加以控制就成單邊樹了。因為二叉樹沒規定不可以單邊開叉,而且一直往下開叉,5左單邊4再左單邊3,再左單邊2,類似的就失去了二叉查詢樹的意義了。這下紅黑就出來了。

紅黑為了達到樓上的特點就列出了五個特點

一、節點必須紅黑兩種顏色。treemap就用了boolean值分別表示

二、根節點必須黑色

三、沒個尾節點下面是黑色節點(感覺沒啥意義)

四、紅色節點下面或者上面不可以連續兩個出現

五、任意節點到他子節點經過的黑色節點個數一樣多。

那這玩意搞著五個條件無非就是限制插入,刪除。有人破壞樹的情況下,限制的。

例如,插入一個節點,首先二分查詢節點,再fix。fix就為了滿足這五個規則。一般都是不符合後面這兩條。紅色不可以連著,任意節點到子節點黑色同樣個數。

jdk經典案例,treeset、treemap。上酒,小二。

就treemap原始碼分析。

一、treemap內部類子節點除了map中的key和value,還有左右節點和父節點,最後特有顏色(boolean值),遍歷都是一樣的。

static final class Entry<K,V> implements Map.Entry<K,V> {
        K key;
        V value;
        Entry<K,V> left = null;
        Entry<K,V> right = null;
        Entry<K,V> parent;
        boolean color = BLACK;
        Entry(K key, V value, Entry<K,V> parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
        }

二、put查找出要插入的位置,也就是父節點。  fixAfterInsertion(e);這裡是重點。

    public V put(K key, V value) {
        Entry<K,V> t = root;
        if (t == null) {
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            if (key == null)
                throw new NullPointerException();
            Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

三、重點  fixAfterInsertion(e);

private void fixAfterInsertion(Entry<K,V> x) {

//直接紅色,就要搞破壞,黑色就沒啥了
        x.color = RED;

//新增節點非空,且不是根節點,且新增節點(自己,以下稱呼自己)父親是紅色,如果父親是黑色也不搞事。

        while (x != null && x != root && x.parent.color == RED) {

//父親跟父親的父親的左節點equal?也就是父親是否為祖父的左節點
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {

//祖父右節點,叔叔
                Entry<K,V> y = rightOf(parentOf(parentOf(x)));

//叔叔顏色紅色,跟父親紅色一樣,省事,父親和叔叔一起搞成黑色,祖父搞成黑色,再以祖父為自己,繼續迴圈遍歷
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));//以祖父為自己,遍歷
                } else {

//叔叔顏色為黑色

//自己是父親的右節點要左轉
                    if (x == rightOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateLeft(x);
                    }

//變換父親(黑色)和祖父顏色(紅色)
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);

//右轉祖父
                    rotateRight(parentOf(parentOf(x)));
                }
            } else {
                Entry<K,V> y = leftOf(parentOf(parentOf(x)));

//叔叔為紅色跟樓上一樣,做同樣操作,說明無論父親是左,還是右,叔叔跟父親要是同樣顏色,一樣變色就可以。
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {

//父親是祖父的右,自己是父親的左,右轉。樓上跟這個就是反著來。父親是祖父的左,自己是父親的右,左轉
                    if (x == leftOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateRight(x);
                    }

//跟上面的操作一樣。父親黑色,祖父紅色
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);

//左轉祖父(發現都是左右轉交替)
                    rotateLeft(parentOf(parentOf(x)));
                }
            }
        }
        root.color = BLACK;
    }

不強求記,畢竟像紅黑樹,外面大把的實現。

可以畫圖,進一步分析為啥,等有空再搞