紅黑樹的插入

紅黑樹的性質

一棵知足如下性質的二叉搜索樹是一棵紅黑樹node

  1. 每一個結點或是黑色或是紅色。算法

  2. 根結點是黑色的。this

  3. 每一個葉結點(NIL)是黑色的。spa

  4. 若是一個結點是紅色的,則它的兩個子結點都是黑色的。prototype

  5. 對每一個結點,從該結點到其全部後代葉結點的簡單路徑上,均包含相同數目的黑色結點。debug

性質1和性質2,不用作過多解釋。3d

clipboard.png

性質3,每一個葉結點(NIL)是黑色的。這裏的葉結點並非指上圖中結點1,5,8,15,而是指下圖中值爲null的結點,它們的顏色爲黑色,且是它們父結點的子結點。code

clipboard.png

性質4,若是一個結點是紅色的(圖中用白色代替紅色),則它的兩個子結點都是黑色的,例如結點2,5,8,15。可是,若是某結點的兩個子結點都是黑色的,該結點未必是紅色的,例如結點1blog

性質5,對每一個結點,從該結點到其全部後代葉結點的簡單路徑上,均包含相同數目的黑色結點。例如,從結點2到其全部後代葉結點的簡單路徑上,黑色結點的數量都爲2;從根結點11到其全部後代葉結點的簡單路徑上,黑色結點的數量都爲3。ip

這樣的一棵樹有什麼特色呢?

經過對任何一條從根到葉結點的簡單路徑上各個結點的顏色進行約束,紅黑樹確保沒有一條路徑會比其餘路徑長出2倍,由於是近似於平衡的。——《算法導論》

因爲性質4,紅黑樹中不會出現兩個紅色結點相鄰的情形。樹中最短的可能出現的路徑是都是黑色結點的路徑,樹中最長的可能出現的路徑是紅色結點和黑色結點交替的路徑。再結合性質5,每條路徑上均包含相同數目的黑色結點,因此紅黑樹確保沒有一條路徑會比其餘路徑長出2倍

紅黑樹的插入

首先以二叉搜索樹的方式插入結點,並將其着爲紅色。若是着爲黑色,則會違背性質5,不便調整;若是着爲紅色,可能會違背性質2或性質4,能夠經過相對簡單的操做,使其恢復紅黑樹的性質。

一個結點以二叉搜索樹的方式被插入後,可能出現如下幾種狀況:

情形1

插入結點後,無父結點,結點插入成爲根結點,違背性質2,將結點調整爲黑色,完成插入。

情形2

插入結點後,其父結點爲黑色,沒有違背任何性質,不用調整,完成插入。例以下圖中插入結點13

clipboard.png

情形3

插入結點後,其父結點爲紅色,違背了性質4,須要採起一系列的調整。例以下圖中插入結點4

clipboard.png

那麼一系列的調整是什麼呢?

若是插入結點node的父結點father爲紅色,則結點father必然存在黑色的父結點grandfather,由於若是結點father不存在父結點的話,就是根結點,而根結點是黑色的。那麼結點grandfather的另外一個子結點,咱們能夠稱之爲結點uncle,即結點father的兄弟結點。結點uncle可能爲黑色,也可能爲紅色。

先從最簡單的情形分析,由於複雜的情形能夠轉化爲簡單的情形,簡單的情形就是結點uncle爲黑色的情形。

clipboard.png

情形3.1

如上圖(a)中,情形是這樣的,node 爲紅,father 爲紅,grandfatheruncle 爲黑,α,β,θ,ω,η 都是結點對應的子樹。假設整棵二叉搜索樹中,只有nodefather因違背性質4而沒法成爲正常的紅黑樹,此時將圖(a)調整成圖(b),則能夠恢復成正常的紅黑樹。整個調整過程當中實際分爲兩步,旋轉變色

什麼是旋轉?

clipboard.png

如上圖(c)是一棵二叉搜索樹的一部分,其中 x, y 是結點,α,β,θ 是對應結點的子樹。由圖可知,α < x < β < y < θ ,即 α子樹中的全部結點都小於x,結點 x都小於 β子樹中的全部結點,β子樹中的全部結點的值都小於結點 y 的值,結點 y 的值都小於 θ子樹中的全部結點。在二叉搜索樹中,若是結點y的值比結點x的值大,那麼結點x在結點y的左子樹中,如圖(c);或者結點y在結點x的右子樹中,如圖(d)。故 α < x < β < y < θ ,也能夠用圖(d)的結構來表現。這就是旋轉,它不會破壞二叉搜索樹的性質。

node 爲紅,father 爲紅,grandfatheruncle 爲黑的具體情形一

圖(a)中,nodefather 的左子結點, fathergrand 的左子結點,node < father < θ < grand < uncle。這種情形中 father < grand,便可以表現爲 father 是 grand 的左子樹,也能夠表現爲 grand 是 father 的右子樹,故將圖(a)中 father 和 grand 旋轉,旋轉雖然不會破壞二叉搜索樹的性質,可是旋轉以後,會破壞紅黑樹的性質,因此還須要調整結點的顏色。

變色

因此圖(a)旋轉事後,還要將 grand 變爲紅色,father 變爲黑色,變成圖(b),完成插入。

node 爲紅,father 爲紅,grandfatheruncle 爲黑的具體情形二

nodefather 的右子結點, fathergrand 的右子結點,以下圖(e),就是具體情形一的翻轉。

clipboard.png

即,uncle < grand < θ < father < node ,將圖(e)中 father 和 grand 旋轉變色後,變成圖(f),完成插入。

node 爲紅,father 爲紅,grandfatheruncle 爲黑的具體情形三

nodefather 的右子結點, fathergrand 的左子結點,以下圖(m)。

clipboard.png

將圖(m)中 node 和 father 旋轉後,變成圖(n),將father看做新的node,就成爲了具體情形一,再次旋轉變色後,完成插入。

node 爲紅,father 爲紅,grandfatheruncle 爲黑的具體情形四

nodefather 的右子結點, fathergrand 的左子結點,以下圖(i),就是具體情形三的翻轉。

clipboard.png

將圖(i)中 node 和 father 旋轉後,變成圖(j),將father看做新的node,就成爲了具體情形二,再次旋轉變色後,完成插入。

情形3.2

nodefatheruncle 爲紅,grandfather 爲黑

clipboard.png

如上圖(k),不旋轉,而是將grand着紅,father和uncle着黑,同時將grand做爲新的node,進行情形的判斷。若是grand做爲新的node後,變成了情形2,則插入完成;若是變成了情形3.1,則調整後,插入完成;若是還是情形3.2,則繼續將grand,father和uncle變色,和node結點上移,若是新的node結點沒有父節點了,則變成了情形1,將根結點着爲黑色,那麼插入完成。

綜上

node的情形 操做
情形1 node爲紅,無father 將node從新着色
情形2 node爲紅,father爲黑
情形3.1 node,father爲紅,grand,uncle爲黑 旋轉一次或兩次,並從新着色
情形3.2 node,father,uncle爲紅,grand爲黑 將father, uncle,grand從新着色, grand做爲新的node

代碼

// 結點
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
  }
}

let arr = [11, 2, 14, 1, 7, 15, 5, 8, 4, 16]
let tree = new RedBlackTree()
arr.forEach(i => tree.insert(new Node(i)))
debugger
相關文章
相關標籤/搜索