1. 程式人生 > 實用技巧 >KAL1 LINUX 官方文件之虛擬化 --- 安裝 VMware Tools

KAL1 LINUX 官方文件之虛擬化 --- 安裝 VMware Tools

平衡二叉樹

  平衡二叉查詢樹(Self-balancing binary search tree)又被稱為AVL樹(有別於AVL演算法),且具有以下性質:它是一 棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,並且左右兩個子樹都是一棵平衡二叉樹。

對節點10來說,左邊節點10的高度差為1,右邊10的高度差為2,所以右邊的不是平衡二叉查詢樹。

  

平衡因子

某結點的左子樹與右子樹的高度(深度)差即為該結點的平衡因子(BF,Balance Factor)。平衡二叉樹上所有結點的平衡因子只可能是 -1,0 或 1。如果某一結點的平衡因子絕對值大於1則說明此樹不是平衡二叉樹。

按照公式 平衡因子=左子樹的高度-右子樹的高度

1: 表示左子樹比右子樹高

-1 :表示右子樹比左子樹高

0: 表示左子樹和右子樹等高

上述右圖節點10的平衡因子 = 左子樹的高度 - 右子樹的高度 = 2 - 0 = 2;不滿足平衡二叉樹的特性。

紅黑樹的性質

  自平衡的查詢樹通過定義一些性質,將任意節點的左右子樹高度差控制在規定範圍內,以達到平衡狀態。以紅黑樹為例,紅黑樹通過如下的性質定義實現自平衡:

  1. 節點是紅色或黑色。
  2. 根是黑色。
  3. 所有葉子都是黑色(葉子是NIL節點)。
  4. 每個紅色節點必須有兩個黑色的子節點。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點。)
  5. 從任一節點到其每個葉子的所有簡單路徑都包含相同數目的黑色節點(簡稱黑高)。

正是紅黑樹的這5條性質,使一棵n個節點的紅黑樹始終保持了logn的高度,從而也就解釋了上面所說的“紅黑樹的查詢、插入、刪除的時間複雜度最壞為O(log n)”這一結論成立的原因。

(注:上述第3、5點性質中所說的葉子節點(NIL節點),包括wikipedia.演算法導論上所認為的葉子節點即為樹尾端的NIL指標,或者說NULL節點。然百度百科以及網上一些其它博文直接說的葉節點,則易引起誤會,因此葉節點非子節點)

如下圖所示,即是一顆紅黑樹(下圖引自wikipedia:http://t.cn/hgvH1l):

如上圖所示,葉子節點(NIL節點)它不包含資料而只充當樹在此結束的指示,這些節點在繪圖中經常被省略,望看到此文後的讀者朋友注意。

樹的旋轉

在對紅黑樹進行插入和刪除等操作時,對樹做了修改可能會破壞紅黑樹的性質。為了繼續保持紅黑樹的性質,可以通過對節點進行重新著色,以及對樹進行相關的旋轉操作,即通過修改樹中某些節點的顏色及指標結構,來達到對紅黑樹進行插入或刪除節點等操作後繼續保持它的性質或平衡的目的。

LL(右旋)

LL的意思是向左子樹(L)的左孩子(L)中插入新節點後導致不平衡,這種情況下需要右旋操作,而不是說LL的意思是右旋,後面的也是一樣。如圖,節點4插入到節點10的左子樹的左子樹中,導致節點10的平衡因子 = 2 - 0 = 2,不滿足平衡二叉樹的特性,需要進行右旋。

我們將這種情況抽象出來,得到下圖:

我們需要對節點y進行平衡的維護。步驟如下圖所示:

RR(左旋)

RR的意思是向右子樹(R)的左孩子(R)中插入新節點後導致不平衡,這種情況下需要左旋操作,而不是說RR的意思是左旋。如圖,節點13插入到節點10的右子樹的右子樹中,導致節點10的平衡因子 = 0 - 2 = -2,不滿足平衡二叉樹的特性,需要進行左旋。


我們將這種情況抽象出來,得到下圖:

我們需要對節點y進行平衡的維護。步驟如下圖所示:

LR


我們將這種情況抽象出來,得到下圖:

我們需要對節點y進行平衡的維護。步驟如下圖所示:

RL


我們將這種情況抽象出來,得到下圖:

我們需要對節點y進行平衡的維護。步驟如下圖所示:

插入

  紅黑樹的插入過程和二叉查詢樹插入過程基本類似,不同的地方在於,紅黑樹插入新節點後,需要進行調整,以滿足紅黑樹的性質。性質1規定紅黑樹節點的顏色要麼是紅色要麼是黑色,那麼在插入新節點時,這個節點應該是紅色還是黑色呢?答案是紅色,原因也不難理解。如果插入的節點是黑色,那麼這個節點所在路徑比其他路徑多出一個黑色節點,這個調整起來會比較麻煩(參考紅黑樹的刪除操作,就知道為啥多一個或少一個黑色節點時,調整起來這麼麻煩了)。如果插入的節點是紅色,此時所有路徑上的黑色節點數量不變,僅可能會出現兩個連續的紅色節點的情況。這種情況下,通過變色和旋轉進行調整即可,比之前的簡單多了。

情況一:

插入的新節點 N 是紅黑樹的根節點,這種情況下,我們把節點 N 的顏色由紅色變為黑色,性質2(根是黑色)被滿足。同時 N 被染成黑色後,紅黑樹所有路徑上的黑色節點數量增加一個,性質5(從任一節點到其每個葉子的所有簡單路徑都包含相同數目的黑色節點)仍然被滿足。

情況二:

N 的父節點是黑色,這種情況下,性質4(每個紅色節點必須有兩個黑色的子節點)和性質5沒有受到影響,不需要調整。

情況三:

N 的父節點是紅色(節點 P 為紅色,其父節點必然為黑色),叔叔節點 U 也是紅色。由於 P 和 N 均為紅色,所有性質4被打破,此時需要進行調整。這種情況下,先將 P 和 U 的顏色染成黑色,再將 G 的顏色染成紅色。此時經過 G 的路徑上的黑色節點數量不變,性質5仍然滿足。但需要注意的是 G 被染成紅色後,可能會和它的父節點形成連續的紅色節點,此時需要遞歸向上調整。

情況四:

N 的父節點為紅色,叔叔節點為黑色。節點 N 是 P 的右孩子,且節點 P 是 G 的左孩子。此時先對節點 P 進行左旋,調整 N 與 P 的位置。接下來按照情況五進行處理,以恢復性質4。

這裡需要特別說明一下,上圖中的節點 N 並非是新插入的節點。當 P 為紅色時,P 有兩個孩子節點,且孩子節點均為黑色,這樣從 G 出發到各葉子節點路徑上的黑色節點數量才能保持一致。既然 P 已經有兩個孩子了,所以 N 不是新插入的節點。情況四是由以 N 為根節點的子樹中插入了新節點,經過調整後,導致 N 被變為紅色,進而導致了情況四的出現。考慮下面這種情況(PR節點就是上圖的 N 節點):

如上圖,插入節點 N 並按情況三處理。此時 PR被染成了紅色,與 P 節點形成了連續的紅色節點,這個時候就需按情況四再次進行調整。

情況五:

N 的父節點為紅色,叔叔節點為黑色。N 是 P 的左孩子,且節點 P 是 G 的左孩子。此時對 G 進行右旋,調整 P 和 G 的位置,並互換顏色。經過這樣的調整後,性質4被恢復,同時也未破壞性質5。

插入總結

上面五種情況中,情況一和情況二比較簡單,情況三、四、五稍複雜。但如果細心觀察,會發現這三種情況的區別在於叔叔節點的顏色,如果叔叔節點為紅色,直接變色即可。如果叔叔節點為黑色,則需要選選擇,再交換顏色。當把這三種情況的圖畫在一起就區別就比較容易觀察了,如下圖:

HashMap中的紅黑樹

hashMap中連結串列轉為紅黑樹的兩大條件

  table陣列的大小>64

  連結串列中的元素的數量>8

造一些資料來觀察連結串列轉成紅黑樹的過程

public class Main {

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Map<String,String> map = new HashMap<String, String>();
        for (int i = 0; i < 65; i++) {
            map.put("sdf"+ String.valueOf(i),"s" + String.valueOf(i));
            if(getHashCodeIndex("sdf"+ String.valueOf(i)) == 26){
                System.out.println("index=======26時的key:" +  "sdf"+ String.valueOf(i));
            }
        }
        System.out.println(getHashCodeIndex("caocao"));
        System.out.println(getHashCodeIndex("sevennnn"));
        System.out.println(getHashCodeIndex("hate"));
        System.out.println(getHashCodeIndex("happyy"));
        System.out.println(getHashCodeIndex("hapqw"));
        System.out.println(getHashCodeIndex("mg"));
        System.out.println(getHashCodeIndex("vqv"));
        System.out.println(getHashCodeIndex("vaf"));
        System.out.println(getHashCodeIndex("vbde"));
        System.out.println("================");
        map.put("caocao","11");
        map.put("sevennnn","22");
        map.put("hate","33");
        map.put("happyy","44");
        map.put("hapqw","55");
        map.put("mg","66");
        map.put("vqv","77");
        map.put("vaf","88");
        map.put("vbde","99");
    }

    /**
     * @Description table大小為128,新增元素時在陣列中的下標index
     * @Param [key]
     * @return int
     * @date 2020/8/28 14:32
     * @auther Administrator
     */
    public static int getHashCodeIndex(String key){
        int h;
        h = key.hashCode();
        int hash = h ^ (h >>> 16);
        return 127 & hash;
    }
}

  輸出:

index=======26時的key:sdf15
index=======26時的key:sdf59
26
26
26
26
26
26
26
26
26
================

  所以,當map新增("vqv","77")時,連結串列中的元素將轉化成紅黑樹

/**
     * Replaces all linked nodes in bin at index for given hash unless
     * table is too small, in which case resizes instead.
     */
    final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
            do {
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    }

  treeifyBin(Node<K,V>[] tab, int hash)方法是將單向連結串列(Node)轉化成雙向連結串列(TreeNode)

  TreeNode類的繼承關係、屬性等,所以treeNode中是含有next屬性的。上述treeNode物件的parent,left,right屬性都是null

/**
     * Entry for Tree bins. Extends LinkedHashMap.Entry (which in turn
     * extends Node) so can be used as extension of either regular or
     * linked node.
     */
    static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        TreeNode<K,V> parent;  // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;    // needed to unlink next upon deletion
        boolean red;
        TreeNode(int hash, K key, V val, Node<K,V> next) {
            super(hash, key, val, next);
        }
}
/**
     * HashMap.Node subclass for normal LinkedHashMap entries.
     */
    static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }
static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
}

然後treeify(Node<K,V>[] tab)方法形成具體的紅黑樹,每插入一個節點圖示說明紅黑樹的變化

  插入第一個節點("sdf15","s15"),該節點是紅黑樹的根節點,插入情況的第一種

  插入第二個節點("sdf59","s59"),插入情況的第二種

  插入第三個節點("caocao","11"),

    因為新插入的節點是紅色,這樣節點2和節點3都是紅色,不符合紅黑樹特性,需要進行調整。首先將節點2改為黑色,節點1改為紅色。此時節點1的平衡因子為2,不滿足平衡二叉樹的特性,且根節點為紅色不滿足紅黑樹特性,以節點1進行右旋

    右旋過後的紅黑樹

  插入第四個節點("sevennnn","22")

連續兩個紅色節點不滿足紅黑樹特性,進行維護

  插入第五個節點("hate","33")

  插入第六個節點("happyy","44")

進行右旋

然後是左旋之前

進行左旋

  插入第七個節點("hapqw","55")

插入節點之後紅黑樹的維護

  插入第八個節點("mg","66")

  插入第九個節點("vqv","77")

插入節點後進行紅黑樹的維護

參考:

詳細圖文——AVL樹

紅黑樹詳解

紅黑樹詳細分析,看了都說好

AVL樹平衡因子詳解(左子樹-右子樹)