查詢演算法(下)

goodMorning發表於2019-03-29

簡介

上文 最後我們總結出二叉樹是無法自平衡的:原因是二叉樹不能調整節點位置。本文我們將介紹紅黑樹,一種能自平衡的二叉樹,為了方便理解紅黑樹,有必要先學習2-3樹。

2-3樹

2-3樹的定義:同時具有2-節點,3-節點的樹,而n-節點指一個節點最多有n個子節點。一個例子:

查詢演算法(下)
可以形象地稱2-3樹為“3叉樹”?,也因為2-3樹節點可以多保留一個值,所以具備了調整節點位置的能力。

查詢與插入

查詢

2-3樹的查詢和二叉樹無本質區別,仍然是比大小。

插入

插入操作2-3樹和二叉樹完全不同:由於節點可以容納2個健,所以先將健放在最底層節點,然後判斷健個數,如果超過2,則拆分推中(將大小健拆分成兩個節點,將中間鍵推到父節點內),此時父節點也增加了健個數,做相同的判斷以及拆分推中操作,直至根節點。下面看幾個例子:

不拆分推中(2-節點 -> 3-節點)

查詢演算法(下)
即插入前節點是1-節點,那麼整棵樹的高度是不變的。

拆分推中(3-節點 -> 3個2-節點)

查詢演算法(下)
上圖列出了插入後拆分推中的所有情況:

  • 根節點提升
  • 父節點是2節點時左側提升
  • 父節點是2節點時右側提升
  • 父節點是3節點時左側提升
  • 父節點是3節點時中間提升
  • 父節點是3節點時右側提升 其中只有第1種情況,整棵樹的高度增加,另外5種情況都是區域性變化,不影響其他部分的高度,所以自始至終,2-3樹都是一個平衡樹。

一個真實的例子

查詢演算法(下)
當插入元素D時,先儲存在最底層節點,因為健的個數超過2(A-C-D),所以將C推到父節點,父節點也做同樣的操作,最終根節點推中,整棵樹的高度+1。由此可見,因為是根節點推中,所以最底層節點的高度是一起增加的,所以樹是平衡的。

小結

總結下2-3樹和二叉樹的區別:二叉樹向下生長,而2-3樹是向上生長。

紅黑樹

終於到紅黑樹了,我們先給出紅黑樹的定義:

  • 紅黑樹是一棵二叉樹
  • 每個節點都有顏色屬性,紅或黑(該屬性也可以理解為和父節點連結的顏色)
  • 紅色屬性只能是左子節點(或左連結) 暫且不提紅黑樹的定義,先看下紅黑樹和2-3樹的關係。

和2-3樹的等價性

前面我們討論了2-3樹的定義、特點、操作,卻沒有實現程式碼,原因在於2-3樹實現相當複雜,複雜的資料結構和變化運算幾乎抵消演算法本身的優勢。 那怎麼辦呢?你猜對了,用紅黑樹表示2-3樹(想象紅色節點和父節點是一個整體,不就是3-節點嗎),因為每棵2-3樹確實存在至少一棵紅黑樹與之對應,並且紅黑樹是二叉樹,使得我們可以用二叉樹的操作實現2-3樹的變化。

查詢演算法(下)

插入

下面我們看下紅黑樹的插入過程(和上面2-3樹的插入過程對比下):

不拆分推中(2-節點 -> 3-節點)

查詢演算法(下)
很簡單:紅黑樹中1個節點變成了2個節點,對應2-3樹中2-節點變成3-節點。

拆分推中(3-節點 -> 3個2-節點)

查詢演算法(下)
查詢演算法(下)
查詢演算法(下)
上圖省略了插入的動作,但也不難理解:當紅黑樹中一個節點的存在兩個紅色連結時,最終都會變成3個2-節點,對應2-3樹中拆分推中。

左旋、右旋及顏色反轉

上一節圖例中,紅黑樹使用了左旋轉(rotate left)、右旋轉(rotate right)和顏色反轉(color flip)三種變化,我們一一解釋:

左旋轉

左旋轉目的是讓當前節點符合紅黑樹的定義(紅色節點只能在左分支)。

private Node rotateLeft(Node node) {
  Node right = node.right;
  right.red = node.red;
  node.right = right.left;
  node.red = true;
  right.left = node;
  return right;
}
複製程式碼

右旋轉

右旋轉的目的是為顏色反轉變化做準備。

private Node rotateRight(Node node) {
  Node left = node.left;
  left.red = node.red;
  node.left = left.right;
  node.red = true;
  left.right = node;
  return left;
}
複製程式碼

顏色反轉

顏色反轉對應2-3樹中的拆分推中操作。

private void flipColor(Node node) {
  node.left.red = node.right.red = false;
  node.red = true;
}
複製程式碼

我理解3種操作直接或間接在紅黑樹上實現2-3樹的操作。 還有一點:插入的元素是紅色,正好對應2-3中新元素新增在底層節點,而非底層節點的子節點。

程式碼

private Node put(Node curr, Key key, Value value) {
  if (curr == null) 
    return new Node(key, value, true);

  int r = key.compareTo(curr.key);
  if (r < 0) 
    curr.left = put(curr.left, key, value);
  else if (r > 0) 
    curr.right = put(curr.right, key, value);
  else
    curr.value = value;

  if (isRed(curr.right) && !isRed(curr.left)) 
    curr = rotateLeft(curr);
  if (isRed(curr.left) && isRed(curr.left.left)) 
     curr = rotateRight(curr);
  if (isRed(curr.left) && isRed(curr.right))
    flipColor(curr);

  return curr;
}
複製程式碼

可見,紅黑樹的插入函式對比二叉樹版本 ,僅僅多了最後的旋轉和顏色反轉操作。

小結

其實本節介紹的紅黑樹其實更應該叫做左傾紅黑樹,因為我們規定了紅色節點必須是左節點。左傾紅黑樹是紅黑樹的改進版,有興趣的同學可以自行百度。

總結

本文在前文二叉樹的基礎上先介紹了2-3樹、紅黑樹,介紹2-3樹是為紅黑樹做鋪墊,他們都是平衡樹,具備對數級別的時間複雜度。

引用

相關文章