1. 程式人生 > >紅黑樹知識全面詳解之紅黑樹介紹及旋轉詳解

紅黑樹知識全面詳解之紅黑樹介紹及旋轉詳解

一、二叉查詢樹(二叉查詢樹、二叉搜尋樹 

二叉排序樹(Binary Sort Tree)或者是一棵空樹;或者是具有下列性質的二叉樹:

  1. 若左子樹不空,則左子樹上所有結點的值均小於它的根結點的值;
  2. 若右子樹不空,則右子樹上所有結點的值均大於它的根結點的值;
  3. 左、右子樹也分別為二叉排序樹;
  4. 沒有鍵值相等的節點 

二、紅黑樹

紅黑樹,一種二叉查詢樹,但在每個結點上增加一個儲存位表示結點的顏色,可以是Red或Black。通過對任何一條從根到葉子的路徑上各個結點著色方式的限制,紅黑樹確保沒有一條路徑會比其他路徑長出倆倍,因而是接近平衡的

因為一棵由n個結點隨機構造的二叉查詢樹的高度為lgn,所以順理成章,二叉查詢樹的一般操作的執行時間為O(lgn)。但二叉查詢樹若退化成了一棵具有n個結點的線性鏈後,則這些操作最壞情況執行時間為O(n)。

紅黑樹雖然本質上是一棵二叉查詢樹,但它在二叉查詢樹的基礎上增加了著色和相關的性質使得紅黑樹相對平衡,從而保證了紅黑樹的查詢、插入、刪除的時間複雜度最壞為O(log n),為了保證這個性質,所以紅黑樹有以下幾個特性:

紅黑樹雖然本質上是一棵二叉查詢樹,但它在二叉查詢樹的基礎上增加了著色和相關的性質使得紅黑樹相對平衡,從而保證了紅黑樹的查詢、插入、刪除的時間複雜度最壞為O(log n)

,為了保證這個性質,所以紅黑樹有以下幾個特性:

  1. 每個結點要麼是紅的要麼是黑的。
  2. 根結點是黑的。  
  3. 每個葉結點(葉結點即指樹尾端NIL指標或NULL結點)都是黑的。  
  4. 如果一個結點是紅的,那麼它的兩個兒子都是黑的。  
  5.  對於任意結點而言,其到葉結點樹尾端NIL指標的每條路徑都包含相同數目的黑結點。 

                                                                                              圖1.紅黑樹圖

三、紅黑樹的旋轉

紅黑樹的基本操作是新增、刪除。在對紅黑樹進行新增或刪除之後,都會用到旋轉方法。為什麼呢?道理很簡單,新增或刪除紅黑樹中的節點之後,紅黑樹就發生了變化,可能不滿足紅黑樹的5條性質,也就不再是一顆紅黑樹了,而是一顆普通的樹。而通過旋轉,可以使這顆樹重新成為紅黑樹。簡單點說,旋轉的目的是讓樹保持紅黑樹的特性。旋轉包括兩種:左旋和右旋。如下圖所示:

  1. 左旋

左旋的動態圖與靜態圖如下所示:左邊為左旋前,右邊為旋轉後

                                                                        圖2:左旋轉動態圖

                                                                     圖3:左旋轉靜態圖

左旋轉的虛擬碼如下,我們可以從中分析出左旋轉的流程

LEFT-ROTATE(T, x)
01  y ← right[x]            // 前提:這裡假設x的右孩子為y。下面開始正式操作
02  right[x] ← left[y]      // 將 “y的左孩子” 設為 “x的右孩子”,即 將β設為x的右孩子
03  p[left[y]] ← x          // 將 “x” 設為 “y的左孩子的父親”,即 將β的父親設為x
04  p[y] ← p[x]             // 將 “x的父親” 設為 “y的父親”
05  if p[x] = nil[T]
06  then root[T] ← y                 // 情況1:如果 “x的父親” 是空節點,則將y設為根節點
07  else if x = left[p[x]]
08            then left[p[x]] ← y    // 情況2:如果x是它父節點的左孩子,則將y設為“x的父節點的左孩子”
09            else right[p[x]] ← y   // 情況3:(x是它父節點的右孩子)將y設為“x的父節點的右孩子”
10  left[y] ← x             // 將 “x” 設為 “y的左孩子”
11  p[x] ← y                // 將 “x的父節點” 設為 “y”

示例:

                                                                    圖4:左旋轉示例圖

2.右旋轉

右旋的動態圖與靜態圖如下所示:左邊為右旋前,右邊為旋轉後

                                                                    圖5:右旋轉動態圖

                                                                         圖6:右旋轉靜態圖

右旋轉的虛擬碼如下

RIGHT-ROTATE(T, y)  
01  x ← left[y]             // 前提:這裡假設y的左孩子為x。下面開始正式操作
02  left[y] ← right[x]      // 將 “x的右孩子” 設為 “y的左孩子”,即 將β設為y的左孩子
03  p[right[x]] ← y         // 將 “y” 設為 “x的右孩子的父親”,即 將β的父親設為y
04  p[x] ← p[y]             // 將 “y的父親” 設為 “x的父親”
05  if p[y] = nil[T]       
06  then root[T] ← x                 // 情況1:如果 “y的父親” 是空節點,則將x設為根節點
07  else if y = right[p[y]]  
08            then right[p[y]] ← x   // 情況2:如果 y是它父節點的右孩子,則將x設為“y的父節點的左孩子”
09            else left[p[y]] ← x    // 情況3:(y是它父節點的左孩子) 將x設為“y的父節點的左孩子”
10  right[x] ← y            // 將 “y” 設為 “x的右孩子”
11  p[y] ← x                // 將 “y的父節點” 設為 “x”

示例: