1. 程式人生 > >Java實現資料結構——紅黑樹

Java實現資料結構——紅黑樹

紅黑樹定義

相比二叉查詢樹,紅黑樹中的節點多個顏色屬性。通過顏色屬性,確保了從根節點到每個葉子節點的簡單路徑,沒有一條路徑超過其他路徑2倍,近似於平衡。
性質:

  1. 每個節點或是紅色,或是黑色
  2. 根節點是黑色
  3. 每個葉節點是黑色
  4. 如果一個節點是紅色,那麼它的兩個子節點都是黑色
  5. 對於每個節點,從該節點到其所有後代葉節點的簡單路徑上,包含相同數目的黑色節點
    Java程式碼實現中,性質3:每個葉節點為黑色,預設無值葉節點指向Null

旋轉

通過旋轉操作,改變樹中節點的指標結構,並且保持二叉查詢樹性質(當前節點大於等於左子樹所有節點,小於右子樹所有節點)。

左旋

將當前節點移動到其左孩子節點的位置,右孩子移動到當前節點的位置
步驟:

  1. 關聯當前節點c和其右孩子的左孩子
  2. 關聯當前節點的雙親和右孩子
  3. 關聯當前節點和右孩子
    /**
     * 左旋
     *
     * @param root 根結點
     * @param c 當前結點
     * @return 根結點
     */
    public <E> RBTreeNode<E> rotateLeft(RBTreeNode<E> root, RBTreeNode<E> c) {
        RBTreeNode<E> r, cp, rl;
        if (c != null
&& (r = c.right) != null) { // 1.connect c and rl if ((rl = c.right = r.left) != null) { rl.parent = c; } // 2.connect r and cp if ((cp = r.parent = c.parent) == null) { (root = r).red = false; // done if c is root
} else if (cp.left == c) { cp.left = r; } else { cp.right = r; } // 3.connect c and r r.left = c; c.parent = r; } return root; }

右旋

將當前節點移動到其右孩子節點的位置,左孩子移動到當前節點的位置
步驟:

  1. 關聯當前節點和其左孩子的右孩子
  2. 關聯當前節點的雙親和其左孩子
  3. 關聯當前節點和其左孩子
    /**
     * 右旋
     *
     * @param root 根結點
     * @param c 當前結點
     * @return root 根結點
     */
    public <E> RBTreeNode<E> rotateRight(RBTreeNode<E> root, RBTreeNode<E> c) {
        RBTreeNode l, cp, lr;
        if (c != null && (l = c.left) != null) {
            // 1.connect c and lr
            if ((lr = c.left = l.right) != null) {
                lr.parent = c;
            }
            // 2.connect l and cp
            if ((cp = l.parent = c.parent) == null) {
                (root = l).red = false;
            } else if (cp.left == c) {
                cp.left = l;
            } else {
                cp.right = l;
            }
            // 3.connect c and l
            l.right = c;
            c.parent = l;
        }
        return root;
    }

插入

查詢樹的插入位置,可參考二叉查詢樹-新增元素
根據紅黑樹的基本性質,新增節點的顏色為紅色更為方便進行操作(黑色的話會破壞性質5)
在插入節點為紅色的前提下,破壞紅黑樹的性質有且僅有下面兩種情況:
- 插入節點為根節點(空樹新增節點)
- 插入節點的父節點為紅色

插入節點x的父節點xp是左孩子

迭代下面操作,直到x或xp為根節點:
1. 如果x的叔父節點u為紅色:將x的祖父節點xpp的黑色屬性賦予給它的兩個孩子,xpp設定為x節點。
2. 如果x的叔父節點u為黑色,且x為右孩子:以xp左旋(由於x和xp都是紅色,不影響黑高),將x設定為xp。此時,x為左孩子。
3. 如果x的叔父節點u為黑色,且x為左孩子:xp和xpp的顏色互換,並且,以xpp做右旋,平衡黑高
演算法導論截圖:
父節點為左孩子
步驟2和步驟3解決的問題:
叔父節點為黑色

插入節點x的父節點xp是右孩子

迭代下面操作,直到x或xp為根節點:
1. 如果x的叔父節點u為紅色:將x的祖父節點xpp的黑色屬性賦予給它的兩個孩子,xpp設定為x節點。
2. 如果x的叔父節點u為黑色,且x為左孩子:以xp右旋(由於x和xp都是紅色,不影響黑高),將x設定為xp。此時,x為右孩子。
3. 如果x的叔父節點u為黑色,且x為右孩子:xp和xpp的顏色互換,並且,以xpp做左旋,平衡黑高

Java程式碼實現

    @Override
    public boolean insert(E e) {
        // 1、關聯插入位置
        RBTreeNode<E> newNode = createRBTreeNode(e);
        RBTreeNode<E> parent = null; // 插入元素的父結點
        if (root == null) {
            root = newNode;
            root.red = false;
        } else {
            RBTreeNode<E> current = root;
            while (current != null) {
                if (e.compareTo(current.e) < 0) {
                    parent = current;
                    current = current.left;
                } else if (e.compareTo(current.e) > 0) {
                    parent = current;
                    current = current.right;
                } else {
                    return false;
                }
            }
            if (e.compareTo(parent.e) < 0) {
                parent.left = newNode;
            } else {
                parent.right = newNode;
            }
        }
        newNode.parent = parent;
        // 2、保持紅黑樹性質
        root = this.balanceInsertion(root, newNode);
        size++;
        return true;
    }
    /**
     * 平衡插入後的樹
     *
     * @param root 根結點
     * @param x 插入結點
     */
    public <E> RBTreeNode<E> balanceInsertion(RBTreeNode<E> root, RBTreeNode<E> x) {
        // 1.遍歷結點必為紅結點
        x.red = true;
        for (RBTreeNode<E> xp, xpp, xppl, xppr; ; ) {
            // 2-1.空樹
            if ((xp = x.parent) == null) {
                x.red = false;
                return x;
            }
            // 2-2.xp為黑結點 || xp為根結點
            else if (!xp.red || (xpp = xp.parent) == null) {
                return root;
            }
            // 2-3-1.xp is left-child
            // case1: a -> b
            if (xp == (xppl = xpp.left)) {
                // 2-3-1-1.x uncle is red
                if ((xppr = xpp.right) != null && xppr.red) {
                    xppr.red = false;
                    xp.red = false;
                    xpp.red = true;
                    x = xpp;
                }
                // 2-3-1-2.x uncle is black
                else {
                    // x is right-child
                    // case2: b -> c
                    if (x == xp.right) {
                        root = rotateLeft(root, x = xp);
                        xpp = (xp = x.parent) == null ? null : xp.parent;
                    }
                    // x is left-child
                    // case3: c -> d
                    if (xp != null) {
                        xp.red = false;
                        if (xpp != null) {
                            xpp.red = true;
                            root = rotateRight(root, xpp);
                        }
                    }
                }
            }
            // 2-3-2.xp is right-child
            else {
                // 2-3-2-1.x uncle is red
                if ((xppl = xpp.left) != null && xppl.red) {
                    xppl.red = false;
                    xp.red = false;
                    xpp.red = true;
                    x = xpp;
                }
                // 2-3-2-2.x uncle is black
                else {
                    // x is left-child
                    if (x == xp.left) {
                        root = rotateRight(root, x = xp);
                        xpp = (xp = x.parent) == null ? null : xp.parent;
                    }
                    // x is right-child
                    if (xp != null) {
                        xp.red = false;
                        if (xpp != null) {
                            xpp.red = true;
                            root = rotateLeft(root, xpp);
                        }
                    }
                }
            }
        }
    }

總結

當插入節點的叔父節點為黑色的時候,x和xp轉換為同側,即:(xpp.left=xp & xp.left=x)或(xpp.right=xp & xp.right = x)
GitHub檢視原始碼