1. 程式人生 > >筆記:紅黑樹旋轉和插入

筆記:紅黑樹旋轉和插入

紅黑樹

紅黑樹是每個節點都帶有顏色屬性的二叉查詢樹,顏色或紅色或黑色。在二叉查詢樹強制一般要求以外,對於任何有效的紅黑樹我們增加了如下的額外要求:
- 性質1. 節點是紅色或黑色。
- 性質2. 根節點是黑色。
- 性質3. 每個葉節點(NIL節點,空節點)是黑色的。
- 性質4. 每個紅色節點的兩個子節點都是黑色。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點)
- 性質5. 從任一節點到其每個葉子的所有路徑都包含相同數目的黑色節點。

旋轉

左旋

image

    /**
     * 左旋,
     * @param current
     */
    private
void rotateLeft(Node current) { Node right = current.rightChild; Node temp = right.leftChild; right.leftChild = current; current.rightChild = temp; }

右旋

image

    /**
     * 右旋
     * @param current
     */
    private void rotateRight(Node current) {
        Node left = current.leftChild;
        Node temp = left.rightChild;
        left.rightChild = current;
        current.leftChild = temp;
    }

紅黑樹的插入

插入過程和二叉樹的插入過程相同,先找到插入的節點然後插入。不同的是在插入後,需要通過旋轉和著色操作,使紅黑樹滿足規則,即滿足黑色平衡。

插入一個節點

    /**
     * 插入一個節點
     * @param node
     * @return
     */
    public void insertNode(Node node) throws KeyAlreadyExistsException {
        if (root == null) {
            node.color = NodeColor.BLACK;
            root = node;
        } else
{ Stack<Node> parentStack = new Stack<Node>(); Node current = root; while (current != null) { parentStack.push(current); if (node.isBiggerThan(current)) { current = current.rightChild; } else if (node.isSmallerThan(current)){ current = current.leftChild; } else { throw new KeyAlreadyExistsException("this key is already exists!!!"); } } Node parent = parentStack.peek(); if (node.isBiggerThan(parent)) { parent.rightChild = node; } else { parent.leftChild = node; } //調整使樹平衡 insertFixUp(parentStack, node); } }

調整和著色

插入新節點時,預設新節點為紅色節點。於是有如下兩種情況:
1. 父節點為黑色,滿足黑色平衡,不需要進行調整。
2. 父節點為紅色,不滿足規則,需要進行調整。

當插入節點的父節點為紅色時,出現連續紅色節點,不滿足性質4,因此需要調整。調整時分為如下兩種情況:
1. 插入節點的叔叔節點為紅色,此時需要通過著色操作,將父節點和叔叔節點變為黑色,並將祖父節點變為紅色,將祖父節點作為“當前節點”。並繼續對“當前節點”進行調整。
2. 插入節點的叔叔節點為黑色,需要進行旋轉,使樹滿足規則。

針對上面的第二種情況,也就是叔叔節點為黑色時,可分為如下兩種情況進行旋轉。

  • 1、插入節點在外側(插入節點和父節點方向相同:同在左側、同在右側),如下圖所示:
    image
    對於這種情況,只需要對祖父節點進行一次旋轉即可修正。以祖父節點為軸向插入節點一側的相反方向旋轉一次,然後將原父節點顏色置為黑色,將原祖父節點顏色置為紅色。即可滿足所有的性質。這裡以插入節點在左側為例,先將祖父節點右旋(插入節點的相反方向),然後將原父節點置為紅色,祖父節點置為黑色。調整後,滿足所有性質。
    image

  • 2、 插入節點在內側(插入節點和父節點方向相反),如下圖所示:
    image
    對於插入節點在內側的情況,可以以父節點為軸,向相反方向旋轉一次。然後將原父節點作為“當前節點”,於是就變為情況1,進行情況1的操作即可。這裡以插入節點在右側為例,以父節點為軸,左旋一次。然後以父節點為當前節點,變為情況1。
    image

對於以上情況,在旋轉時需要用到父節點和祖父節點,將旋轉過的子樹與原樹連線,還需要用到增祖父節點。因此,如果節點中沒有存放父節點的資訊時,建議將使用棧來存放當前節點的所有祖先節點。調整樹的程式碼如下:

    /**
     * 調整紅黑樹,使其滿足黑色高度相同
     * @param parentStack
     * @param newNode
     */
    private void insertFixUp(Stack<Node> parentStack, Node newNode) {

        Node parent = parentStack.pop();
        Node current = newNode;
        Node uncle = null;
        Node gParent = null;

        while (parent != null) {
            //父節點黑色
            if (parent.isBlack()) {
                return ;
            }
            gParent = parentStack.pop();
            uncle = gParent.getAnotherChild(parent);

            if (uncle != null && uncle.isRed()) {
                uncle.color = NodeColor.BLACK;
                parent.color = NodeColor.BLACK;
                gParent.color = NodeColor.RED;
                current = gParent;
                if (parentStack.empty()) {
                    current.color=NodeColor.BLACK;
                    parent = null;
                } else {
                    parent = parentStack.pop();
                }
            } else {
                // uncle is null or uncle is black, rotate and break while
                //情況1:插入節點在內側時,反向旋轉父節點,使其到外側,變為情況2
                if (parent.isLeftOf(gParent) && current.isRightOf(parent)) {
                    rotateLeft(parent);
                    gParent.leftChild = current;
                    current = parent;
                    parent = gParent.leftChild;
                } else if (parent.isRightOf(gParent) && current.isLeftOf(parent)) {
                    rotateRight(parent);
                    gParent.rightChild = current;
                    current = parent;
                    parent = gParent.rightChild;
                }
                //情況2:插入節點在外側時,反向旋轉祖父節點,完成調整
                if (parent.isRightOf(gParent) && current.isRightOf(parent)) {
                    rotateLeft(gParent);
                    gParent.color = NodeColor.RED;
                    parent.color = NodeColor.BLACK;
                    current = parent;
                } else if (parent.isLeftOf(gParent) && current.isLeftOf(parent)) {
                    rotateRight(gParent);
                    gParent.color = NodeColor.RED;
                    parent.color = NodeColor.BLACK;
                    current = parent;
                }
                parent = null;
            }
        } // end while
        if (parentStack.empty()) {
            root = current;
        }
        if (!parentStack.empty()){
            if (gParent.isRightOf(parentStack.peek())){
                parentStack.peek().rightChild = current;
            } else {
                parentStack.peek().leftChild = current;
            }
        }
    }