學習紅黑樹,用js擼了一個node
紅黑樹是一個效率很高且穩定的數據結構,插入和刪除操做的時間複雜度都是logn。算法
紅黑樹的性質:數據結構
插入操做之後再補~函數
刪除操做是紅黑樹最難的部分,一般有兩種實現方式:自頂向下和自底向上。《算法導論》裏使用的是自底向上的實現方式,對我而言至關晦澀,又看了幾篇相似的實現方式,須要羅列出至關多的情形,實現不易。《數據結構與算法 -- C語言實現》裏使用的是自頂向下的實現方式,但只討論了大體邏輯,並未給出具體實現,最後在這篇文章裏找到了完整的實現。自頂向下的方式確實簡單易懂,且很是巧妙,我也是用這種方式來實現紅黑樹的刪除操做學習
在此以前,先複習一下前面列出的紅黑樹的性質。刪除操做之因此複雜,是由於若是須要刪除的節點是黑色的,那麼直接刪除它會破壞性質4。所以,咱們須要保證刪除該節點以後,能有一種方式修復刪除後被破壞的部分。自底向上實現的思路是:先刪除節點,而後經過旋轉、變色等手段恢復破壞了紅黑樹性質的部分,而自頂向下實現的思路是:在查找須要刪除的節點的路徑上,保證每一個節點都是紅色的(若是不是就要經過變換讓它變成紅色,且不破壞樹的性質),若是它是要刪除的節點,就能夠安心地刪除它。就思路而言,顯然自底向上的方式更易理解,好像也更容易實現,但當你去處理修復過程時會發現有至關多的狀況須要考慮。而自頂向下的方式看似笨拙,卻可以經過巧妙的變換簡化變換的過程ui
總的來講,就是咱們要讓當前訪問的節點X變紅,而後再去考慮它是否是須要刪除的節點this
刪除是一個遞歸函數
,在進入遞歸以前須要確保當前當前結構符合啓動條件。這裏的結構指以X
爲中心的部分樹結構,可能包含P
, S
,GP
, XL
,XR
,SL
,SR
。啓動條件以下:spa
即:X和它的兄弟節點S爲黑色,父親節點P爲紅色。實現insert
時咱們作了一個特殊處理,構造了一個假的根節點,值爲負無窮,顏色爲黑色,所以全部的真實節點都在它的右子樹上。它的左節點是一個null節點(黑色),右節點是真正的根節點(必然是黑色)。而自頂向下刪除的第一步,就是把根節點塗成紅色,這樣就自然知足了啓動條件.net
若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正式須要刪除的節點,須要分兩種狀況
ok,2
這類大的狀況就處理好了,下面須要考慮X有至少一個紅色節點的狀況
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;
複製代碼