樹的概述
樹是一種非常常用的數據結構,樹與前面介紹的線性表,棧,隊列等線性結構不同,樹是一種非線性結構
1.樹的定義和基本術語
計算機世界里的樹,是從自然界中實際的樹抽象而來的,它指的是N個有父子關系的節點的有限集合。對于這個有限的節點集合而言,它滿足如下條件:
當N=0時,改節點集合為空,這課樹也被稱為空樹
在任意的非空樹中,有且僅有一個根(root)節點
當N>1時,除根節點以外的其余節點可分為M個互為相交的有限集合T1,T2,...,Tm,其中的每個集合本身又是一棵樹,并稱其為根的子樹(subtree)。
從上面定義可以發現樹的遞歸特性:一棵樹由根和若干棵子樹組成,而每棵子樹又由若干棵更小的子樹組成。
樹中任一節點可以有0或多個子節點,但只能有一個父節點。根節點是一個特例,根節點沒有父節點,葉子節點沒有子節點。樹中每個節點既可以是其上一級節點的子節點,也可以是下一級節點的父節點,因此同一個節點既可以是父節點,也可以是子節點(類似于一個人—————他既是他兒子的父親,又是他父親的兒子)。
很顯然,父子關系是一種非線性關系,所以樹結構是非線性結構。
如果按節點是否包含子節點來分,節點可以分成以下兩種:
普通節點:包含子節點的節點
葉子節點:沒有子節點的節點,因此葉子節點不可作為父節點
如果按節點是否具有唯一的父節點來分,節點有可分為如下兩種:
根節點:沒有父節點的節點,根節點不可作為子節點
普通節點:具有唯一父節點的節點
一棵樹只能有一個根節點,如果一棵樹有了多個根節點,那么它已經不再是一棵樹了,而是多棵樹的集合,有時也被稱為森林。示意圖如下:
tree.PNG
與樹有關的術語有如下一些:
節點:樹的最基本組成單元,通常包括一個數據元素及若干指針用于指向其他節點。
節點的度:節點擁有的子樹的個數被稱為節點的度(degree)
樹的度:樹中所有節點的度的最大值就是該樹的度
葉子節點:度為0的節點被稱為葉子節點或終端節點
分支節點:度不為0的節點被稱為分支節點或非終端節點
子節點,父節點,兄弟節點:節點的子樹的根被稱為該節點的子節點,而該節點稱為子節點的父節點(parent).具有相同父節點的子節點之間互稱為兄弟節點。
節點的層次(level):節點的層次從根開始算起,根的層次值為1,其余節點的層次值為父節點層次值加l。
樹的深度(depth):樹中節點的最大層次值稱為樹的深度或高度。
有序樹與無序樹:如果將樹中節點的各棵子樹看成從左到右是有序的(即不能互換),則稱該樹為有序樹,否則稱為無序樹。
祖先節點(ancestor):從根到該節點所經分支上的所有節點
后代節點(descendant):以某節點為根的子樹中任一節點都稱為該節點的后代節點。
森林(forest):森林是;兩顆或兩顆以上互不相交的樹的集合,刪去一棵樹的根,就得到一個森林。
樹的基本操作
如果需要實現一棵樹,程序不僅要以合適的方式保存該樹的所有節點,還要記錄節點與節點之間的父子關系。接下來,還應該為樹實現如下基本操作。
初始化:通常是一個構造器,用于創建一棵空樹,或者以指定節點為根來創建樹。
為指定節點添加子節點
判斷樹是否為空
返回根節點
返回指定節點(非根節點)的父節點
返回指定節點(非葉子節點)的所有子節點
返回指定節點(非葉子節點)的第i個子節點
返回該樹的深度
返回指定節點的位置
為了實現樹這種數據結構,程序必須能記錄節點與節點之間的父子關系,為此有一下兩種選擇:
父節點表示法:每個子節點都記錄它的父節點。
子節點鏈表示法:每個非葉子節點通過一個鏈表來記錄它所有的子節點。
父節點表示法
通過前面的介紹可以發現,樹中除根節點之外的每個節點都有一個父節點。為了記錄樹中節點與節點之間的父子關系,可以為每個節點增加一個parent域,用以記錄該節點的父節點。用如下圖和如下表來表示
tree_show.PNG
數組索引
data
parent
0
A
-1
1
B
0
2
C
0
3
D
0
4
E
1
5
F
3
6
G
3
7
H
4
8
I
4
9
J
4
10
K
6
...
...
...
由此可見,只要用一個節點數組來保存樹里的每個節點,并讓每個節點記錄其父節點在數組中的索引即可。
子節點鏈表表示法
父節點表示法的思想是讓每個節點“記住”它的父節點的索引,父節點表示法是從子節點著手的;反過來,還有另外一種方式:讓父節點“記住”它的所有子節點口在這種方式下,由于每個父節點需要記住多個子節點,因此必須采用“子節點鏈”表示法。示意圖如下:
tree_linked.PNG
二叉樹
二叉樹的定義和基本概念
二叉樹指的是每個節點最多只能有兩個子樹的有序樹。通常左邊的子樹被稱作“左子樹”(left subtree),右邊的子樹被稱為“右子樹”(right subtree).由此可見,二叉樹依然是樹,它是一種特殊的樹。
二叉樹的每個節點最多只有來兩顆樹(不存在度大于2的節點),二叉樹的子樹有左,右之分,次序不能顛倒。
樹和二叉樹的兩個重要區別如下:
樹中節點的最大度數沒有限制,而二叉樹節點的最大度數為2,也就是說,二叉樹是節點的最大度數為2的樹。
無序樹的節點無左右之分,而二叉樹的節點有左,右之分,也就是說,二叉樹是有序樹。
一棵深度為k的二叉樹,如果它包含了
2^k-1
個節點,就把這棵二叉樹稱為滿二叉樹。滿二叉樹的特點是。每一層上的節點數都是最大節點數,即各層節點數分別為1,2,4,8, 16,...,滿二叉樹下圖所示:
two_tree.PNG
一顆有n個節點的二叉樹,按滿二叉樹的編號方式對它進行編號,若樹中所有節點和滿二叉樹1~n編號完全一致,則稱該樹為完全二叉樹。也就是說,如果一顆二叉樹除最后一層外,其余層的所有節點都是滿的,并且最后一層或者是滿的,或者僅在右邊缺少若干連續的節點,則此二叉樹就是完全二叉樹。
綜上所述,二叉樹大致有如下幾個性質:
二叉樹第i層上的節點數據至多為2的i-1次方
深度為k的二叉樹至多有2的k次方-1個節點.滿二叉樹的每層節點的數量依次為1, 2, 4,8,…,因此深度為k的滿二叉樹包含的節點數為公比為2的等比數列的前k項總和,
即2的k次方一1。
在任何一棵二叉樹中,如果其葉子節點的數量為n0,度為2的子節點數量為n2,則
n0=n2 + 1。這是因為:如果為任意葉子節點增加一個子節點,則原有葉子節點變成非葉子節點,新增節點變成葉子節點,上述等式不變;如果為任意葉子節點增加兩個子節點,則原有葉子節點變成度為2的非葉子lto點,新增的兩個節點變成葉子節點,上述等式依然不變。
具有n個節點的完全二叉樹的深度為log2(n+1)
對于一顆具有n個節點的完全二叉樹的節點按層自左向右編號,則對任一編號為i(n>=i>=1)的節點有下列性質。
當i==1時,節點i是二叉樹的根;若i>1,則節點的父節點是i/2
若2i
若2i+1<=n,則節點i有右孩子,右孩子的編號是2i+1;否則,節點無右孩子。
1~n/2范圍的節點都是有孩子節點的非葉子節點,其余的節點全部都是葉子節點。編號為n/2的節點可能只有左子節點,也可能即有左子節點,又有右子節點。
二叉樹的基本操作
二叉樹記錄其節點之間的父子關系更加簡單,因為二叉樹中的每個節點最多只能保存兩個子節點。接下來,程序也需要為二叉樹實現如下基本操作。
初始化:通常是一個構造器,用于創建一顆空樹,或者以指定節點為根來創建二叉樹。
為指定節點添加子節點
判斷二叉樹是否為空
返回根節點
返回指定節點(非根節點)的父節點
返回指定節點(非葉子節點)的左子節點
返回指定節點(非葉子節點)的右子節點
返回該二叉樹的深度
返回指定節點的位置
要實現二叉樹這種數據結構,有以下三種選擇。
順序存儲:采用數組來記錄二叉樹的所有節點。
二叉鏈表存儲:每個節點保留一個left,right域,分別指向其左、右子節點。
三叉鏈表存儲:每個節點保留一個left, right,parent域,分別指向其左、右子節點和父節點。
二叉樹的順序存儲
順序存儲指的是充分利用滿二叉樹的特性:每層的節點數分別為1, 2, 4, 8,…,2的(i-1)2的i次方。一棵
深度為i的二叉樹最多只能包含2的i次方一1個節點,因此只要定義一個長度為2的i次方一1的數組即可存儲這棵二叉樹。
對于普通二叉樹(不是滿二叉樹),那些空出來的節點對應的數組元素留空就可以了。由此可見,二叉樹采用順序存儲會造成一定的空間浪費。對于下圖1所示的二叉樹(完全二叉樹),采用下圖2所示的數組來保存即可。
圖1.PNG
圖2.PNG
對于左圖所示的二叉樹,需使用右圖所示的數組來保存。
compare_tree.PNG
當使用數組來存儲二又樹的所有節點時可能會產生一定的空間浪費,如果該二叉樹是完全二叉樹,就不會有任何空間浪費了;但如果該二叉樹的所有節點都只有右子節點,那么就會產生相當大的空間浪費.
二叉樹的二叉鏈表存儲
二叉鏈表存儲的思想是讓每個節點都能“記住”它的左,右兩個子節點。為每個節點增加left,right兩個指針,分別引用改節點的左,右兩個子節點,因此二叉鏈表存儲的每個節點有如下圖結構:
two_fork_tree.PNG
二叉鏈表存儲的二叉樹的節點大致有如下定義:
class Node{
Object data;
Node left;
Node right;
}
對于這種二叉鏈表存儲的二叉樹,如果程序需要,為指定節點添加子節點也非常容易,讓父節點的left或right引用指向新節點即可。
二叉樹的三叉鏈表存儲
三叉鏈表存儲的思想是讓每個節點不僅“記住”它的左右兩個子節點,還要“記住”它的父節點,因此需要為每個節點增加left,right和parent三個指針,分別引用該節點的左,右兩個子節點和父節點。因此,三叉鏈表存儲的每個節點有如下圖的結構:
three_tree.PNG
因此三叉鏈表存儲的二叉樹的節點大致如下:
class Node{
Object data;
Node left;
Node right;
Node parent;
}
對于這種三叉鏈表存儲的二叉樹,如果程序需要,為指定節點添加子節點也非常容易,除了要維護父節點的left,right引用之外,還要維護新增節點的parent引用。
遍歷二叉樹
遍歷二叉樹指的是按某種規律依次訪問二叉樹的每個節點,對二叉樹的遍歷過程就是講非線性結構的二叉樹的節點排列成線性序列的過程。
如果采用順序結構來保存二叉樹,程序遍歷二叉樹非常容易,無須進行任何思考,直接遍歷底層數組即可。如果采用鏈表來保存二叉樹的節點,則有以下兩種遍歷方式。
深度優先遍歷:這種遍歷算法將先訪問到樹中最深層次的節點
廣度優先遍歷:這種遍歷算法將逐層訪問每層的節點,先訪問根(第一層)節點,然后訪問第二層的節點.....一次類推。因此,廣度優先遍歷方法又被稱為按層遍歷。
先(前)序遍歷二叉樹
中序遍歷二叉樹
后序遍歷二叉樹
如果L,D,W表示左子樹、根、右子樹,習慣上總是必須先遍歷左子樹,后遍歷右子樹,根據遍歷根節點的順序不同,上面三種算法可表示如下。
DLR:先序遍歷
LDR:中序遍歷
LRD:后序遍歷
深度遍歷的先序遙歷、中序遍歷、后序遍歷這三種遍歷方式的名稱都是針對根節點(D)而言的。先處理根節點(D)時就稱為先序遍歷。其次處理根節點(D)時就稱為中序遍歷;最后處理根節點(D)時就稱為后序遍歷。
先序遍歷
先序遍歷指先處理根節點,其處理順序如下:
(1) 訪問根節點
(2) 遞歸遍歷左子樹
(3) 遞歸遍歷右子樹
中序遍歷
中序遍歷指其次處理根節點.其處理順序如下。
(1) 遞歸遍歷左子樹
(2) 訪問根節點
(3) 遞歸遍歷右子樹
后序遍歷
后序遍歷指最后處理根節點,其處理順序如下。
(1) 遞歸遍歷左子樹
(2) 遞歸遍歷右子樹
(3) 訪問根節點
廣度優先(按層)遍歷
廣度優先遍歷又稱為按層遍歷,整個遍歷算法是先遍歷幾叉樹的第一層(根節點),再遍歷根節點的兩個子’節點(第二層)……依此類推,逐層遍歷二叉樹的所有節點。
為了實現廣度優先遍歷,可以借助于具有FIFO特征的隊列來實現。如下所示。
建一個隊列(先進先出),把樹的根節點壓入隊列。
從隊列中彈出一個節點(第一個彈出的就是根節點),然后把改節點的左,右節點壓入隊列,如果沒有子節點,則說明已經達到葉子節點了。
用循環重復執行2步,知道隊列為空。當隊列為空時,說明所有的葉子節點(深度最深的層)都已經經過了隊列,也就完成了遍歷。
轉換方法
由于二叉樹是一種更“確定”(它的每個節點最多只有兩個子節點)的數據結構,因此不管是存儲、增加、刪除節點,還是遍歷節點,程序都可以更簡單、方便地實現口反之,由于樹的每個節點具有個數不確定的節點,因此程序實現起來更復雜。
為了充分利用二義樹的簡單易用性,可以將普通樹轉換為二叉樹,以二叉樹的形式來保存柞通樹,當程序需要樹時,再將悅義樹轉換為普通樹。
森林其實更簡單,如果將一棵伶通樹的根節點去掉,這棵樹就變成了森林。或者可以轉換一下思維,森林其實就是有多個根節點的樹。
森林,樹和二叉樹的轉換
有序樹,森林和二叉樹之間有一一映射的關系,可以進行互相轉換。
多叉樹向二叉樹的方法如下:
(1)加虛線:同一個父節點的相鄰兄弟節點之間加虛線
(2)抹實線:每個節點只保留它與最左子節點的連線,與其他字節點的連線都被抹掉。
(3)虛改實:虛線改為實線
如圖就是多叉圖向二叉樹轉換的結果
forest_tree.PNG
圖中的虛線就是新增的“父子”關系。這個轉換結果來看,多叉樹1轉換為二叉樹的方法的關鍵思想就是:所有子節點只保留子節點,其他子節點轉為左子節點的右子節點鏈。
按照這個轉換思路,森林也可轉換為二叉樹————只要把森林當成一顆根節點被刪除的多叉樹即可。下圖示范了將森林轉換為二叉樹的結果。
forest_to_tree.PNG
反過來,二叉樹也可恢復出對應的多叉樹,森林,恢復方法如下:
-(1)加虛線:若某節點I是父節點的左子節點,則為該節點I的右孩子鏈的所有節點分別于節點I的父節點添加連線
(2)抹線:把有虛線的節點于原父節點的連線抹去
(3)整理:虛改實并按層排列
把二叉樹轉換為多叉樹
two_tree_more_tree.PNG
如果二叉樹的根節點有右子節點————右子節點就代表根節點的兄弟節點,這種情況會轉換得到森林。
把二叉樹轉換為森林
tree_to_forest.PNG
樹的鏈表存儲
根據上面介紹的理論,二義樹可以和多叉樹之間進行自由轉換,因此可以得到普通樹的另外一種保存方式:以二義樹的形式保存多叉樹,實際需要的時候再將二叉樹轉換為普通樹。
至于到底以哪種方式來保存二叉樹,完全是自由的。通常會選擇使用三叉鏈表存儲方式來保存二叉樹,這樣得到的二叉樹操作起來更方便,進行二叉樹和多叉樹之間轉換時也更方便。
哈夫曼樹
哈夫曼樹又被稱為最優二叉樹,是一種帶權路徑最短的二叉樹。哈夫曼樹是二叉樹的一種應用,在信息檢索中很常用.
哈夫曼樹的定義和基本概念
在介紹哈夫曼樹之前先來介紹一些相關的概念。
節點之間的路徑長度:從一個節點到另一個節點之間的分支數量稱為兩個節點之間的路徑長度
樹的路徑長度:從根節點到樹中的每一個節點的路徑長度之和。
對于下圖所示的而二叉樹,該樹的路徑長度為17.即0+1+2+2+3+4+5==17.
hafuman.PNG
節點的帶權路徑長度:從該節點到根節點之間的路徑長度與節點的權的乘積
樹的帶權路徑長度:樹中所有葉子節點的帶權路徑長度之和。帶權路徑如圖:
daiquan.PNG
對于哈夫曼樹,有一個很重要的定理:對于具有對n個葉子節點的哈夫曼樹,一共需要2乘以n-1個節點。因為對于二叉樹來說,有三種類型節點,即度數為2的節點、度數為1的節點和度數為0的葉子節點,而哈夫曼樹的非葉子節點都是由兩個節點合并產生的,所以不會出現度
數為1的節點。而生成的非葉子節點的個數為葉子節點個數-1因此n個葉子節點的哈夫曼樹,一共需要Z乘以n-1個節點。
創建哈夫曼樹
創建哈夫曼樹,可以按如下步驟進行:
根據給定的。個權值{wl,w2,...,wn}構造n棵二叉樹的集合F={T1,T2,...,Tn} },F集合中每棵二叉樹都只有一個根節點。
選取F集合中兩棵根節點的權值最小的樹作為左、右子樹以構造一棵新的二叉樹,且將新的二叉樹的根節點的權值設為左、右子樹上根節點的權值之和。
將新的二叉樹加入到F集合中,并刪除第2步中被選中的兩棵樹。
重復第2和3步,直到F集合中只剩下一棵樹,這棵樹就是哈夫曼樹。
下圖顯示了創建哈夫曼樹的過程。
hafuman_tree.PNG
哈夫曼編碼
根據哈夫曼樹可以解決報文編碼問題。假設需要對一個字符串如“a6cdabcaba”進行編碼,將它轉換為唯一的二進制碼,但要求轉換出來的二進制碼的長度最小。
假設每個字符在字符串中出現的頻率為W}其編碼長度為L,編碼字符有n個,則編碼后二進制碼的總長度為W1L1+W2L2+W3L3+...+WnLn,這正好符合哈夫曼樹的處理原則。因此可采用哈夫曼樹的原理構造二進制編碼,并使電文總長最短。
對于“abcdabcaba”字符串,總共只有a,b,c,d,這四個字符,它們出現的次數是4,3,2,1次__這相當于它們的權值。于是,將a,b,c,d四個字符以出現的次數為權值構造哈夫曼樹,得到如下圖結構:
hanfuman1.PNG
從哈夫曼樹根節點開始,對左子樹分配代碼“0”,對右子樹分配代碼“1”,一直到達葉子節點。然后.將從樹根沿每條路徑到達葉子節點的代碼排列起來,便得到了每個葉子節點的哈夫曼編碼。下圖顯示了對a, b, c, d四個字符編碼得到的哈夫曼編碼。
hanfuma2.PNG
排序二叉樹
排序二叉樹是一種特殊結構的二叉樹,通過它可以非常方便地對樹中的所有節點進行排序和檢索
排序二叉樹要么是一顆空二叉樹,要么是具有下列性質的二叉樹
若它的左子樹不空,則左子樹上所有的節點的值均小于它的根節點的值
若它的右子樹不空,則右子樹上所有的節點均大于它的根節點的值
它的左右子樹分別為排序二叉樹。
下圖顯示了一棵排序二叉樹.
對于排序二叉樹,若按中序遍歷就可以得到由小到大的有序序列。中序遍歷得:
{2,3,4,8,9,9,10,13,15,18)
sort_tree.PNG
創建排序二義樹的步驟,就是不斷地向排序二義樹添加節點的過程,幾體如下。
以根節點為當前節點開始搜索
拿新節點的值和當前節點開始搜索
如果新節點的值更大,則以當前的右子節點作為新的當前節點的右子節點作為新的當前節點;如果新節點的值更小,則以當前節點的右子節點作為新的當前節點。
重復第2和3兩個步驟,直到搜索到合適的葉子節點。
將新節點添加為第4步找到的葉子節點的子節點,如果新節點更大,則添加為右子節點;否則,添加為左子節點。
當程序從排序二叉樹中刪除一個節點之后,為了讓它依然保持為排序哭叉樹,必須對該排序二叉樹進行維護。維護可分為如下幾種情況。
被刪除節點是葉子節點,只需將它從其父節點中刪除。
被刪除轉點p只有左子樹或只有右子樹,如果p是它的父節點的左子節點,則將p的左子樹或右子樹添加成p一節點的父節點的左子節點即可;如果p是它的父節點的右子節點,則將p的左子樹或右子樹添加成P節點的父節點的右子節點即可。簡單來說,如果要側除的節點只有一個子節點,即可用它的子節點來代替要側除的節點。
被刪除的節點只有左子樹的情況
delete_only_left_tree.PNG
被刪除節點只有右子樹的情況
delete_only_right_tree.PNG
若被刪除節點p的左、右子樹均非空,則有以下兩種做法。
將pL設為P的父節點q的左或右子節點(取決于P是其節父點q的左、右子節點),
將pR設為P節點的中序前趨節點s的右子節點(s是pL最右下的節點,也就是pL子樹中最大的節點)。采用這種方式刪除節點的示意圖如下:
delete_left_right.PNG
以P節點的中序前趨或后繼替代P所指節點,然后從原排序二叉樹中刪除中序前趨或后繼節點。簡單來說,就是用大于p的最小節點或小于P的最大節點代替P節點點,采
用這種方式刪除節點的示意圖如下圖:
delete_left_right_center.PNG
紅黑樹
排序二叉樹雖然可以快速檢索,但在最壞的情況下,如果插入的節點集本身就是有序的,要么是由小到大排列,要么是由大到小排列,那么最后得到的排序二義樹將變成鏈表:所有節點只有左節點(如果插入節點集合本身是由大到小排列的),或者所有節點只有右節點(如果
插入節點集合本身是由小到大排列的)。在這種情況下,排序二叉樹就變成了普通鏈表,其檢索效率就會很低。
為了改變排序二叉樹存在的不足,對二叉樹進行改進————紅黑樹,他將這種排序二叉樹稱為“對稱二叉B樹”。
紅黑樹是一個更高效的檢索二叉樹,因此常常用來實現關聯數組。典型的,JDK提供的集合類TreeMap本身就是一顆紅黑樹的實現。
紅黑樹在原有的排序二叉樹上增加如下幾個要求:
性質l:每個節點要么是紅色,要么是黑色。
性質2:根節點永遠是黑色的。
除質3:所有的葉子節點都是空節點(即null),并且是黑色的。
性質4:每個紅色節點的兩個子節點都是黑色的。(從每個葉子到根的路徑上不會有兩個連續的紅色節點。)
性質5:從任一節點到其子樹中每個葉子節點的路徑都包含相同數量的黑色節點。
java實現的紅黑樹結構如下圖:
red_black_tree.PNG
根據性質5,紅黑樹從根節點到每個葉子節點的路徑都包含相同數量的黑色節點,因此從根節點到葉子節點的路徑中包含的黑色節點數被稱為樹的“黑色高度(black-height)".
性質4則保證了從根節點到葉子節點的最長路徑的一長度不會超過任何其他路徑的2倍。假如有一棵黑色高度為3的紅黑樹,從根節點到葉子節點的最短路徑長度是2,該路徑上全是黑色節點〔黑色節點-黑色節點-黑色節點)。最長路徑也只可能為4,在每個黑色節點之間插入一個紅色節點〔黑色節點-紅色節點-黑色書點-紅色節點-黑色節點),性質4保證絕不可能插入更多的紅色節點。由此可見,紅黑樹中最長的路徑就是一條紅黑交替的路徑。
由此可以得出結論:對于給定的黑色高度為N的紅黑樹,從根到葉子節點的最短路徑長度為N-1,最長路徑長度為2*(N-1).
紅黑樹通過上面這種限制來保證它大致是平衡的—因為紅黑樹的高度不會無限增高,這樣能保證紅黑樹在最壞的情況下都是高效的,不會出現普通排序二叉樹的情況。
由于紅黑樹只是一棵特殊的排序二叉樹,因此對紅黑樹上的只讀操作與普通排序二叉樹上的只讀操作完全相同,只是紅黑樹保持了大致平衡,因此檢索性能更好.
但在紅黑樹上進行插入操作和刪除操作會導致樹不再符合紅黑樹的特征,因此插入操作和刪除操作都需要進行一定的維護,以保證插入節點、刪除節點后的樹依然是紅黑樹。
插入操作
插入操作按如下步驟進行:
以排序二叉樹的方法插入新節點,并將它設為紅色。
進行顏色調換和樹旋轉
這種顏色調換和樹旋轉就比較復雜了,下面將分情況進行介紹。在介紹中,把新插入的節點定義為N節點,把N節點的父節點定義為P節點,把P節點的兄弟節點定義為U節點,把P節點的父節點定義為G節點。
情形1:新節點N是樹的根節點,沒有父節點。
在這種情形下,直接將它設置為黑色以滿足性質2。
情形2:新節點的父節點P是黑色的
在這種情形下,新插入的節點是紅色的,因此依然滿足性質4。而且因為新節點N有兩個黑色葉子節點,但是由于新節點N是紅色的,通過它的每個子節點的路徑依然保持相同的黑色節點數,因此依然滿足性質5
3.情形3:父節點P和父節點的兄弟節點U都是紅色的
在這種情形下,程序應該將P節點、U節點都設置為黑色,并將P節點的父節點設置為紅色(用來保持性質5)。現在,新節點N有了一個黑色的父節點P。由于從P節點、U節點到根節點的任何路徑都必須通過G節點,這些路徑上的黑色節點數目沒有改變(原來有葉子和G節點兩個黑色節點,現在有葉子和P節點兩個黑色節點)。
經過上面處理后,紅色的G節點的父節點也有可能是紅色的,這就違反了性質4,因此還需要對G節點遞歸地進行整個過程〔把G節點當成新插入的節點進行處理)。
下圖顯示了處理過程:
red_black_tree_1.PNG
情形4:父節點P是紅色的,而其兄弟節點U是黑色的或缺少;且新節點N是父節點P的右子節點,而父節點P又是其父節點G的左子節點。
在這種情形下,對新節點和其父節點進行一次左旋轉。接著,按情形5處理以前的父節點P(也就是把P當成新插入的節點)。這將導致某些路徑通過它們以前不通過的新節點N或父節點P其中之一,但是這兩個節點都是紅色的,因此不會影響性質5。
red_black_tree_2.PNG
情形5:父節點F是紅色的,而其兄弟節點U是黑色的或缺少:且新節點N是其父節點的左子節點,而父節點F父是其父節點G的左子節點。
在這種情形下,需要對節點G進行一次右旋轉口在旋轉產生的樹中,以前的父節點P現在是新節點N和節點G的父節點。由于以前的節點G是黑色的(否則父節點P就不可能是紅色的),切換以前的父節點P和節點G的顏色,使之滿足性質4。性質5也仍然保持滿足,因為通過這三個節點中任何一個的所有路徑以前都通過節點G,現在它們都通過以前的父節點P。在各自的情形下,這都是三個節點中唯一的黑色節點。
red_black_tree_3.PNG
刪除操作
紅黑樹的刪除操作比插入操作要稍微復雜一些,實際上也可按如下步驟進行:
以排序二叉樹的方法刪除指定節點。
進行顏色調換和樹旋轉,使之滿足紅黑樹特征。