用Js實現紅黑樹

學習紅黑樹,用js擼了一個node

紅黑樹是一個效率很高且穩定的數據結構,插入和刪除操做的時間複雜度都是logn。算法

紅黑樹的性質:數據結構

  1. 每個節點或者着紅色,或者着黑色
  2. 根是黑色
  3. 若是一個節點是紅色的,那麼它的子節點必須是黑色
  4. 從一個節點到一個Null節點(樹葉)的每一條簡單路徑必須包含相同數目的黑色節點

插入操做之後再補~函數

刪除操做(自頂向下的實現方式)

刪除操做是紅黑樹最難的部分,一般有兩種實現方式:自頂向下自底向上。《算法導論》裏使用的是自底向上的實現方式,對我而言至關晦澀,又看了幾篇相似的實現方式,須要羅列出至關多的情形,實現不易。《數據結構與算法 -- C語言實現》裏使用的是自頂向下的實現方式,但只討論了大體邏輯,並未給出具體實現,最後在這篇文章裏找到了完整的實現。自頂向下的方式確實簡單易懂,且很是巧妙,我也是用這種方式來實現紅黑樹的刪除操做學習

在此以前,先複習一下前面列出的紅黑樹的性質。刪除操做之因此複雜,是由於若是須要刪除的節點是黑色的,那麼直接刪除它會破壞性質4。所以,咱們須要保證刪除該節點以後,能有一種方式修復刪除後被破壞的部分。自底向上實現的思路是:先刪除節點,而後經過旋轉、變色等手段恢復破壞了紅黑樹性質的部分,而自頂向下實現的思路是:在查找須要刪除的節點的路徑上,保證每一個節點都是紅色的(若是不是就要經過變換讓它變成紅色,且不破壞樹的性質),若是它是要刪除的節點,就能夠安心地刪除它。就思路而言,顯然自底向上的方式更易理解,好像也更容易實現,但當你去處理修復過程時會發現有至關多的狀況須要考慮。而自頂向下的方式看似笨拙,卻可以經過巧妙的變換簡化變換的過程ui

總的來講,就是咱們要讓當前訪問的節點X變紅,而後再去考慮它是否是須要刪除的節點this

  1. 啓動條件

刪除是一個遞歸函數,在進入遞歸以前須要確保當前當前結構符合啓動條件。這裏的結構指以X爲中心的部分樹結構,可能包含P, S,GP, XL,XR,SL,SR。啓動條件以下:spa

即:X和它的兄弟節點S爲黑色,父親節點P爲紅色。實現insert時咱們作了一個特殊處理,構造了一個假的根節點,值爲負無窮,顏色爲黑色,所以全部的真實節點都在它的右子樹上。它的左節點是一個null節點(黑色),右節點是真正的根節點(必然是黑色)。而自頂向下刪除的第一步,就是把根節點塗成紅色,這樣就自然知足了啓動條件.net

  1. 若X有兩個黑色的節點。注意,當X爲葉子節點時也適用,由於葉子節點的子節點爲Null節點,是黑色3d

    這時還須要考察S的子節點的狀況才能決定如何變換,所以2還須要分幾種子情形

    2.1 符合2的條件,同時S也有兩個黑色的子節點

    這種狀況是最簡單的,讓X變紅只須要變換P,X,S的顏色便可。變換方法以下圖

    2.2 符合2的條件,且S有一個紅色的左子節點,一個黑色的右子節點

    這種狀況下,X變紅後,左邊就少了一個黑色的節點,只能從P的右子樹借一個過來。SL這個紅色的節點能夠利用起來,讓它移到P如今的位置。顯然這須要一個雙旋轉(SL位於整個旋轉路徑的內側),變換以下:

    2.3 符合2的條件,且S有一個黑色的左子節點,一個紅色的右子節點

    2.2相似,咱們要利用SR這個紅色節點,由於SR在整個旋轉路徑的外側,因此使用一個單旋轉

    2的這三種狀況完成變換以後,X已是紅色且保證了紅黑樹的性質,接下來就能夠判斷X是否爲須要刪除的節點了。這一步咱們也標記一下,叫它D-1好了

    D-1

    若是X不是要刪除的節點,那麼降低一層,即讓P = X, X = XL或者XR, S = X的另外一個子節點,而後進入下一個刪除循環(即1)。由於X是紅色,它的子節點必然爲黑色,因此符合啓動條件

    若是X正式須要刪除的節點,須要分兩種狀況

    1. X剛好是葉子節點,這種狀況直接刪除X便可,不會對紅黑樹的性質有任何影響
    2. X爲非葉子節點,若是直接刪除X,它的子節點應該如何與它的父節點對接是個很複雜的操做,因此咱們採用二叉查找樹的節點刪除方法:找到該節點的後繼或前驅(若是後繼不存在,再使用它的前驅節點),用它的值代替X的值,而後再去刪除它的後繼或前驅,反覆這個過程直到咱們須要刪除的節點是葉子節點

    ok,2這類大的狀況就處理好了,下面須要考慮X有至少一個紅色節點的狀況

  2. X至少有一個紅色節點。這種狀況須要改變一下思路,由於X有至少一個紅色節點,若是X不是要刪除的節點,就不必再把X變紅了,若是直接降低一層,X有很大機率直接落到紅色的節點上,這能節省不少時間

    因此對於3這種狀況,先判斷X是否爲要刪除的節點,這就分紅兩種狀況

    3.1 X不是要刪除的節點,那麼降低一層。這時X可能落到紅色節點上,也可能落到黑色節點上,兩種狀況都須要考慮

    3.1.1 X是紅色節點,那麼X已經符合D-1的刪除條件,能夠直接進入D-1

    3.1.2 X是黑色節點,這時須要做一次節點變換。爲了更直觀,這裏分紅兩個步驟,第一步降低一層,並讓X落到黑色節點上,第二步纔是變換

    此時X仍是黑色,並不知足進入D-1的條件,可是仔細看X節點的上下結構,P、SR、X構成的子樹正好知足1的啓動條件。因此下一步是進入1的刪除循環

    自此,3.1的全部狀況已經處理好了,下面咱們來看X是須要刪除的節點這種狀況

    3.2 X是須要刪除的節點。由於X有紅色的子節點,因此它不多是葉子節點,也就是說即便把它變紅也不能直接刪除。在這種狀況下,咱們在D-1的基礎上稍做修改:找到X的後繼或前驅,用它的值代替X的值,以後降低一層,再一次進入到3的邏輯

這就是自頂向下刪除須要考慮的全部情形了。我畫了一個流程圖,梳理了刪除的邏輯

按照這個流程圖來寫代碼,會很是清晰~

源碼

/** * RedBlackTreeNode.js * 紅黑樹的節點實現,比普通的二叉樹節點多了color屬性 */
class RedBlackTreeNode {
    constructor(data, color, lchild, rchild) {
        // validate color
        if (!Color[color]) {
            throw new Error(`color can only be RED or BLACK`);
        } 

        this.color = color;
        this.data = data;
        this.lchild = lchild;
        this.rchild = rchild; 
    } 
}

const Color = {
    "RED": "RED",
    "BLACK": "BLACK"
};

module.exports = {
    RedBlackTreeNode,
    Color,
};
複製代碼
/** * @file 紅黑樹實現 * @author YDSS * * Created on Sun May 27 2018 */

const { RedBlackTreeNode, Color } = require("./RedBlackTreeNode");

class RedBlackTree {
    constructor(arr) {
        this._initialize();
        this.create(arr);
    }

    _initialize() {
        // init NullNode
        this.NullNode = new RedBlackTreeNode(
            Number.NEGATIVE_INFINITY,
            Color.BLACK,
            null,
            null
        );
        this.NullNode.lchild = this.NullNode;
        this.NullNode.rchild = this.NullNode;
        // extra attr for recognizing the NullNode
        this.NullNode.type = "null";
        // init header
        this.header = new RedBlackTreeNode(
            Number.NEGATIVE_INFINITY,
            Color.BLACK,
            this.NullNode,
            this.NullNode
        );
        // init nodes to store parent, grandparent and grandgrandparent
        this.X = null;
        this.P = null;
        this.GP = null;
        this.GGP = null;
        // X's sister
        this.S = null;
    }

    create(arr) {
        arr.forEach(item => {
            this.header = this.insert(item);
        });
    }

    find(val) {
        return this._find(val, this.header);
    }

    _find(val, T) {
        if (!T) {
            return null;
        }

        if (val === T.data) {
            return T;
        }
        if (val > T.data) {
            return this._find(val, T.rchild);
        }
        if (val < T.data) {
            return this._find(val, T.lchild);
        }
    }

    insert(data) {
        this.X = this.P = this.GP = this.GGP = this.header;

        this.NullNode.data = data;
        while (data !== this.X.data) {
            this.GGP = this.GP;
            this.GP = this.P;
            this.P = this.X;

            if (data < this.X.data) {
                this.X = this.X.lchild;
            } else {
                this.X = this.X.rchild;
            }
            if (
                this.X.lchild.color === Color.RED &&
                this.X.rchild.color === Color.RED
            )
                this._handleReorient(data);
        }

        // duplicate
        if (this.X !== this.NullNode) {
            return this.NullNode;
        }

        this.X = new RedBlackTreeNode(
            data,
            Color.RED,
            this.NullNode,
            this.NullNode
        );
        if (data < this.P.data) {
            this.P.lchild = this.X;
        } else {
            this.P.rchild = this.X;
        }
        this._handleReorient(data);

        return this.header;
    }

    _handleReorient(data) {
        this.X.color = Color.RED;
        this.X.lchild.color = Color.BLACK;
        this.X.rchild.color = Color.BLACK;

        if (this.P.color === Color.RED) {
            this.GP.color = Color.RED;

            if (data < this.GP.data !== data < this.P.data)
                this.P = this._rotate(data, this.GP);
            this.X = this._rotate(data, this.GGP);
            this.X.color = Color.BLACK;
        }
        this.header.rchild.color = Color.BLACK;
    }

    /** * single rotate * * @param {*} data * @param {RedBlackTreeNode} Parent Parent Node of the subtree will rotate */
    _rotate(data, Parent) {
        if (data < Parent.data) {
            return (Parent.lchild =
                data < Parent.lchild.data
                    ? this._singleRotateWithLeft(Parent.lchild)
                    : this._singleRotateWithRight(Parent.lchild));
        } else {
            return (Parent.rchild =
                data > Parent.rchild.data
                    ? this._singleRotateWithRight(Parent.rchild)
                    : this._singleRotateWithLeft(Parent.rchild));
        }
    }

    _singleRotateWithLeft(T) {
        let root = T.lchild;

        T.lchild = root.rchild;
        root.rchild = T;

        return root;
    }

    _singleRotateWithRight(T) {
        let root = T.rchild;

        T.rchild = root.lchild;
        root.lchild = T;

        return root;
    }

    /** * find precursor node of this node * if this node doesn't have the left subtree, return null * * @param {*} data data of current node * @return {BinaryTreeNode|Null} */
    findPrecursor(node) {
        // let node = this.find(data);

        // if (!node) {
        // throw new Error(`node with data(${data}) is not in the tree`);
        // }

        if (!node.lchild) {
            return null;
        }

        let pre = node.lchild;
        let tmp;
        while (!this._isNilNode(tmp = pre.lchild)) {
            pre = tmp;
        }

        return pre;
    }

    /** * find successor node of this node * if this node doesn't have the right subtree, return null * * @param {BinaryTreeNode} current node * @return {BinaryTreeNode|Null} */
    findSuccessor(node) {
        // let node = this.find(data);

        // if (!node) {
        // throw new Error(`node with data(${data}) is not in the tree`);
        // }

        if (!node.rchild) {
            return null;
        }

        let suc = node.rchild;
        let tmp;
        while (!this._isNilNode(tmp = suc.lchild)) {
            suc = tmp;
        }

        return suc;
    }

    /** * delete node by means of top to down * * @param {*} val */
    delete(val) {
        // prepare for deleting
        this.header.color = Color.RED;
        this.GP = null;
        this.P = this.header;
        this.X = this.header.rchild;
        this.S = this.header.lchild;

        this._delete(val);
    }

    _delete(val) {
        if (
            this.X.lchild.color === Color.BLACK &&
            this.X.rchild.color === Color.BLACK
        ) {
            // S has two black children
            if (
                this.S.lchild.color === Color.BLACK &&
                this.S.rchild.color === Color.BLACK
            ) {
                this._handleRotateSisterWithTwoBlackChildren();
                // judge if X.data is what we are looking for
                this._handleDeleteXWhenXhasTwoBlackChildren(val);
            }
            // S has at last one red children
            else {
                // single rotate when S with it's red child in a line,
                // reference to avl rotate
                // 2.3
                if (
                    this.S.data > this.P.data ===
                    (this.S.rchild.color === Color.RED)
                ) {
                    this._rotate(this.S.data, this.GP);
                    // change color
                    this.P.color = Color.BLACK;
                    this.X.color = Color.RED;
                    this.S.color = Color.RED;
                    this.S.lchild.color = Color.BLACK;
                    this.S.rchild.color = Color.BLACK;
                    // judge if X.data is what we are looking for
                    this._handleDeleteXWhenXhasTwoBlackChildren(val);
                    // double rotate when S with it's red child in a z-shape line
                    // 2.2
                } else {
                    let firstData =
                        this.S.data < this.P.data
                            ? this.S.rchild.data
                            : this.S.lchild.data;
                    this._rotate(firstData, this.P);
                    this._rotate(this.S.data, this.GP);
                    // change color
                    this.P.color = Color.BLACK;
                    this.X.color = Color.RED;
                    // judge if X.data is what we are looking for
                    this._handleDeleteXWhenXhasTwoBlackChildren(val);
                }
            }
        } else {
            this._handleDeleteXWhenXhasAtLastOneRedChild(val);
        }
    }

    // 2.1
    _handleRotateSisterWithTwoBlackChildren() {
        this.P.color = Color.BLACK;
        this.X.color = Color.RED;
        this.S.color = Color.RED;
    }

    _handleDeleteXWhenXhasTwoBlackChildren(val) {
        if (this.X.data === val) {
            if (this._hasChild(this.X)) {
                val = this._replaceWithSuccessorOrPrecursor(val);
                this._descend(val);
                this._delete(val);
            } else {
                // delete X when it's a leaf
                this._deleteLeafNode(val, this.P);
            }
        } else {
            this._descend(val);
            this._delete(val);
        }
    }

    _handleDeleteXWhenXhasAtLastOneRedChild(val) {
        if (this.X.data === val) {
            val = this._replaceWithSuccessorOrPrecursor(val);
            this._descend(val);
        } else {
            this._descend(val);
        }
        // X is red, enter the phase of judging X's data
        if (this.X.color === Color.RED) {
            this._handleDeleteXWhenXhasTwoBlackChildren(val);
        } else {
            this._handleRotateWhenXIsBlackAndSisterIsRed();
            this._delete(val);
        }
    }

    // 3.1.2
    _handleRotateWhenXIsBlackAndSisterIsRed() {
        let curGP = this._rotate(this.S.data, this.GP);
        // change color
        this.S.color = Color.BLACK;
        this.P.color = Color.RED;
        // fix pointer of S and GP
        this.S = this.X.data > this.P.data ? this.P.lchild : this.P.rchild;
        this.GP = curGP;
    }

    _deleteLeafNode(val, parent) {
        if (parent.rchild.data === val) {
            parent.rchild = this.NullNode;
        } else {
            parent.lchild = this.NullNode;
        }
    }

    _hasChild(node) {
        return !this._isNilNode(node.lchild) || !this._isNilNode(node.rchild);
    }

    _isNilNode(node) {
        return node === this.NullNode;
    }

    /** * replace X with it's successor, * if it has no successor, instead of it's precursor * @param {*} val the delete data * * @return {*} updated delete data */
    _replaceWithSuccessorOrPrecursor(val) {
        let child = this.findSuccessor(this.X);
        if (!child) {
            child = this.findPrecursor(this.X);
        }
        this.X.data = child.data;

        return child.data;
    }

    /** * descend one floor * * @param {*} val the val of node will be deleted */
    _descend(val) {
        this.GP = this.P;
        this.P = this.X;

        if (val < this.X.data) {
            this.S = this.X.rchild;
            this.X = this.X.lchild;
        } else if (val > this.X.data) {
            this.S = this.X.lchild;
            this.X = this.X.rchild;
        }
        // val === this.X.data when X's successor or precursor
        // is it's child, in this situation it's wrong to choise
        // where X to go down by comparing val, cause X.data is equal
        // with new delete value
        else {
            if (val === this.X.lchild) {
                this.S = this.X.rchild;
                this.X = this.X.lchild;
            }
            else {
                this.S = this.X.lchild;
                this.X = this.X.rchild;
            }
        }
    }
}

module.exports = RedBlackTree;
複製代碼
相關文章
相關標籤/搜索