紅黑樹的刪除

紅黑樹的刪除

可能出現的情形討論

刪除紅黑樹中一個結點,刪除的結點是其子結點狀態和顏色的組合。子結點的狀態有三種:無子結點、只有一個子結點、有兩個子結點。顏色有紅色和黑色兩種。因此共會有6種組合。node

組合1:被刪結點無子結點,且被刪結點爲紅色

此時直接將結點刪除便可,不破壞任何紅黑樹的性質。算法

組合2:被刪結點無子結點,且被刪結點爲黑色

處理方法略微複雜,稍後再議。segmentfault

組合3:被刪結點有一個子結點,且被刪結點爲紅色

clipboard.png

這種組合是不存在的,如圖假如被刪結點node只有一個有值的子結點value,而以value爲根結點的子樹中,必然還存在null結點,如此不符合紅黑樹的性質5,對每一個結點,從該結點到其全部後代葉結點的簡單路徑上,均包含相同數目的黑色結點。this

組合4:被刪結點有一個子結點,且被刪結點爲黑色

clipboard.png

這種組合下,被刪結點node的另外一個子結點value必然爲紅色,此時直接將node刪掉,用value代替node的位置,並將value着黑便可。spa

組合5&6:被刪結點有兩個子結點,且被刪結點爲黑色或紅色

當被刪結點node有兩個子結點時,先要找到這個被刪結點的後繼結點successor,而後用successor代替node的位置,同時着成node的顏色,此時至關於successor被刪。prototype

由於node有兩個子結點,因此successor必然在node的右子樹中,必然是下圖兩種形態中的一種。debug

clipboard.png

如果(a)的情形,用successor代替node後,至關於successor被刪,若successor爲紅色,則變成了組合1;若successor爲黑色,則變成了組合2。code

如果(b)的情形,用successor代替node後,至關於successor被刪,若successor爲紅色,則變成了組合1;若successor爲黑色,則變成了組合2或4。ip

綜上

若被刪結點是組合1或組合4的狀態,很容易處理;被刪結點不多是組合3的狀態;被刪結點是組合5&6的狀態,將變成組合1或組合2或組合4。rem

再議組合2:被刪結點無子結點,且被刪結點爲黑色

由於刪除黑色結點會破壞紅黑樹的性質5,因此爲了避免破壞性質5,在替代結點上額外增長一個黑色,這樣不違背性質5而只違背性質1,每一個結點或是黑色或是紅色。此時將額外的黑色移除,則完成刪除操做。

clipboard.png

而後再結合node原來的父結點father和其兄弟結點brother來分析。

情形一

brother爲黑色,且brother有一個與其方向一致的紅色子結點son,所謂方向一致,是指brother爲father的左子結點,son也爲brother的左子結點;或者brother爲father的右子結點,son也爲brother的右子結點。

clipboard.png

圖(c)中,白色表明隨即是黑或是紅,方形結點除了存儲自身黑色外,還額外存儲一個黑色。將brother和father旋轉,並從新上色後,變成了圖(d),方形結點額外存儲的黑色轉移到了father,且不違背任何紅黑樹的性質,刪除操做完成。

圖(c)中的情形顛倒過來,也是同樣的操做。

情形二

brother爲黑色,且brother有一個與其方向不一致的紅色子結點son

clipboard.png

圖(e)中,將son和brother旋轉,從新上色後,變成了圖(f),情形一。

圖(e)中的情形顛倒過來,也是同樣的操做。

情形三

brother爲黑色,且brother無紅色子結點

此時若father爲紅,則從新着色便可,刪除操做完成。如圖下圖(g)和(h)。

clipboard.png

此時若father爲黑,則從新着色,將額外的黑色存到father,將father做爲新的結點進行情形判斷,遇到情形1、情形二,則進行相應的調整,完成刪除操做;若是沒有,則結點一直上移,直到根結點存儲額外的黑色,此時將該額外的黑色移除,即完成了刪除操做。

clipboard.png

情形四

brother爲紅色,則father必爲黑色。

clipboard.png

圖(i)中,將brother和father旋轉,從新上色後,變成了圖(j),新的brother變成了黑色,這樣就成了情形1、2、三中的一種。若是將son和brother旋轉,不管怎麼從新上色,都會破壞紅黑樹的性質4或5,例如圖(k)。
圖(i)中的情形顛倒過來,也是同樣的操做。

代碼

// 結點
function Node(value) {
  this.value = value
  this.color = 'red' // 結點的顏色默認爲紅色
  this.parent = null
  this.left = null
  this.right = null
}

function RedBlackTree() {
  this.root = null
}

RedBlackTree.prototype.insert = function (node) {
  // 以二叉搜索樹的方式插入結點
  // 若是根結點不存在,則結點做爲根結點
  // 若是結點的值小於node,且結點的右子結點不存在,跳出循環
  // 若是結點的值大於等於node,且結點的左子結點不存在,跳出循環
  if (!this.root) {
    this.root = node
  } else {
    let current = this.root
    while (current[node.value <= current.value ? 'left' : 'right']) {
      current = current[node.value <= current.value ? 'left' : 'right']
    }
    current[node.value <= current.value ? 'left' : 'right'] = node
    node.parent = current
  }
  // 判斷情形
  this._fixTree(node)
  return this
}

RedBlackTree.prototype._fixTree = function (node) {
  // 當node.parent不存在時,即爲情形1,跳出循環
  // 當node.parent.color === 'black'時,即爲情形2,跳出循環
  while (node.parent && node.parent.color !== 'black') {
    // 情形3
    let father = node.parent
    let grand = father.parent
    let uncle = grand[grand.left === father ? 'right' : 'left']
    if (!uncle || uncle.color === 'black') {
      // 葉結點也是黑色的
      // 情形3.1
      let directionFromFatherToNode = father.left === node ? 'left' : 'right'
      let directionFromGrandToFather = grand.left === father ? 'left' : 'right'
      if (directionFromFatherToNode === directionFromGrandToFather) {
        // 具體情形一或二
        // 旋轉
        this._rotate(father)
        // 變色
        father.color = 'black'
        grand.color = 'red'
      } else {
        // 具體情形三或四
        // 旋轉
        this._rotate(node)
        this._rotate(node)
        // 變色
        node.color = 'black'
        grand.color = 'red'
      }
      break // 完成插入,跳出循環
    } else {
      // 情形3.2
      // 變色
      grand.color = 'red'
      father.color = 'black'
      uncle.color = 'black'
      // 將grand設爲新的node
      node = grand
    }
  }

  if (!node.parent) {
    // 若是是情形1
    node.color = 'black'
    this.root = node
  }
}

RedBlackTree.prototype._rotate = function (node) {
  // 旋轉 node 和 node.parent
  let y = node.parent
  if (y.right === node) {
    if (y.parent) {
      y.parent[y.parent.left === y ? 'left' : 'right'] = node
    }
    node.parent = y.parent
    if (node.left) {
      node.left.parent = y
    }
    y.right = node.left
    node.left = y
    y.parent = node
  } else {
    if (y.parent) {
      y.parent[y.parent.left === y ? 'left' : 'right'] = node
    }
    node.parent = y.parent
    if (node.right) {
      node.right.parent = y
    }
    y.left = node.right
    node.right = y
    y.parent = node
  }
}

RedBlackTree.prototype.remove = function (node) {
  while (true) {
    let {
      left,
      right,
      parent,
      color
    } = node
    // 組合1
    if (!left && !right && color === 'red') {
      parent[parent.left === node ? 'left' : 'right'] = null
      return this
    }
    // 組合2
    if (!left && !right && color === 'black') {
      if (parent) {
        let nullNode = new Node(null)
        nullNode.parent = parent
        nullNode.color = ['black', 'black']
        parent[parent.left === node ? 'left' : 'right'] = nullNode
        this._repairTree(nullNode)
      } else {
        this.root = null
      }
      return this
    }
    // 組合4
    if ((!left && right && color === 'black') || (left && !right && color === 'black')) {
      if (parent) {
        parent[parent.left === node ? 'left' : 'right'] = node.left || node.right
      } else {
        this.root = node.left || node.right
      }
      node[node.left ? 'left' : 'right'].color = 'black'
      return this
    }
    // 組合5&6
    if (left && right) {
      // 尋找後繼結點
      let successor = right
      while (successor.left) {
        successor = successor.left
      }
      // 用後繼結點代替node
      node.value = successor.value
      // 刪除后街結點
      node = successor
      /* let successorColor = successor.color
      let successorLeft = successor.left
      let successorRight = successor.right
      let successorParent = successor.parent
      // 用後繼節點代替node
      if (parent) {
        parent[parent.left === node ? 'left' : 'right'] = successor
      } else {
        this.root = successor
      }
      successor.parent = parent
      successor.left = left
      successor.right = right
      left.parent = successor
      right.parent = successor
      successor.color = color
      // 刪除successor
      node.left = successorLeft
      node.right = successorRight
      node.parent = successorParent
      node.color = successorColor */
    }
  }
}

RedBlackTree.prototype._repairTree = function (node) {
  while (node.parent) {
    let father = node.parent
    let brother = father[father.left === node ? 'right' : 'left']
    let son = brother[father.left === node ? 'right' : 'left']
    let daugh = brother[father.left === node ? 'left' : 'right']
    if (brother.color === 'black') {
      if (son && son.color === 'red') {
        // 情形一
        // 旋轉brother和father
        this._rotate(brother)
        // 變色
        brother.color = father.color
        father.color = 'black'
        son.color = 'black'
        // 移除black
        if (!node.value) {
          // nullNode
          father[father.left === node ? 'left' : 'right'] = null
        } else {
          node.color = 'black'
        }
        // 刪除操做完成
        return
      } else if (daugh && daugh.color === 'red') {
        // 情形二
        // 旋轉son和brother
        this._rotate(son)
        // 變色
        son.color = 'black'
        brother.color = 'red'
        // 變成情形一,繼續循環
      } else {
        // 情形三
        // brother無紅子結點
        if (father.color === 'red') {
          // father爲紅色
          father.color = 'black'
          brother.color = 'red'
          // 移除black
          if (!node.value) {
            // nullNode
            father[father.left === node ? 'left' : 'right'] = null
          } else {
            node.color = 'black'
          }
          // 刪除操做完成
          return
        } else {
          // father爲黑色
          father.color = ['black', 'black']
          brother.color = 'red'
          // 移除black
          if (!node.value) {
            // nullNode
            father[father.left === node ? 'left' : 'right'] = null
          } else {
            node.color = 'black'
          }
          node = father
          // 結點上移,繼續循環
        }
      }
    } else {
      // 情形四
      this._rotate(brother)
      brother.color = 'black'
      father.color = 'red'
      // 繼續循環
    }
  }
  this.root = node
  node.color = 'black'
}

RedBlackTree.prototype.find = function (value) {
  let current = this.root
  while (current.value !== value) {
    current = current[value >= current.value ? 'right' : 'left']
  }
  return current
}

let arr = [11, 2, 14, 1, 7, 15, 5, 8, 4]
let tree = new RedBlackTree()
arr.forEach(i => tree.insert(new Node(i)))
let findNode = tree.find(15)
tree.remove(findNode)
debugger

紅黑樹的插入

一點感悟

紅黑樹的插入和刪除都是經過分類討論來解決的,耐心的分析便可。爲數很少使用技巧的地方,是爲了維持紅黑樹的性質,在結點上存兩個黑色,固然這是算法導論告訴個人。

相關文章
相關標籤/搜索