200行代碼實現紅黑樹+原理介紹(全網最詳細)

首先手擼紅黑樹,需要明白紅黑樹是什麼東西?

FBI警告:

千萬不要去看源碼去理解紅黑樹!

千萬不要去看源碼去理解紅黑樹!

千萬不要去看源碼去理解紅黑樹!(重要的事情說三遍,因爲你會懵逼的,除非你是大神!!!)

其實紅黑樹其底層就是(特殊的,只是類似自平衡)二叉查找樹.就是這麼簡單

很多人懵逼就是不知道其底層結構.

網上很多教程提到的情況
可能有同學會問,什麼是二叉查找樹?什麼是平衡樹?對於這兩個有疑問的同學,可以看下我往期的博客

30分鐘看完數據結構和算法原理

首先要明白 紅黑樹五條性質: 
性質一:節點是紅色或者是黑色; 
在樹裏面的節點不是紅色的就是黑色的,沒有其他顏色,要不怎麼叫紅黑樹呢,是吧。 
性質二:根節點是黑色; 
根節點總是黑色的。它不能爲紅。 
性質三:每個葉節點(NIL或空節點)是黑色;

 

這個圖片就是一個紅黑樹,NIL節點是個空節點,並且是黑色的。 
性質四:每個紅色節點的兩個子節點都是黑色的(也就是說不存在兩個連續的紅色節點,黑色的可以連在一起); 
就是連續的兩個節點不能是連續的紅色,連續的兩個節點的意思就是父節點與子節點不能是連續的紅色。

性質五:從任一節點到其沒個葉節點的所有路徑都包含相同數目的黑色節點

下面的圖從根節點到每一個NIL節點的路徑中,都包含了相同數量的黑色節點。

 

這五條性質約束了紅黑樹,可以通過數學證明來證明,滿足這五條性質的二叉樹可以將查找刪除維持在對數時間內。 
當我們進行插入或者刪除操作時所作的一切操作都是爲了調整樹使之符合這五條性質。 
下面我們先介紹兩個基本操作,旋轉。 
旋轉的目的是將節點多的一支出讓節點給另一個節點少的一支,旋轉操作在插入和刪除操作中經常會用到,所以要熟記。


什麼是左旋?左旋就是將當前要旋轉的點進行逆時鐘轉90度,然後把要旋轉的點(未旋轉之前)的右節點的左子樹節點掛到要旋轉的點(已經旋轉完畢)的右子樹節點

好懵逼啊!!!不着急用一個動畫了解

左旋圖:

右旋恰好相反

右旋圖:

 

 

接下來講插入:

首先一個節點要插進來,顏色默認紅色,根據節點的大小,想按照二叉查找樹的規則插到相應的位置.

然後就要進行顏色變換了

變換規則如下

 

(下面這段話非常重要,一定要看完)

首先我們要明白有幾種情況需要注意一下(新插入的節點默認爲紅色!)

1.如果是插入到根節點,直接改爲黑色即可

2.如果插入的的父節點是黑色,直接插入即可

3.出現雙紅,也就是如果插入的節點的父節點是紅色.通俗講就是有兩個紅色的節點連在一起了.

   這裏面分兩種情況.

 (1).如果插入的節點的父節點的兄弟(叔叔節點)如果都是紅色,就直接改顏色. 把父節點和叔叔節點改爲黑色,把爺爺節點改爲紅色.

 (2)如果插入的節點的父節點的兄弟(叔叔節點)如果是黑色,下面要分兩種情況

     A.如果插入的節點的父節點是爺爺節點左子樹    

           如果當前節點作爲父節點的右子樹,則進行左旋

           怎麼左旋:左旋之前,當前指針移動到當前節點的父節點

           如果當前節點作爲父節點的左子樹,則進行右旋

            怎麼右旋:右旋之前,當前指針移動到當前節點的爺爺節點,然後把父節點改爲黑色,爺爺節點改爲紅色

     B.如果插入的節點的父節點是爺爺節點右子樹(這點很重要,網上的教程幾乎沒講,自己摸索出來的)

           如果當前節點作爲父節點的右子樹,則進行左旋(這裏跟上面情況一樣)

           怎麼左旋:左旋之前,當前指針移動到當前節點的爺爺節點,然後把父節點改爲黑色,爺爺節點改爲紅色

           如果當前節點作爲父節點的左子樹,則進行右旋(這裏跟上面情況一樣)

           怎麼右旋:右旋之前,當前指針移動到當前節點的父節點

又是好懵逼啊?!!!!

彆着急用一個例子分析

首先我已經有了一個紅黑樹,現在有一個節點6想往裏面插(我承認我沒有開車),先根據二叉查找樹的規則插到相應的位置,如下圖

然後插完你會發現6 和7 顏色一樣,連在一起了,破壞了紅黑樹的規則了(有毒.......)

出現這種問題,只能進行接下來的顏色變換了(根據3.1規則)

首先當前節點6 的父親7變爲黑色

再把當前節點6 的叔叔13變爲黑色

再把當前節點6 的爺爺12變爲紅色

然後再把指針移到12這個節點(也就是當前節點變成12了)

如下圖:

 然後又發現,5和12節點顏色一樣了(可怕...........)

接下來我們想進行上一步的做法,卻發現,當前節點(也就是12)的叔叔(也就是30)是黑色,(尷尬......) (根據3.2規則)

繼續先判斷,12節點的父節點作爲爺爺節點的左子樹(根據3.2.A規則),很明顯5節點作爲19的左子樹

接下來可能要進行左旋或者是右旋了,

如果當前節點作爲父節點的右子樹,則進行左旋

如果當前節點作爲父節點的左子樹,則進行右旋

很明顯,12這個節點爲5節點的右子樹,需要進行左旋.左旋之前,當前指針移動到當前節點的父節點(也就是5節點變爲當前節點)

 

 左旋後的圖:

 

接下來又發現,5和12節點顏色一樣了,然後5節點的叔叔是黑色,

繼續先判斷,5節點的父節點作爲爺爺節點的左子樹(根據3.2.A規則),很明顯12節點作爲19的左子樹

要左旋或者右旋,但是5節點作爲12的左子樹,需要右旋

右旋需要注意:

1.先把5的父親12節點變爲黑色,再把5的爺爺變爲紅色

2.指針移到爺爺,也就是當前節點變爲19節點了

3.以當前節點19進行右旋

右旋後的圖:

 紅黑樹插入完成

 

大家可以去下面的網址去練一下,紅黑樹的生成過程

https://www.cs.usfca.edu/~galles/visualization/RedBlack.html

創建紅黑樹代碼

//紅黑樹
 
//節點
class Node {
    constructor(value) {
        this.value = value
        this.left = null
        this.right = null
        this.parent = null
        this.color = "r"  //默認紅色
    }
}
 
//樹
class Tree {
 
    constructor() {
        this.root = null
        // this.findLeaf()
    }
 
    //添加節點
    add(data) {
        let node = new Node(data)
        //如果根節點爲空
        if (this.root == null) {
            node.color = "b"
            this.root = node
            return
        }
        let leaft = this.findLeaf(this.root, data)//獲取插入在哪個葉子節點上
        this.addLR(leaft, node)//插入到葉子節點
        this.changeToSuccess(node)//調用糾正
 
       // this.root = this.findRoot(this.root)//尋找根節點,因爲多次旋轉之後,一開始插入的節點早就不是根節點了
    }
 
    //查找要插入的葉子節點
    findLeaf(root, data) {
        if (root == null) {
            return null
        }
        if (data < root.value) {
            if (root.left == null) {
                return root
            }
            return this.findLeaf(root.left, data) //這裏的return可能有點繞, 最後相當把  return root 這個結果給返回
        } else {
 
            if (root.right == null) {
                return root
            }
            return this.findLeaf(root.right, data)
        }
 
    }
    //插在葉子節點的左邊還是右邊
    addLR(leaft, node) {
        if (leaft == null || node == null) {
            retutn
        }
        node.parent = leaft
        if (node.value < leaft.value) {
            //leaft.left = node
            this.addL(leaft, node)
            return
        }
        if (node.value > leaft.value) {
            //leaft.right = node
            this.addR(leaft, node)
            return
        }
    }
    //插在節點的左邊
    addL(leaft, node) {
        if (node != null) {
            node.parent = leaft
        }
        if (leaft != null) {
            leaft.left = node
        }
    }
    //插在節點的右邊
    addR(leaft, node) {
        if (node != null) {
            node.parent = leaft
        }
        if (leaft != null) {
            leaft.right = node
        }
    }
 
    /**
     * 尋找根節點,現在用不到了
     * @param {*} node 
     */
    findRoot(node) {
        if (node.parent == null) {
            return node
        } else {
            return this.findRoot(node.parent) //這裏一定要return ,相當於最後把  return node 返回
        }
        //  return node.parent == null ? node : this.findRoot(node.parent)
    }
 
    /**
     * 修正紅黑樹函數
     * @param {*} node 
     */
    changeToSuccess(node) {
        if (node == null) return
        //如果插入的第一個節點是root節點,直接改顏色
        if (node == this.root) {
            this.root.color = "b"
            return
        }
        //如果插入的節點是黑色,不用改顏色
        if (this.getNodeColor(node.parent) == "b") {
            return
        }
 
 
        //如果插入的父親和叔叔是紅色
        if (this.getNodeColor(node.parent.parent.left) == "r" &&
            this.getNodeColor(node.parent.parent.right) == "r") {
            node.parent.parent.left.color = "b"
            node.parent.parent.right.color = "b"
            node.parent.parent.color = "r"
            this.changeToSuccess(node.parent.parent)//當前節點變爲爺爺,這裏要遞歸一下,因爲有可能爺爺跟上面的節點出現雙紅
            return
        }
 
 
 
        //自己作爲父親的左子樹 ,父親作爲爺爺右子樹(右旋)
        if (node == node.parent.left && node.parent == node.parent.parent.right) {
            this.rotateR(node.parent)  //node.parent這個是當前的指針
            this.changeToSuccess(node.right)//尋找旋轉之前node.parent的位置(因爲是當前指針的位置)
            return
        }
        //自己作爲父親的左子樹 ,父親作爲爺爺左子樹(右旋+改顏色)
        if (node == node.parent.left && node.parent == node.parent.parent.left) {
            node.parent.color = "b" //先改顏色
            node.parent.parent.color = "r"
            this.rotateR(node.parent.parent)
            this.changeToSuccess(node.parent.right)
            return
        }
 
        //自己作爲父親的右子樹 ,父親作爲爺爺左子樹(左旋)
        if (node == node.parent.right && node.parent == node.parent.parent.left) {
            this.rotateL(node.parent)  //node.parent這個是當前的指針
            this.changeToSuccess(node.left)//尋找旋轉之前node.parent的位置
            return
        }
        //自己作爲父親的右子樹 ,父親作爲爺爺右子樹(左旋+改顏色)
        if (node == node.parent.right && node.parent == node.parent.parent.right) {
            node.parent.color = "b" //先改顏色
            node.parent.parent.color = "r"
            this.rotateL(node.parent.parent)
            this.changeToSuccess(node.parent.left)
            return
        }
    }
    /**
     * 獲取節點的顏色
     * @param {*} node 
     */
    getNodeColor(node) {
        return node == null ? "b" : node.color
    }
 
    //左旋
    rotateL(node) {
        let parent = node.parent
        let self = node
        let right = node.right
        let son = right.left
        if (parent != null) { // node節點父親爲空,說明node爲root節點
            this.addLR(parent, right)// node節點父親操作
            this.addR(node, son)    //node本身節點操作
            this.addLR(right, self) //node右子樹操作
        } else {
            this.addR(node, son)    //node本身節點操作
            this.addLR(right, self) //node右子樹操作
            
            right.parent = null   //這一步非常重要,如果不清空,後面想找父節點很難找
            this.root=right      //這個根節點要記錄
        }
 
    }
    //右旋
    rotateR() {
        let parent = node.parent
        let self = node
        let left = node.left
        let son = left.right
        if (parent != null) { // node節點父親爲空,說明node爲root節點
            this.addLR(parent, left)// node節點父親操作
            this.addR(node, son)    //node本身節點操作
            this.addLR(left, self) //node左子樹操作
        } else {
            this.addR(node, son)    //node本身節點操作
            this.addLR(left, self) //node左子樹操作
            
            left.parent = null   //這一步非常重要,如果不清空,後面想找父節點很難找
            this.root=left  //這個根節點要記錄
        }
 
    }
    /**
     * 
     * 樹遍歷,採用前序遍歷
     */
    printTree(){
        if(this.root!=null){
            console.log("採用前序遍歷")
            this.prePrint(this.root)
        } 
    }
    //前序遍歷
    prePrint(root){
        console.log("值:"+root.value,"顏色:"+root.color)
        if(root.left){
            this.prePrint(root.left)
        }
        if(root.right){
            this.prePrint(root.right)
        }
    }
}
 
// let tree = new Tree()

//測試
function test() {
    let tree = new Tree()
    let i = 0
    //插入5個數據
    while (i++ < 5) {
        tree.add(i)
    }
    //打印樹
    tree.printTree()
}
test() 

 

繼續完善紅黑樹功能

1.刪除功能(比較麻煩)

2.修改功能(前面實現,這個就簡單)

3.查詢功能(簡單)

4.添加功能(前面已經實現)

代碼如下:

 

//紅黑樹

//節點
class Node {
    constructor(value) {
        this.value = value
        this.left = null
        this.right = null
        this.parent = null
        this.color = "r"  //默認紅色
    }
}

//樹
class Tree {

    constructor() {
        this.root = null
        // this.findLeaf()
    }

    //添加節點
    add(data) {
        let node = new Node(data)
        //如果根節點爲空
        if (this.root == null) {
            node.color = "b"
            this.root = node
            return
        }
        let leaft = this.findLeaf(this.root, data)//獲取插入在哪個葉子節點上
        this.addLR(leaft, node)//插入到葉子節點
        this.changeToSuccess(node)//調用糾正

        // this.root = this.findRoot(this.root)//尋找根節點,因爲多次旋轉之後,一開始插入的節點早就不是根節點了
    }

    //查找要插入的葉子節點
    findLeaf(root, data) {
        if (root == null) {
            return null
        }
        if (data < root.value) {
            if (root.left == null) {
                return root
            }
            return this.findLeaf(root.left, data) //這裏的return可能有點繞, 最後相當把  return root 這個結果給返回
        } else {

            if (root.right == null) {
                return root
            }
            return this.findLeaf(root.right, data)
        }

    }
    //插在葉子節點的左邊還是右邊
    addLR(leaft, node) {
        if (leaft == null || node == null) {
            retutn
        }
        node.parent = leaft
        if (node.value < leaft.value) {
            //leaft.left = node
            this.addL(leaft, node)
            return
        }
        if (node.value > leaft.value) {
            //leaft.right = node
            this.addR(leaft, node)
            return
        }
    }
    //插在節點的左邊
    addL(leaft, node) {
        if (node != null) {
            node.parent = leaft
        }
        if (leaft != null) {
            leaft.left = node
        }
    }
    //插在節點的右邊
    addR(leaft, node) {
        if (node != null) {
            node.parent = leaft
        }
        if (leaft != null) {
            leaft.right = node
        }
    }

    /**
     * 尋找根節點,現在用不到了
     * @param {*} node 
     */
    findRoot(node) {
        if (node.parent == null) {
            return node
        } else {
            return this.findRoot(node.parent) //這裏一定要return ,相當於最後把  return node 返回
        }
        //  return node.parent == null ? node : this.findRoot(node.parent)
    }

    /**
     * 修正紅黑樹函數
     * @param {*} node 
     */
    changeToSuccess(node) {
        if (node == null) return
        //如果插入的第一個節點是root節點,直接改顏色
        if (node == this.root) {
            this.root.color = "b"
            return
        }
        //如果插入的節點是黑色,不用改顏色
        if (this.getNodeColor(node.parent) == "b") {
            return
        }


        //如果插入的父親和叔叔是紅色
        if (this.getNodeColor(node.parent.parent.left) == "r" &&
            this.getNodeColor(node.parent.parent.right) == "r") {
            node.parent.parent.left.color = "b"
            node.parent.parent.right.color = "b"
            node.parent.parent.color = "r"
            this.changeToSuccess(node.parent.parent)//當前節點變爲爺爺,這裏要遞歸一下,因爲有可能爺爺跟上面的節點出現雙紅
            return
        }

        //自己作爲父親的左子樹 ,父親作爲爺爺右子樹(右旋)
        if (node == node.parent.left && node.parent == node.parent.parent.right) {
            this.rotateR(node.parent)  //node.parent這個是當前的指針
            this.changeToSuccess(node.right)//尋找旋轉之前node.parent的位置(因爲是當前指針的位置)
            return
        }
        //自己作爲父親的左子樹 ,父親作爲爺爺左子樹(右旋+改顏色)
        if (node == node.parent.left && node.parent == node.parent.parent.left) {
            node.parent.color = "b" //先改顏色
            node.parent.parent.color = "r"
            this.rotateR(node.parent.parent)
            this.changeToSuccess(node.parent.right)
            return
        }

        //自己作爲父親的右子樹 ,父親作爲爺爺左子樹(左旋)
        if (node == node.parent.right && node.parent == node.parent.parent.left) {
            this.rotateL(node.parent)  //node.parent這個是當前的指針
            this.changeToSuccess(node.left)//尋找旋轉之前node.parent的位置
            return
        }
        //自己作爲父親的右子樹 ,父親作爲爺爺右子樹(左旋+改顏色)
        if (node == node.parent.right && node.parent == node.parent.parent.right) {
            node.parent.color = "b" //先改顏色
            node.parent.parent.color = "r"
            this.rotateL(node.parent.parent)
            this.changeToSuccess(node.parent.left)
            return
        }
    }
    /**
     * 獲取節點的顏色
     * @param {*} node 
     */
    getNodeColor(node) {
        return node == null ? "b" : node.color
    }

    //左旋
    rotateL(node) {
        let parent = node.parent
        let self = node
        let right = node.right
        let son = right.left
        if (parent != null) { // node節點父親爲空,說明node爲root節點
            this.addLR(parent, right)// node節點父親操作
            this.addR(node, son)    //node本身節點操作
            this.addLR(right, self) //node右子樹操作
        } else {
            this.addR(node, son)    //node本身節點操作
            this.addLR(right, self) //node右子樹操作

            right.parent = null   //這一步非常重要,如果不清空,後面想找父節點很難找
            this.root = right      //這個根節點要記錄
        }

    }
    //右旋
    rotateR() {
        let parent = node.parent
        let self = node
        let left = node.left
        let son = left.right
        if (parent != null) { // node節點父親爲空,說明node爲root節點
            this.addLR(parent, left)// node節點父親操作
            this.addR(node, son)    //node本身節點操作
            this.addLR(left, self) //node左子樹操作
        } else {
            this.addR(node, son)    //node本身節點操作
            this.addLR(left, self) //node左子樹操作

            left.parent = null   //這一步非常重要,如果不清空,後面想找父節點很難找
            this.root = left  //這個根節點要記錄
        }

    }
    /**
     * 
     * 樹遍歷,採用前序遍歷
     */
    printTree() {
        if (this.root != null) {
            console.log("採用前序遍歷")
            this.prePrint(this.root)
        }
    }
    //前序遍歷
    prePrint(root) {
        console.log("值:" + root.value, "顏色:" + root.color)
        if (root.left) {
            this.prePrint(root.left)
        }
        if (root.right) {
            this.prePrint(root.right)
        }
    }

    //尋找目標值的節點,高效
    findNode(root, data) {
        if (data == root.value) {
            return root
        }
        if (data < root.value) {  //如果目標值小於當前節點值,往左邊走
            if (root.left != null) {
                return this.findNode(root.left, data)   //最後相當於  return root
            }
        }
        if (data > root.value) { //如果目標值大於當前節點值,往右邊走
            if (root.right != null) {
                return this.findNode(root.right, data)
            }
        }
    }
    //全部前序遍歷一次,低效(這裏不建議用)
    findNode2(root, data) {
        let node = null
        if (data == root.value) {
            return root
        }

        if (root.left != null) {
            node = this.findNode(root.left, data)   //最後相當於  return root
            if (node != null) {
                return node   //找到一定要return,不然後面繼續運行,導致node又被清空
            }
        }


        if (root.right != null) {
            node = this.findNode(root.right, data)
            if (node != null) {
                return node
            }
        }


    }
    //判斷當前節點是父親的左子樹,並清空儲存父節點的值,從而達到刪除的目的
    clearNode(node) {
        if (node.parent.left == node) {
            node.parent.left = null
        } else {
            node.parent.right = null
        }
    }

    //刪除節點
    deleteNode(data, n = null) {
        //這個n=null 非常有用,後面遞歸的時候,不用再去根據值去找那個節點了
        if (this.root != null) {
            let node
            if (n == null) {
                node = this.findNode(this.root, data)
            } else {
                node = n
            }
            //如果是葉子節點直接刪除
            if (node.left == null && node.right == null) {
                //刪除操作 (紅黑樹刪除分幾種情況)
                //1.當被刪除元素爲紅時,直接刪除(這裏的節點都是葉子節點了)
                if (node.color == "r") {
                    this.clearNode(node)
                    return
                }
                //2.當被刪除元素爲黑且爲根節點時,直接刪除
                if (node.color == "b" && node.parent == null) {
                    this.clearNode(node)
                    return
                }
                //4.當被刪除元素爲黑,
                //且兄弟節點爲黑,兄弟節點兩個孩子也爲黑,父節點爲紅,此時,
                //交換兄弟節點與父節點的顏色
                if (node.parent.left == node) { //判斷是左兄弟還是右兄弟
                    if (node.color == "b" && this.getNodeColor(node.parent) == "r"
                        && this.getNodeColor(node.parent.right) == "b"
                        && this.getNodeColor(node.parent.right.left) == "b"
                        && this.getNodeColor(node.parent.right.right) == "b") {
                        let bcolor = node.parent.right.color
                        node.parent.right.color = this.getNodeColor(node.parent)
                        node.parent.color = bcolor
                        this.clearNode(node)
                        return

                    }
                } else {
                    if (node.color == "b" && this.getNodeColor(node.parent) == "r"
                        && this.getNodeColor(node.parent.left) == "b"
                        && this.getNodeColor(node.parent.left.left) == "b"
                        && this.getNodeColor(node.parent.left.right) == "b"
                    ) {

                        let bcolor = node.parent.left.color
                        node.parent.left.color = this.getNodeColor(node.parent)
                        node.parent.color = bcolor

                        this.clearNode(node)
                        return
                    }
                }


                //5.當被刪除元素爲黑、並且爲父節點的左支,且兄弟顏色爲黑,兄弟的右支爲紅色,(1,3,5,8)
                //交換兄弟與父親的顏色,並把兄弟的右支塗黑,並以父節點爲中心左轉
                if (node.color == "b" && node.parent.left == node && this.getNodeColor(node.parent.right) == "b"
                    && this.getNodeColor(node.parent.right.right) == "r") {
                    let bcolor = this.getNodeColor(node.parent.right)
                    node.parent.right.color = this.getNodeColor(node.parent)
                    node.parent.color = bcolor
                    node.parent.right.right.color = "b"
                    this.rotateL(node.parent)
                    this.clearNode(node)
                    return
                }
                //5.1當被刪除元素爲黑、並且爲父節點的右支,且兄弟顏色爲黑,兄弟的右支爲紅色,(1,2,4,5)
                //兄弟與兄弟的右子節點顏色互換,以兄弟節點爲中心進行左轉,然後就變成了規則6.1一樣了,在按照規則6.1進行旋轉
                if (node.color == "b" && node.parent.right == node &&
                    this.getNodeColor(node.parent.left) == "b"
                    && this.getNodeColor(node.parent.left.right) == "r") {
                    let bcolor = this.getNodeColor(node.parent.left)
                    node.parent.left.color = this.getNodeColor(node.parent.left.right.color)
                    node.parent.left.right.color = bcolor
                    this.rotateL(node.parent.left)
                    this.deleteNode(node.value, node)
                    return
                }

                //6.當被刪除元素爲黑、並且爲父節點的左支,且兄弟顏色爲黑,兄弟的左支爲紅色,
                //兄弟與兄弟的左子節點顏色互換,以兄弟節點爲中心進行右轉,然後就變成了規則5一樣了,在按照規則5進行旋轉
                if (node.color == "b" && node.parent.left == node && this.getNodeColor(node.parent.right) == "b"
                    && this.getNodeColor(node.parent.right.left) == "r"
                ) {
                    let bcolor = this.getNodeColor(node.parent.right)
                    node.parent.right.color = this.getNodeColor(node.parent.left)
                    node.parent.left.color = bcolor
                    this.rotateR(node.parent.right)
                    this.deleteNode(node.value, node)
                    return
                }

                //6.1當被刪除元素爲黑、並且爲父節點的右支,且兄弟顏色爲黑,兄弟的左支爲紅色,
                //交換兄弟與父親的顏色,並把兄弟的右支塗黑,並以父節點爲中心右轉(1,4,5,8)
                if (node.color == "b" && node.parent.right == node && this.getNodeColor(node.parent.left) == "b"
                    && this.getNodeColor(node.parent.left.left) == "r"
                ) {
                    let bcolor = this.getNodeColor(node.parent.left)
                    node.parent.left.color = this.getNodeColor(node.parent)
                    parent.color = bcolor
                    node.parent.left.right.color = "b"
                    this.rotateR(node.parent)
                    this.clearNode(node)
                    return
                }

                //8.被刪除元素爲黑且兄弟節點爲黑,兄弟節點的孩子爲黑,父親爲黑,
                //這個時候需要將兄弟節點變爲紅,再把父親看做那個被刪除的元素(只是看做,實際上不刪除),看看父親符和哪一條刪除規則
                if (node.parent.left == node) { //判斷是左兄弟還是右兄弟
                    if (node.color == "b" && this.getNodeColor(node.parent) == "b"
                        && this.getNodeColor(node.parent.right) == "b"
                        && this.getNodeColor(node.parent.right.left) == "b"
                        && this.getNodeColor(node.parent.right.right) == "b") {
                        this.deleteNode(node.parent.value, node.parent)
                        return
                    }
                } else {
                    if (node.color == "b" && this.getNodeColor(node.parent) == "b"
                        && this.getNodeColor(node.parent.left) == "b"
                        && this.getNodeColor(node.parent.left.left) == "b"
                        && this.getNodeColor(node.parent.left.right) == "b") {
                        this.deleteNode(node.parent.value, node.parent)
                        return
                    }
                }

                //9.當被刪除的元素爲黑,且爲父元素的左支,兄弟節點爲紅色
                //交換兄弟節點與父親結點的顏色,以父親結點進行左旋,就變成了情況4,
                if (node.color == "b" && node.parent.left == node
                    && this.getNodeColor(node.parent.right) == "r") {
                    let bcolor = this.getNodeColor(node.parent.right)
                    node.parent.right.color = this.getNodeColor(node.parent)
                    node.parent.color = bcolor
                    this.rotateL(node.parent)
                    this.deleteNode(node.value, node)
                    return
                }
            }

            //如果被刪除的元素有一個子節點,可以將子節點直接移到被刪除元素的位置(紅黑樹只換值)
            if (node.left == null || node.right == null) {
                if (node.left != null) { //有左子樹
                    let val = node.left.value
                    node.left.value = node.value
                    node.value = val
                    this.deleteNode(node.left.value, node.left)
                    return
                }

                if (node.right != null) { //有右子樹
                    let val = node.right.value
                    node.right.value = node.value
                    node.value = val

                    //3.當被刪除元素爲黑,且有一個右子節點爲紅時,將右子節點塗黑放到被刪除元素的位置
                    if (node.color == "b") {
                        if (node.right.color == "r") {
                            node.color = "b"
                            node.right.color = "r"
                        }
                    }
                    this.deleteNode(node.right.value, node.right)
                    return
                }
            }

            //如果有兩個子節點,這時候就可以把被刪除元素的右支的最小節點互換,(紅黑樹只換值)
            if (node.left != null && node.right != null) {
                let rsNode = this.findRightSmall(node.right)
                let val = node.value
                node.value = rsNode.value
                rsNode.value = val
                this.deleteNode(rsNode.value, rsNode)
                return
            }

        }
    }

    //尋找右子樹最左的節點
    findRightSmall(node) {
        if (node.left == null) {
            return node
        }
        return this.findRightSmall(node.left)
    }
    //修改數據,先刪除,後添加
    editNode(data, val) {
        this.deleteNode(data)
        this.add(val)
    }
    //查找節點
    selectNode(data) {
        if (this.root != null) {
            return this.findNode(this.root, data)
        }
    }

}

// let tree = new Tree()

//測試
function test() {
    let tree = new Tree()
    let i = 0
    //插入5個數據
    while (i++ < 8) {
        tree.add(i)//添加節點
    }
    // tree.deleteNode(7)//刪除7的節點
    // tree.editNode(1,60)//把1修改成60
    let node = tree.selectNode(5)//查詢5的節點
    tree.printTree()//打印
    console.log(node)
}
test()

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章