hashmap底層以及紅黑樹 終於懂了!!!

0. 引言

jdk 1.7裏面
HashMap中的數據結構是數組+單鏈表的組合,以鍵值對(key-value)的形式存儲元素的,通過put()和get()方法儲存和獲取對象。

在JDK1.6,JDK1.7中,HashMap採用位桶+鏈表實現,即使用鏈表處理衝突,同一hash值的鏈表都存儲在一個鏈表裏。但是當位於一個桶中的元素較多,即hash值相等的元素較多時,通過key值依次查找的效率較低。
而JDK1.8中,HashMap採用位桶+鏈表+紅黑樹實現,當鏈表長度超過閾值(8)時,將鏈表轉換爲紅黑樹,這樣大大減少了查找時間。

1. 紅黑樹

AVL樹是帶有平衡條件的二叉查找樹,一般是用平衡因子差值判斷是否平衡並通過旋轉來實現平衡,左右子樹樹高不超過1,和紅黑樹相比,AVL樹是嚴格的平衡二叉樹,平衡條件必須滿足(所有節點的左右子樹高度差不超過1)。不管我們是執行插入還是刪除操作,只要不滿足上面的條件,就要通過旋轉來保持平衡,而的英文旋轉非常耗時的,由此我們可以知道AVL樹適合用於插入與刪除次數比較少,但查找多的情況。
AVL侷限性
由於維護這種高度平衡所付出的代價比從中獲得的效率收益還大,故而實際的應用不多,更多的地方是用追求局部而不是非常嚴格整體平衡的紅黑樹。當然,如果應用場景中對插入刪除不頻繁,只是對查找要求較高,那麼AVL還是較優於紅黑樹。
紅黑樹
一種二叉查找樹,但在每個節點增加一個存儲位表示節點的顏色,可以是紅或黑(非紅即黑)。通過對任何一條從根到葉子的路徑上各個節點着色的方式的限制,紅黑樹確保沒有一條路徑會比其它路徑長出兩倍,因此,紅黑樹是一種弱平衡二叉樹(由於是弱平衡,可以看到,在相同的節點情況下,AVL樹的高度低於紅黑樹),相對於要求嚴格的AVL樹來說,它的旋轉次數少,所以對於搜索,插入,刪除操作較多的情況下,我們就用紅黑樹。

性質

  • 每個節點不是紅色就是黑色
  • 不可能有連在一起的紅色節點
  • 根節點是黑色
  • 葉子節點(null)都是黑色

變換規則

  • 改變顏色
  • 左旋
  • 右旋



package algorithm;


public class RedBlackTree<T extends Comparable<T>> {

    private RBTNode<T> mRoot;    // 根結點

    private static final boolean RED   = false;
    private static final boolean BLACK = true;

    public class RBTNode<T extends Comparable<T>> {
        boolean color;        // 顏色
        T key;                // 關鍵字(鍵值)
        RBTNode<T> left;    // 左孩子
        RBTNode<T> right;    // 右孩子
        RBTNode<T> parent;    // 父結點

        public RBTNode(T key, boolean color, RBTNode<T> parent, RBTNode<T> left, RBTNode<T> right) {
            this.key = key;
            this.color = color;
            this.parent = parent;
            this.left = left;
            this.right = right;
        }

        public T getKey() {
            return key;
        }

        public String toString() {
            return ""+key+(this.color==RED?"(R)":"B");
        }
    }

    public RedBlackTree() {
        mRoot=null;
    }

    private RBTNode<T> parentOf(RBTNode<T> node) {
        return node!=null ? node.parent : null;
    }
    private boolean colorOf(RBTNode<T> node) {
        return node!=null ? node.color : BLACK;
    }
    private boolean isRed(RBTNode<T> node) {
        return ((node!=null)&&(node.color==RED)) ? true : false;
    }
    private boolean isBlack(RBTNode<T> node) {
        return !isRed(node);
    }
    private void setBlack(RBTNode<T> node) {
        if (node!=null)
            node.color = BLACK;
    }
    private void setRed(RBTNode<T> node) {
        if (node!=null)
            node.color = RED;
    }
    private void setParent(RBTNode<T> node, RBTNode<T> parent) {
        if (node!=null)
            node.parent = parent;
    }
    private void setColor(RBTNode<T> node, boolean color) {
        if (node!=null)
            node.color = color;
    }

    /*
     * 前序遍歷"紅黑樹"
     */
    private void preOrder(RBTNode<T> tree) {
        if(tree != null) {
            System.out.print(tree.key+" ");
            preOrder(tree.left);
            preOrder(tree.right);
        }
    }

    public void preOrder() {
        preOrder(mRoot);
    }

    /*
     * 中序遍歷"紅黑樹"
     */
    private void inOrder(RBTNode<T> tree) {
        if(tree != null) {
            inOrder(tree.left);
            System.out.print(tree.key+" ");
            inOrder(tree.right);
        }
    }

    public void inOrder() {
        inOrder(mRoot);
    }


    /*
     * 後序遍歷"紅黑樹"
     */
    private void postOrder(RBTNode<T> tree) {
        if(tree != null)
        {
            postOrder(tree.left);
            postOrder(tree.right);
            System.out.print(tree.key+" ");
        }
    }

    public void postOrder() {
        postOrder(mRoot);
    }


    /*
     * (遞歸實現)查找"紅黑樹x"中鍵值爲key的節點
     */
    private RBTNode<T> search(RBTNode<T> x, T key) {
        if (x==null)
            return x;

        int cmp = key.compareTo(x.key);
        if (cmp < 0)
            return search(x.left, key);
        else if (cmp > 0)
            return search(x.right, key);
        else
            return x;
    }

    public RBTNode<T> search(T key) {
        return search(mRoot, key);
    }

    /*
     * (非遞歸實現)查找"紅黑樹x"中鍵值爲key的節點
     */
    private RBTNode<T> iterativeSearch(RBTNode<T> x, T key) {
        while (x!=null) {
            int cmp = key.compareTo(x.key);

            if (cmp < 0) 
                x = x.left;
            else if (cmp > 0) 
                x = x.right;
            else
                return x;
        }

        return x;
    }

    public RBTNode<T> iterativeSearch(T key) {
        return iterativeSearch(mRoot, key);
    }

    /* 
     * 查找最小結點:返回tree爲根結點的紅黑樹的最小結點。
     */
    private RBTNode<T> minimum(RBTNode<T> tree) {
        if (tree == null)
            return null;

        while(tree.left != null)
            tree = tree.left;
        return tree;
    }

    public T minimum() {
        RBTNode<T> p = minimum(mRoot);
        if (p != null)
            return p.key;

        return null;
    }
     
    /* 
     * 查找最大結點:返回tree爲根結點的紅黑樹的最大結點。
     */
    private RBTNode<T> maximum(RBTNode<T> tree) {
        if (tree == null)
            return null;

        while(tree.right != null)
            tree = tree.right;
        return tree;
    }

    public T maximum() {
        RBTNode<T> p = maximum(mRoot);
        if (p != null)
            return p.key;

        return null;
    }

    /* 
     * 找結點(x)的後繼結點。即,查找"紅黑樹中數據值大於該結點"的"最小結點"。
     */
    public RBTNode<T> successor(RBTNode<T> x) {
        // 如果x存在右孩子,則"x的後繼結點"爲 "以其右孩子爲根的子樹的最小結點"。
        if (x.right != null)
            return minimum(x.right);

        // 如果x沒有右孩子。則x有以下兩種可能:
        // (01) x是"一個左孩子",則"x的後繼結點"爲 "它的父結點"。
        // (02) x是"一個右孩子",則查找"x的最低的父結點,並且該父結點要具有左孩子",找到的這個"最低的父結點"就是"x的後繼結點"。
        RBTNode<T> y = x.parent;
        while ((y!=null) && (x==y.right)) {
            x = y;
            y = y.parent;
        }

        return y;
    }
     
    /* 
     * 找結點(x)的前驅結點。即,查找"紅黑樹中數據值小於該結點"的"最大結點"。
     */
    public RBTNode<T> predecessor(RBTNode<T> x) {
        // 如果x存在左孩子,則"x的前驅結點"爲 "以其左孩子爲根的子樹的最大結點"。
        if (x.left != null)
            return maximum(x.left);

        // 如果x沒有左孩子。則x有以下兩種可能:
        // (01) x是"一個右孩子",則"x的前驅結點"爲 "它的父結點"。
        // (01) x是"一個左孩子",則查找"x的最低的父結點,並且該父結點要具有右孩子",找到的這個"最低的父結點"就是"x的前驅結點"。
        RBTNode<T> y = x.parent;
        while ((y!=null) && (x==y.left)) {
            x = y;
            y = y.parent;
        }

        return y;
    }

    /* 
     * 對紅黑樹的節點(x)進行左旋轉
     *
     * 左旋示意圖(對節點x進行左旋):
     *      px                              px
     *     /                               /
     *    x                               y                
     *   /  \      --(左旋)-.           / \                #
     *  lx   y                          x  ry     
     *     /   \                       /  \
     *    ly   ry                     lx  ly  
     *
     *
     */
    private void leftRotate(RBTNode<T> x) {
        // 設置x的右孩子爲y
        RBTNode<T> y = x.right;

        // 將 “y的左孩子” 設爲 “x的右孩子”;
        // 如果y的左孩子非空,將 “x” 設爲 “y的左孩子的父親”
        x.right = y.left;
        if (y.left != null)
            y.left.parent = x;

        // 將 “x的父親” 設爲 “y的父親”
        y.parent = x.parent;

        if (x.parent == null) {
            this.mRoot = y;            // 如果 “x的父親” 是空節點,則將y設爲根節點
        } else {
            if (x.parent.left == x)
                x.parent.left = y;    // 如果 x是它父節點的左孩子,則將y設爲“x的父節點的左孩子”
            else
                x.parent.right = y;    // 如果 x是它父節點的左孩子,則將y設爲“x的父節點的左孩子”
        }
        
        // 將 “x” 設爲 “y的左孩子”
        y.left = x;
        // 將 “x的父節點” 設爲 “y”
        x.parent = y;
    }

    /* 
     * 對紅黑樹的節點(y)進行右旋轉
     *
     * 右旋示意圖(對節點y進行左旋):
     *            py                               py
     *           /                                /
     *          y                                x                  
     *         /  \      --(右旋)-.            /  \                     #
     *        x   ry                           lx   y  
     *       / \                                   / \                   #
     *      lx  rx                                rx  ry
     * 
     */
    private void rightRotate(RBTNode<T> y) {
        // 設置x是當前節點的左孩子。
        RBTNode<T> x = y.left;

        // 將 “x的右孩子” 設爲 “y的左孩子”;
        // 如果"x的右孩子"不爲空的話,將 “y” 設爲 “x的右孩子的父親”
        y.left = x.right;
        if (x.right != null)
            x.right.parent = y;

        // 將 “y的父親” 設爲 “x的父親”
        x.parent = y.parent;

        if (y.parent == null) {
            this.mRoot = x;            // 如果 “y的父親” 是空節點,則將x設爲根節點
        } else {
            if (y == y.parent.right)
                y.parent.right = x;    // 如果 y是它父節點的右孩子,則將x設爲“y的父節點的右孩子”
            else
                y.parent.left = x;    // (y是它父節點的左孩子) 將x設爲“x的父節點的左孩子”
        }

        // 將 “y” 設爲 “x的右孩子”
        x.right = y;

        // 將 “y的父節點” 設爲 “x”
        y.parent = x;
    }

    /*
     * 紅黑樹插入修正函數
     *
     * 在向紅黑樹中插入節點之後(失去平衡),再調用該函數;
     * 目的是將它重新塑造成一顆紅黑樹。
     *
     * 參數說明:
     *     node 插入的結點        // 對應《算法導論》中的z
     */
    private void insertFixUp(RBTNode<T> node) {
        RBTNode<T> parent, gparent;

        // 若“父節點存在,並且父節點的顏色是紅色”
        while (((parent = parentOf(node))!=null) && isRed(parent)) {
            gparent = parentOf(parent);

            //若“父節點”是“祖父節點的左孩子”
            if (parent == gparent.left) {
                // Case 1條件:叔叔節點是紅色
                RBTNode<T> uncle = gparent.right;
                if ((uncle!=null) && isRed(uncle)) {
                    setBlack(uncle);
                    setBlack(parent);
                    setRed(gparent);
                    node = gparent;
                    continue;
                }

                // Case 2條件:叔叔是黑色,且當前節點是右孩子
                if (parent.right == node) {
                    RBTNode<T> tmp;
                    leftRotate(parent);
                    tmp = parent;
                    parent = node;
                    node = tmp;
                }

                // Case 3條件:叔叔是黑色,且當前節點是左孩子。
                setBlack(parent);
                setRed(gparent);
                rightRotate(gparent);
            } else {    //若“z的父節點”是“z的祖父節點的右孩子”
                // Case 1條件:叔叔節點是紅色
                RBTNode<T> uncle = gparent.left;
                if ((uncle!=null) && isRed(uncle)) {
                    setBlack(uncle);
                    setBlack(parent);
                    setRed(gparent);
                    node = gparent;
                    continue;
                }

                // Case 2條件:叔叔是黑色,且當前節點是左孩子
                if (parent.left == node) {
                    RBTNode<T> tmp;
                    rightRotate(parent);
                    tmp = parent;
                    parent = node;
                    node = tmp;
                }

                // Case 3條件:叔叔是黑色,且當前節點是右孩子。
                setBlack(parent);
                setRed(gparent);
                leftRotate(gparent);
            }
        }

        // 將根節點設爲黑色
        setBlack(this.mRoot);
    }

    /* 
     * 將結點插入到紅黑樹中
     *
     * 參數說明:
     *     node 插入的結點        // 對應《算法導論》中的node
     */
    private void insert(RBTNode<T> node) {
        int cmp;
        RBTNode<T> y = null;
        RBTNode<T> x = this.mRoot;

        // 1. 將紅黑樹當作一顆二叉查找樹,將節點添加到二叉查找樹中。
        while (x != null) {
            y = x;
            cmp = node.key.compareTo(x.key);
            if (cmp < 0)
                x = x.left;
            else
                x = x.right;
        }

        node.parent = y;
        if (y!=null) {
            cmp = node.key.compareTo(y.key);
            if (cmp < 0)
                y.left = node;
            else
                y.right = node;
        } else {
            this.mRoot = node;
        }

        // 2. 設置節點的顏色爲紅色
        node.color = RED;

        // 3. 將它重新修正爲一顆二叉查找樹
        insertFixUp(node);
    }

    /* 
     * 新建結點(key),並將其插入到紅黑樹中
     *
     * 參數說明:
     *     key 插入結點的鍵值
     */
    public void insert(T key) {
        RBTNode<T> node=new RBTNode<T>(key,BLACK,null,null,null);

        // 如果新建結點失敗,則返回。
        if (node != null)
            insert(node);
    }


    /*
     * 紅黑樹刪除修正函數
     *
     * 在從紅黑樹中刪除插入節點之後(紅黑樹失去平衡),再調用該函數;
     * 目的是將它重新塑造成一顆紅黑樹。
     *
     * 參數說明:
     *     node 待修正的節點
     */
    private void removeFixUp(RBTNode<T> node, RBTNode<T> parent) {
        RBTNode<T> other;

        while ((node==null || isBlack(node)) && (node != this.mRoot)) {
            if (parent.left == node) {
                other = parent.right;
                if (isRed(other)) {
                    // Case 1: x的兄弟w是紅色的  
                    setBlack(other);
                    setRed(parent);
                    leftRotate(parent);
                    other = parent.right;
                }

                if ((other.left==null || isBlack(other.left)) &&
                    (other.right==null || isBlack(other.right))) {
                    // Case 2: x的兄弟w是黑色,且w的倆個孩子也都是黑色的  
                    setRed(other);
                    node = parent;
                    parent = parentOf(node);
                } else {

                    if (other.right==null || isBlack(other.right)) {
                        // Case 3: x的兄弟w是黑色的,並且w的左孩子是紅色,右孩子爲黑色。  
                        setBlack(other.left);
                        setRed(other);
                        rightRotate(other);
                        other = parent.right;
                    }
                    // Case 4: x的兄弟w是黑色的;並且w的右孩子是紅色的,左孩子任意顏色。
                    setColor(other, colorOf(parent));
                    setBlack(parent);
                    setBlack(other.right);
                    leftRotate(parent);
                    node = this.mRoot;
                    break;
                }
            } else {

                other = parent.left;
                if (isRed(other)) {
                    // Case 1: x的兄弟w是紅色的  
                    setBlack(other);
                    setRed(parent);
                    rightRotate(parent);
                    other = parent.left;
                }

                if ((other.left==null || isBlack(other.left)) &&
                    (other.right==null || isBlack(other.right))) {
                    // Case 2: x的兄弟w是黑色,且w的倆個孩子也都是黑色的  
                    setRed(other);
                    node = parent;
                    parent = parentOf(node);
                } else {

                    if (other.left==null || isBlack(other.left)) {
                        // Case 3: x的兄弟w是黑色的,並且w的左孩子是紅色,右孩子爲黑色。  
                        setBlack(other.right);
                        setRed(other);
                        leftRotate(other);
                        other = parent.left;
                    }

                    // Case 4: x的兄弟w是黑色的;並且w的右孩子是紅色的,左孩子任意顏色。
                    setColor(other, colorOf(parent));
                    setBlack(parent);
                    setBlack(other.left);
                    rightRotate(parent);
                    node = this.mRoot;
                    break;
                }
            }
        }

        if (node!=null)
            setBlack(node);
    }

    /* 
     * 刪除結點(node),並返回被刪除的結點
     *
     * 參數說明:
     *     node 刪除的結點
     */
    private void remove(RBTNode<T> node) {
        RBTNode<T> child, parent;
        boolean color;

        // 被刪除節點的"左右孩子都不爲空"的情況。
        if ( (node.left!=null) && (node.right!=null) ) {
            // 被刪節點的後繼節點。(稱爲"取代節點")
            // 用它來取代"被刪節點"的位置,然後再將"被刪節點"去掉。
            RBTNode<T> replace = node;

            // 獲取後繼節點
            replace = replace.right;
            while (replace.left != null)
                replace = replace.left;

            // "node節點"不是根節點(只有根節點不存在父節點)
            if (parentOf(node)!=null) {
                if (parentOf(node).left == node)
                    parentOf(node).left = replace;
                else
                    parentOf(node).right = replace;
            } else {
                // "node節點"是根節點,更新根節點。
                this.mRoot = replace;
            }

            // child是"取代節點"的右孩子,也是需要"調整的節點"。
            // "取代節點"肯定不存在左孩子!因爲它是一個後繼節點。
            child = replace.right;
            parent = parentOf(replace);
            // 保存"取代節點"的顏色
            color = colorOf(replace);

            // "被刪除節點"是"它的後繼節點的父節點"
            if (parent == node) {
                parent = replace;
            } else {
                // child不爲空
                if (child!=null)
                    setParent(child, parent);
                parent.left = child;

                replace.right = node.right;
                setParent(node.right, replace);
            }

            replace.parent = node.parent;
            replace.color = node.color;
            replace.left = node.left;
            node.left.parent = replace;

            if (color == BLACK)
                removeFixUp(child, parent);

            node = null;
            return ;
        }

        if (node.left !=null) {
            child = node.left;
        } else {
            child = node.right;
        }

        parent = node.parent;
        // 保存"取代節點"的顏色
        color = node.color;

        if (child!=null)
            child.parent = parent;

        // "node節點"不是根節點
        if (parent!=null) {
            if (parent.left == node)
                parent.left = child;
            else
                parent.right = child;
        } else {
            this.mRoot = child;
        }

        if (color == BLACK)
            removeFixUp(child, parent);
        node = null;
    }

    /* 
     * 刪除結點(z),並返回被刪除的結點
     *
     * 參數說明:
     *     tree 紅黑樹的根結點
     *     z 刪除的結點
     */
    public void remove(T key) {
        RBTNode<T> node; 

        if ((node = search(mRoot, key)) != null)
            remove(node);
    }

    /*
     * 銷燬紅黑樹
     */
    private void destroy(RBTNode<T> tree) {
        if (tree==null)
            return ;

        if (tree.left != null)
            destroy(tree.left);
        if (tree.right != null)
            destroy(tree.right);

        tree=null;
    }

    public void clear() {
        destroy(mRoot);
        mRoot = null;
    }

    /*
     * 打印"紅黑樹"
     *
     * key        -- 節點的鍵值 
     * direction  --  0,表示該節點是根節點;
     *               -1,表示該節點是它的父結點的左孩子;
     *                1,表示該節點是它的父結點的右孩子。
     */
    private void print(RBTNode<T> tree, T key, int direction) {

        if(tree != null) {

            if(direction==0)    // tree是根節點
                System.out.printf("%2d(B) is root\n", tree.key);
            else                // tree是分支節點
                System.out.printf("%2d(%s) is %2d's %6s child\n", tree.key, isRed(tree)?"R":"B", key, direction==1?"right" : "left");

            print(tree.left, tree.key, -1);
            print(tree.right,tree.key,  1);
        }
    }

    public void print() {
        if (mRoot != null)
            print(mRoot, mRoot.key, 0);
    }
}
package algorithm;


public class RBTreeTest {

    private static final int a[] = {10, 40, 30, 60, 90, 70, 20, 50, 80};
    private static final boolean mDebugInsert = false;    // "插入"動作的檢測開關(false,關閉;true,打開)
    private static final boolean mDebugDelete = false;    // "刪除"動作的檢測開關(false,關閉;true,打開)

    public static void main(String[] args) {
        int i, ilen = a.length;
        RedBlackTree<Integer> tree=new RedBlackTree<Integer>();

        System.out.printf("== 原始數據: ");
        for(i=0; i<ilen; i++)
            System.out.printf("%d ", a[i]);
        System.out.printf("\n");

        for(i=0; i<ilen; i++) {
            tree.insert(a[i]);
            // 設置mDebugInsert=true,測試"添加函數"
            if (mDebugInsert) {
                System.out.printf("== 添加節點: %d\n", a[i]);
                System.out.printf("== 樹的詳細信息: \n");
                tree.print();
                System.out.printf("\n");
            }
        }

        System.out.printf("== 前序遍歷: ");
        tree.preOrder();

        System.out.printf("\n== 中序遍歷: ");
        tree.inOrder();

        System.out.printf("\n== 後序遍歷: ");
        tree.postOrder();
        System.out.printf("\n");

        System.out.printf("== 最小值: %s\n", tree.minimum());
        System.out.printf("== 最大值: %s\n", tree.maximum());
        System.out.printf("== 樹的詳細信息: \n");
        tree.print();
        System.out.printf("\n");

        // 設置mDebugDelete=true,測試"刪除函數"
        if (mDebugDelete) {
            for(i=0; i<ilen; i++)
            {
                tree.remove(a[i]);

                System.out.printf("== 刪除節點: %d\n", a[i]);
                System.out.printf("== 樹的詳細信息: \n");
                tree.print();
                System.out.printf("\n");
            }
        }

        // 銷燬二叉樹
        tree.clear();
    }
}
發佈了57 篇原創文章 · 獲贊 5 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章