1.前文回顧
上一篇隨筆寫到了紅黑樹的實現及其各種功能的實現,本文將講紅黑樹的刪除。
上一篇隨筆提到了二叉搜索樹的刪除功能在紅黑樹中雖然可以用,但會破壞紅黑樹的結構。
其實紅黑樹的刪除功能是在二叉搜索樹的刪除功能上加上了重構結構的功能。因此,如果不熟悉二叉搜索樹的刪除功能和紅黑樹的,建議先看二叉搜索樹和紅黑樹。
2.紅黑樹的特性
在講刪除前,有必要先講下紅黑樹的特性。因為我們刪除節點后,紅黑樹的特性應該繼續有效。
算法導論的原話:
1. Every node is either red or black.(每個節點不是紅色就是黑色)
2. The root is black.(根節點是黑色的)
3. Every leaf (NIL) is black.(每個空的節點都是黑色的)
4. If a node is red, then both its children are black.(紅色節點的子節點都是黑色的)
5. For each node, all simple paths from the node to descendant leaves contain the same number of black nodes.(任意節點到它的子孫節點的路徑上,含有的黑節點數目相同)
前四條很容易理解,關鍵是第五條。舉個例子:
節點19到節點1,5,14,20,30,35的路徑都經過了2個黑色節點(不算節點19自己)。
3.刪除節點的種類
節點可能有0個子節點、1個子節點、2個子節點,再加上節點可分為紅色節點和黑色節點,因此總共有6種節點。
a. 黑色節點,沒子節點。
b. 黑色節點,1個子節點。
c. 黑色節點,2個子節點。
d. 紅色節點,沒子節點。
e. 紅色節點,1個子節點。
f. 紅色節點,2個子節點。
但是,在紅黑樹中,e情況不可能出現,因為要使父節點變為紅色,只能通過顏色反轉來實現。
b情況中,該子節點只能是紅色,否則不滿足性質5。
假設e情況存在,看下圖:(由於實際情況下,e情況不可能出現,下面是強行造成e情況存在,故這個紅黑樹是錯誤的,不滿足性質5:任意節點到它的子孫節點的路徑上,含有的黑節點數目相同)
我們把目光注視到節點25,這是一個e情況。試想節點20是怎么來的:要么是在節點25之后插入的新節點;要么是刪除了節點25的右節點。
但是,在即將介紹的刪除方法中,不會造成刪除了節點25的右節點后,紅色節點25只有一個左節點20的情況。
那么節點20只能是在節點25之后插入的新節點,但是新節點都是紅色的,插入時,會變成下圖:
這樣會觸發右旋,隨后再觸發顏色反轉、左旋。如下圖:
(由於實際情況下,e情況不可能出現,這里是強行造成e情況存在,故這個紅黑樹是錯誤的,不滿足性質5:任意節點到它的子孫節點的路徑上,含有的黑節點數目相同)
因此,在節點25之后插入的新節點20,並不會造成紅色節點25只有一個左節點20的情況。故,e情況(e. 紅色節點,1個子節點。)不可能出現在紅黑樹中。
請看節點34,這是黑色節點只含有一個黑色子節點的情況。從根節點25到34的左空節點經過了1個黑色節點;從根節點25到節點35經過了2個黑色節點,故不滿足性質5,黑色節點只含有一個黑色子節點的情況在紅黑樹中不存在。
綜上所述,我們只需要處理的情況有5種:
a. 黑色節點,沒子節點。
b. 黑色節點,1個紅色子節點。
c. 黑色節點,2個子節點。
d. 紅色節點,沒子節點。
f. 紅色節點,2個子節點。
4.刪除節點時的各種情況討論
一、節點有兩個子節點時
先討論情況c和f,即節點有兩個子節點。
假設要刪除節點X,按照二叉搜索樹的刪除方法,尋找X的后驅繼承節點Y。然后除了顏色不變之外,互換X和Y的位置。此時X節點只有一個或沒有節點,繼續刪除它。具體刪除方法請看a、b、d情況的刪除。
舉個例子,如下圖:
假設要刪除節點4,則節點5是節點4的后驅繼承者。(節點4的右節點中的最小節點。)交換節點5和4,節點顏色不變,如下圖:(X=4)
繼續刪除X。此時X是沒有子節點的黑色節點,適用於情況a(a. 黑色節點,沒子節點。)的刪除,下文即將介紹。
二、節點是紅色,且沒子節點時
要討論的是情況 d. 紅色節點,沒子節點。直接刪除即可。
舉個例子:
如果要刪除節點1,節點1屬於情況d. 紅色節點,沒子節點。直接刪除,紅黑樹仍遵守所有特性。
三、節點是黑色,且只有一個紅色子節點時
要討論的是情況b(黑色節點,1個紅色子節點)。
從例子入手:
現在要刪除節點2。節點2有一個紅色節點,直接刪除節點2,節點1補上節點2的位置,並且節點1的顏色變為黑色。
如下圖:
即如果要刪除含有一個紅色子節點的黑色節點,直接刪除黑色節點,紅色子節點補位,且變為黑色節點。
四、節點是黑色,且沒有子節點時
要討論的是情況 a. 黑色節點,沒子節點。
這里引入一個新節點雙重黑色節點(double black),圖中用矩形來代表,如下圖:
節點1為雙重黑色節點,雙重黑色節點不能存在於紅黑樹中,我們需要把它變回黑色節點,這樣紅色樹才成立。
首先,雙重黑色節點怎么產生:當我們要刪除一個沒有子節點的黑色節點時,就會產生一個雙重黑色空節點,且補到刪除節點的位置。(還有一種產生方法是在消除雙重黑色節點的過程中出現的,稍后介紹。)
舉個例子:
現在刪除節點1,由於節點1是沒有子節點的黑色節點,故產生一個雙重黑色空節點。如下圖:
然后,我們接下來講如何消除雙重黑色節點。
消除公式:
黑色節點=雙重黑色節點+紅色節點
先介紹鄰居節點:假設節點a的兩個子節點為節點b和節點c,則節點b是節點c的鄰居節點;節點c是節點b的鄰居節點
如上圖,節點10的鄰居節點是節點25;節點25的鄰居節點是節點10。
雙重黑色節點出現時,可能面臨的情況有:
情況1:雙重黑色節點的鄰居節點是黑色節點,且此鄰居節點有一個紅色子節點時(最多只能有一個紅色子節點,如果有兩個,則會觸發反轉顏色。)
情況2:雙重黑色節點的鄰居節點是黑色節點,且此鄰居節點有兩個黑色子節點或黑色空子節點時(根據性質5,鄰居節點不可能有黑色子節點。因為變成雙重黑色節點前,它肯定是一個黑色節點,鄰居節點也是黑色節點,如果鄰居節點有黑色子節點,則會違反性質5。)
情況3:雙重黑色節點的鄰居節點是紅色節點時
情況4:雙重黑色節點是根節點,沒有鄰居節點時(如果雙重黑色節點不是根節點,不可能沒有鄰居節點。因為變成雙重黑色節點前,它肯定是一個黑色節點,根據性質5,它鄰居節點要么是黑色節點,要么是含有兩個黑色子節點的紅色節點。)
下面將根據各種情況來消除雙重黑色節點
情況1:雙重黑色節點的鄰居節點是黑色節點,且有一個紅色子節點時
雙重黑色節點的鄰居節點是黑色節點,且有一個紅色子節點的情況如下圖:(我們這里的紅黑樹所有的紅色聯系都是向左的,所以紅色子節點一定為左子節點)
根據性質5,節點a只能是空節點,節點25只有一個子節點。
先對節點23和25進行右旋:
然后對節點20和23進行左旋,紅色節點25變為黑色節點,雙重黑色空節點變為黑色空節點:
消除完畢。
上述為鄰居節點為右節點時的情形。當鄰居節點是左節點時,看下圖:
對15,13進行右旋,紅色節點10變為黑色節點,雙重黑色空節點變為黑色空節點:
消除完畢。
情況2:雙重黑色節點的鄰居節點是黑色節點,且有兩個黑色空子節點時
當父節點為紅色節點時:
如下圖:
請看紅色節點4,如果這個節點是反轉顏色而來的,那么反轉顏色之前是什么樣子的呢?
逆反轉顏色:
這個時候,雙重黑色空節點和紅色節點重合,相加得黑節點:(黑色節點=雙重黑色節點+紅色節點)
消除完成,觸發左旋:
當父節點為黑色節點時:
如下圖:
黑色節點=雙重黑色節點+紅色節點
黑色節點5可以看作是雙重黑色節點和紅色節點的結合體:(這里會產生一個雙重黑色節點)
然后逆反轉顏色:
然后雙重黑色空節點就消除了。
新產生的黑色節點需要根據鄰居節點的情況進一步消除。(這里鄰居節點的情況是雙重黑色節點的鄰居節點是黑色節點,且有兩個黑色子節點或黑色空子節點,且父節點為紅色節點時)
情況3:雙重黑色節點的鄰居節點是紅色節點時
這個紅色節點肯定是有兩個黑色子節點或黑色空子節點。且此紅色節點肯定是左節點。(因為在此紅黑樹中,所有紅色節點的都是左節點。)
節點a,b可能同時為黑色非空節點,也可能是同時為黑色空節點。
對節點5和11進行右旋:
然后逆反轉顏色和消除:
消除完畢。
情況4:雙重黑色節點是根節點,沒有鄰居節點時
看下圖演變過程:
刪除節點20
雙重黑色空節點鄰居節點為黑色,且沒子節點,消除雙重黑色空節點
如果雙重黑節點是根節點,直接變成黑色節點即可:
觸發左旋:
至此,所有情況討論完畢,紅黑樹刪除節點完成。
參考文章:https://www.geeksforgeeks.org/red-black-tree-set-3-delete-2/
5.完整代碼
節點.h: UCLASS() class ALGORITHM_API ARedBlackNode : public AActor { GENERATED_BODY() public: // Sets default values for this actor's properties ARedBlackNode(); // Called every frame virtual void Tick(float DeltaTime) override; //設值 FORCEINLINE void SetValue(int Newkey, FString NewValue) { Key = Newkey; Value = NewValue; } FORCEINLINE ARedBlackNode* Get() { return this; } //獲取或修改私有變量 FORCEINLINE int GetKey() { return Key; } FORCEINLINE void SetKey(int NewKey) { Key = NewKey; } FORCEINLINE int GetCount() { return Count; } FORCEINLINE void SetCount(int NewCount) { Count = NewCount; } FORCEINLINE FString GetValue() { return Value; } FORCEINLINE void SetValue(FString NewValue) { Value = NewValue; } FORCEINLINE bool GetColor() { return Color; } FORCEINLINE void SetColor(bool IsRed) { Color = IsRed; } FORCEINLINE ARedBlackNode* GetParent() { return Parent; } FORCEINLINE void SetParent(ARedBlackNode* X) { Parent = X; } FORCEINLINE ARedBlackNode* GetNode(bool Left) { if (Left) return LeftNode; return RightNode; } FORCEINLINE void SetNode(bool Left, ARedBlackNode* NewNode) { //設定父節點 if (NewNode) NewNode->SetParent(this); if (Left) LeftNode = NewNode; else RightNode = NewNode; } //獲取鄰居節點,用於刪除 FORCEINLINE ARedBlackNode* GetSibling() { //如果沒父節點,則沒鄰居節點 if (!Parent) return nullptr; //如果這個節點是父節點的左節點,則鄰居節點是父節點的右節點 if (this == Parent->GetNode(true)) return Parent->GetNode(false); //反之則鄰居節點是父節點的左節點 else return Parent->GetNode(true); } //這個節點有沒紅色子節點 FORCEINLINE bool HasRedChild() { return (LeftNode && LeftNode->GetColor() == true) || (RightNode && RightNode->GetColor() == true) ; } //這個節點是左子節點? FORCEINLINE bool IsOnLeft() { if (!Parent) return false; return this == Parent->GetNode(true); } protected: // Called when the game starts or when spawned virtual void BeginPlay() override; private: int Key; FString Value; //左右節點 ARedBlackNode* LeftNode; ARedBlackNode* RightNode; //父節點,這個節點是為了記錄每個節點的位置(用於測試程序是否正確建立紅黑樹),與紅黑樹的實現無關。 ARedBlackNode* Parent; //計算此節點下面共有多少個節點(包括自己) int Count; //與父節點之間的聯系,如果為True,則是紅色的;如果為False,則是黑色的 bool Color; }; 紅黑樹.h: class ARedBlackNode; UCLASS() class ALGORITHM_API ARedBlackBST : public AActor { GENERATED_BODY() public: // Sets default values for this actor's properties ARedBlackBST(); // Called every frame virtual void Tick(float DeltaTime) override; //判斷一個節點和它父節點的聯系是否是紅色的 bool IsRed(ARedBlackNode* X); //查值 FString GetValue(int InputKey); //提供一個方法讓TreeNode之間進行比較 //如果a大於b,返回1;如果a小於b,返回-1;如果相等,返回0 int CompareTo(int a, int b); //向左旋轉 //為什么要左旋:因為在紅黑樹中,所有紅色的聯系都是向左的。 ARedBlackNode* RotateLeft(ARedBlackNode* h); //向右旋轉 //為什么要右旋: //如果出現連續兩個紅色聯系時(即a,b,c是三個連續的節點,且ab,bc間的聯系都是紅色的),需要右旋一次 //然后反轉一次顏色,從而符合紅黑樹的游戲規則。 ARedBlackNode* RotateRight(ARedBlackNode* h); //反轉顏色:如果一個節點的左右聯系都是紅色,則將它們變為黑色,此節點與父節點的聯系變為紅色 void FlipColors(ARedBlackNode* h); //插入一個節點 void Put(int Newkey); ARedBlackNode* Put(ARedBlackNode* h, int NewKey); //中序遍歷 void InorderTraversal(); void Inorder(ARedBlackNode* X); //尋找最小值 int FindMin(); //尋找擁有最小值的節點 ARedBlackNode* FindMin(ARedBlackNode* X); //尋找最大值 int FindMax(); //尋找擁有最大值的節點 ARedBlackNode* FindMax(ARedBlackNode* X); //給定一個數字,尋找最接近它的key(比它小) int FindFloor(int InputKey); ARedBlackNode* FindFloor(ARedBlackNode* X, int InputKey); //給定一個數字,尋找最接近它的key(比它大) int FindCeiling(int InputKey); ARedBlackNode* FindCeiling(ARedBlackNode* X, int InputKey); //求有多少個數字少於給定數字 int Size(ARedBlackNode* X); int Rank(int InputKey); int Rank(int InputKey, ARedBlackNode* X); //更新各節點路線(先進行層次排序,再更新路線) void UpdateRouteString(); void UpdateRouteString(ARedBlackNode* X); //用二叉搜索樹刪除的方法尋找繼承節點(刪除某個節點后,這個節點會補位) ARedBlackNode* BSTReplace(ARedBlackNode* X); //找后驅節點 ARedBlackNode* Successor(ARedBlackNode* X); //刪除某個節點 void DeleteNode(int InputKey); void DeleteNode(ARedBlackNode* X); //消除雙重黑色節點 void FixDoubleBlack(ARedBlackNode* X); //交換兩個節點的值 void SwapKey(ARedBlackNode* X, ARedBlackNode* Y); //更新所有父節點的節點數 void UpdateParentsCount(ARedBlackNode* X); protected: // Called when the game starts or when spawned virtual void BeginPlay() override; private: //根節點 ARedBlackNode* RootNode; //每個節點輸入的值,可根據具體情況更改,這里只輸入空格 FString FakeValue; //把節點接過的路線記錄下來,方便測試 FString RouteString; //把節點按中序遍歷放進數組 TArray<ARedBlackNode*> OrderNodeArray; //把節點按層次遍歷放進數組 TArray<ARedBlackNode*> LevelOrderNodeArray; }; 紅黑樹.cpp: //如果為True,則是紅色的;如果為False,則是黑色的 bool Red = true; bool Black = false; // Sets default values ARedBlackBST::ARedBlackBST() { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; FakeValue = ""; } // Called when the game starts or when spawned void ARedBlackBST::BeginPlay() { Super::BeginPlay(); FRandomStream Stream; Stream.GenerateNewSeed(); //生成節點 for (int i = 0; i < 1000; i++) { Put(Stream.RandRange(0, 100)); } //Put(40); Put(19); Put(11); Put(25); Put(4); Put(14); Put(20); Put(35); Put(2); Put(5); Put(30); Put(1); Put(34); //中序排列 InorderTraversal(); //觀察搜索樹是否排列正確 for (int i = 0; i < OrderNodeArray.Num(); i++) { UKismetSystemLibrary::PrintString(this, FString::FromInt(i) + ": " + FString::FromInt(OrderNodeArray[i]->GetKey())+" "+ OrderNodeArray[i]->GetValue()); } //測試搜索和查值功能 UKismetSystemLibrary::PrintString(this, "Find 40: " + GetValue(40)); //測試尋找最小值、最大值、Floor、Ceiling UKismetSystemLibrary::PrintString(this, "Min: " + FString::FromInt(FindMin()) + " Max: " + FString::FromInt(FindMax())); UKismetSystemLibrary::PrintString(this, "Floor of 50: " + FString::FromInt(FindFloor(50))); UKismetSystemLibrary::PrintString(this, "Ceiling of 50: " + FString::FromInt(FindCeiling(50))); UKismetSystemLibrary::PrintString(this, "Rank(49): "+FString::FromInt(Rank(49))); UpdateRouteString(); for (int i = 0; i < LevelOrderNodeArray.Num(); i++) { FString Temp; if (LevelOrderNodeArray[i]->GetColor()) Temp = "red"; else Temp = "Black"; UKismetSystemLibrary::PrintString(this, FString::FromInt(i) + ": " + FString::FromInt(LevelOrderNodeArray[i]->GetKey()) + " " + LevelOrderNodeArray[i]->GetValue() + " Count: " + FString::FromInt(LevelOrderNodeArray[i]->GetCount()) + " Color:" + Temp); //if (LevelOrderNodeArray[i]->GetKey() == 2) //{ // //測試尋找鄰居節點和是否有紅色子節點 // UKismetSystemLibrary::PrintString(this, "Sibling: " + FString::FromInt(LevelOrderNodeArray[i]->GetSibling()->GetKey()) // + " HasRedChild: " + UKismetStringLibrary::Conv_BoolToString(LevelOrderNodeArray[i]->HasRedChild())); // //測試尋找繼承節點 // if (BSTReplace(LevelOrderNodeArray[i])) // { // UKismetSystemLibrary::PrintString(this, FString::FromInt(BSTReplace(LevelOrderNodeArray[i])->GetKey())); // } // else UKismetSystemLibrary::PrintString(this, "BSTReplace Not Exist"); //} } //測試刪除 UKismetSystemLibrary::PrintString(this, "StartDelete!"); DeleteNode(14); DeleteNode(34); DeleteNode(4); DeleteNode(11); DeleteNode(35); DeleteNode(RootNode); UpdateRouteString(); for (int i = 0; i < LevelOrderNodeArray.Num(); i++) { FString Temp; if (LevelOrderNodeArray[i]->GetColor()) Temp = "red"; else Temp = "Black"; UKismetSystemLibrary::PrintString(this, FString::FromInt(i) + ": " + FString::FromInt(LevelOrderNodeArray[i]->GetKey()) + " " + LevelOrderNodeArray[i]->GetValue() + " Count: " + FString::FromInt(LevelOrderNodeArray[i]->GetCount()) + " Color:" + Temp); } } // Called every frame void ARedBlackBST::Tick(float DeltaTime) { Super::Tick(DeltaTime); } //判斷一個節點和它父節點的聯系是否是紅色的 bool ARedBlackBST::IsRed(ARedBlackNode* X) { if (!X) return false; return X->GetColor() == Red; } FString ARedBlackBST::GetValue(int InputKey) { ARedBlackNode* X = RootNode; while (X != nullptr) { //比較key的大小 int Temp = CompareTo(InputKey, X->GetKey()); //如果輸入的key比X的小,去X的左邊 if (Temp < 0) X = X->GetNode(true); //如果輸入的key比X的大,去X的右邊 else if (Temp > 0) X = X->GetNode(false); //如果相等,說明找到這個key了,輸出Value else return X->GetValue(); } //如果X為空指針,說明找不到這個key return "NotFind"; } //如果a大於b,返回1;如果a小於b,返回-1;如果相等,返回0 int ARedBlackBST::CompareTo(int a, int b) { if (a > b) return 1; else if (a < b) return -1; else return 0; } ARedBlackNode* ARedBlackBST::RotateLeft(ARedBlackNode* h) { //X節點是h的右節點。 //左旋的結果是: //1.X成為了h的父節點,h是X的左節點。 //2.X原來的左節點變成了h的右節點,其它節點不變,h和X之間的聯系還是紅色。 //如果旋轉前,h是根節點,則旋轉后,X是根節點,X沒父節點 bool HIsRoot = h == RootNode; ARedBlackNode* X = h->GetNode(false); //ARedBlackNode* Parent = h->GetParent(); h->SetNode(false, X->GetNode(true)); X->SetNode(true, h); //如果X節點是紅色節點,則左旋后,h是紅色節點,X是黑色節點 if (X->GetColor() == Red) { X->SetColor(h->GetColor()); h->SetColor(Red); } //左旋后,兩節點的Count更新 h->SetCount(1 + Size(h->GetNode(false)) + Size(h->GetNode(true))); X->SetCount(1 + Size(X->GetNode(false)) + Size(X->GetNode(true))); UKismetSystemLibrary::PrintString(this, "RotateLeft"); if (HIsRoot) { X->SetParent(nullptr); RootNode = X; } return X; } ARedBlackNode* ARedBlackBST::RotateRight(ARedBlackNode* h) { //X節點是h的左節點。 //左旋的結果是: //1.X成為了h的父節點,h是X的右節點。 //2.X原來的右節點變成了h的左節點,其它節點不變,h和X之間的聯系還是紅色。 //如果旋轉前,h是根節點,則旋轉后,X是根節點,X沒父節點 bool HIsRoot = h == RootNode; ARedBlackNode* X = h->GetNode(true); //ARedBlackNode* Parent = h->GetParent(); h->SetNode(true, X->GetNode(false)); X->SetNode(false, h); //如果X節點是紅色節點,則右旋后,h是紅色節點,X是黑色節點 if (X->GetColor() == Red) { X->SetColor(h->GetColor()); h->SetColor(Red); } //右旋后,兩節點的Count更新 h->SetCount(1 + Size(h->GetNode(false)) + Size(h->GetNode(true))); X->SetCount(1 + Size(X->GetNode(false)) + Size(X->GetNode(true))); UKismetSystemLibrary::PrintString(this, "RotateRight"); if (X == RootNode) X->SetParent(nullptr); if (HIsRoot) { X->SetParent(nullptr); RootNode = X; } return X; } void ARedBlackBST::FlipColors(ARedBlackNode* h) { //反轉顏色:如果一個節點的左右聯系都是紅色,則將它們變為黑色,此節點與父節點的聯系變為紅色 h->SetColor(Red); h->GetNode(true)->SetColor(Black); h->GetNode(false)->SetColor(Black); h->SetValue(""); } void ARedBlackBST::Put(int Newkey) { RootNode = Put(RootNode, Newkey); //根節點必須是黑色的 RootNode->SetColor(Black); } ARedBlackNode* ARedBlackBST::Put(ARedBlackNode* h, int NewKey) { if (!h) { ARedBlackNode* NewNode = GetWorld()->SpawnActor<ARedBlackNode>(ARedBlackNode::StaticClass()); NewNode->SetValue(NewKey, FakeValue); //新節點與父節點的聯系一開始是紅色的(后續可能會經過旋轉,反轉等操作,變成黑色) NewNode->SetColor(Red); NewNode->SetCount(1); return NewNode; } //與二叉搜索樹相同,如果新節點的key比h節點的key小,則去h節點的左邊;如果大,則去右邊;如果相同,則覆蓋h節點 int Temp = CompareTo(NewKey, h->GetKey()); //如果要插入新節點,則新節點的所有父節點都要更新一次 if (Temp < 0) h->SetNode(true, Put(h->GetNode(true), NewKey)); else if (Temp > 0) h->SetNode(false, Put(h->GetNode(false), NewKey)); else h->SetValue(FakeValue); //更新h節點的Count h->SetCount(1 + Size(h->GetNode(true)) + Size(h->GetNode(false))); //h與右節點聯系是紅色的,且與左節點的聯系是黑色的,則需要左旋 if (IsRed(h->GetNode(false)) && !IsRed(h->GetNode(true))) h = RotateLeft(h); //如果h與左節點的聯系是紅色的,且h與左節點的左節點的聯系也是紅色的,說明出現連續兩個紅色聯系,需要右旋 if (IsRed(h->GetNode(true)) && IsRed(h->GetNode(true)->GetNode(true))) h = RotateRight(h); //如果h節點的左右聯系都是紅色,則需要反轉(注意:根節點的反轉只會把它們變為黑色,因為它沒有父節點) if (IsRed(h->GetNode(true)) && IsRed(h->GetNode(false))) FlipColors(h); //以上三種情況排序是故意的,因為左旋后可能需要右旋,右旋后需要反轉 //以上三種情況可能會分別觸發,也可能會連續觸發 //給左右子節點設置父節點 //if (h->GetNode(true)) h->GetNode(true)->SetParent(h); //if (h->GetNode(false)) h->GetNode(false)->SetParent(h); return h; } void ARedBlackBST::InorderTraversal() { OrderNodeArray.Empty(); Inorder(RootNode); } void ARedBlackBST::Inorder(ARedBlackNode* X) { if (!X) return; //先去加X的左節點 Inorder(X->GetNode(true)); //再加X OrderNodeArray.Add(X); //最后加X的右節點 Inorder(X->GetNode(false)); } int ARedBlackBST::FindMin() { //從根節點開始比較 ARedBlackNode* X = FindMin(RootNode); if (X) return X->GetKey(); return 0; } ARedBlackNode* ARedBlackBST::FindMin(ARedBlackNode* X) { //當節點存在時 while (X) { //如果左節點存在,繼續循環 if (X->GetNode(true)) { X = X->GetNode(true); } //如果右節點不存在,這個節點就是最小值 else { return X; } } return X; } int ARedBlackBST::FindMax() { //從根節點開始比較 ARedBlackNode* X = FindMax(RootNode); if (X) return X->GetKey(); return 0; } ARedBlackNode* ARedBlackBST::FindMax(ARedBlackNode* X) { //當節點存在時 while (X) { //如果右節點存在,繼續循環 if (X->GetNode(false)) { X = X->GetNode(false); } //如果右節點不存在,這個節點就是最小值 else { return X; } } return X; } int ARedBlackBST::FindFloor(int InputKey) { //從根節點開始比較 ARedBlackNode* X = FindFloor(RootNode, InputKey); if (X) return X->GetKey(); return 0; } ARedBlackNode* ARedBlackBST::FindFloor(ARedBlackNode* X, int InputKey) { //如果X節點不存在,就別繼續下去了 if (!X) return nullptr; int Temp = CompareTo(InputKey, X->GetKey()); //如果存在節點的key與輸入值相等,則這個節點就是最接近它了 if (Temp == 0) return X; //如果節點的key比較大,則去找它的左節點,直到找到小於等於輸入值的節點為止 if (Temp < 0) return FindFloor(X->GetNode(true), InputKey); //如果節點的key比較小,則要找的節點可能在它的右節點的左端 ARedBlackNode* T = FindFloor(X->GetNode(false), InputKey); //如果找到了T,則說明找到了,返回T;如果找不到,說明X已經是最接近的了,返回X if (T) return T; else return X; } int ARedBlackBST::FindCeiling(int InputKey) { //從根節點開始比較 ARedBlackNode* X = FindCeiling(RootNode, InputKey); if (X) return X->GetKey(); return 0; } ARedBlackNode* ARedBlackBST::FindCeiling(ARedBlackNode* X, int InputKey) { //如果X節點不存在,就別繼續下去了 if (!X) return nullptr; int Temp = CompareTo(InputKey, X->GetKey()); //如果存在節點的key與輸入值相等,則這個節點就是最接近它了 if (Temp == 0) return X; //如果節點的key比較小,則去找它的右節點,直到找到大於等於輸入值的節點為止 if (Temp > 0) return FindCeiling(X->GetNode(false), InputKey); //如果節點的key比較大,則要找的節點可能在它的左節點的左端 ARedBlackNode* T = FindCeiling(X->GetNode(true), InputKey); //如果找到了T,則說明找到了,返回T;如果找不到,說明X已經是最接近的了,返回X if (T) return T; else return X; } int ARedBlackBST::Size(ARedBlackNode* X) { //如果節點不存在,返回0 if (!X) return 0; //如果節點存在,返回Count return X->GetCount(); } int ARedBlackBST::Rank(int InputKey) { return Rank(InputKey, RootNode); } int ARedBlackBST::Rank(int InputKey, ARedBlackNode* X) { //如果節點不存在,返回0 if (!X) return 0; int Temp = CompareTo(InputKey, X->GetKey()); //如果給定數字比X的key小,則去X的左邊去找比給定數字小的數字 if (Temp < 0) return Rank(InputKey, X->GetNode(true)); //如果給定數字比X的key大,則X和X的左節點都比給定數字小,把它們算上后,去X的右節點找是否還有比給定數字小的數字 else if (Temp > 0) return 1 + Size(X->GetNode(true)) + Rank(InputKey, X->GetNode(false)); //因為右節點都比X大,而X的Key與給定數字相等,故比給定數字小的數字都在X的左節點里 else return Size(X->GetNode(true)); } void ARedBlackBST::UpdateRouteString() { RouteString = ""; LevelOrderNodeArray.Empty(); UpdateRouteString(RootNode); //RootNode->SetValue("Root"); } void ARedBlackBST::UpdateRouteString(ARedBlackNode* X) { TQueue<ARedBlackNode*> q; q.Enqueue(RootNode); while (!q.IsEmpty()) { ARedBlackNode* T; q.Dequeue(T); LevelOrderNodeArray.Add(T); //如果T的父節點存在 if (T->GetParent()) { //如果T是左節點 if (T == T->GetParent()->GetNode(true)) { FString TempString; TempString.Append(T->GetParent()->GetValue()); TempString.Append("->Left"); T->SetValue(TempString); } //如果T是右節點 else if (T == T->GetParent()->GetNode(false)) { FString TempString; TempString.Append(T->GetParent()->GetValue()); TempString.Append("->Right"); T->SetValue(TempString); } else { T->SetValue("Root"); } } //如果父節點不存在,說明是根節點 else { T->SetValue("Root"); } //將出隊的左節點入隊 if (T->GetNode(true)) { //T->GetNode(true)->SetParent(T); q.Enqueue(T->GetNode(true)); } //將出隊的右節點入隊 if (T->GetNode(false)) { //T->GetNode(false)->SetParent(T); q.Enqueue(T->GetNode(false)); } } } ARedBlackNode* ARedBlackBST::BSTReplace(ARedBlackNode* X) { //如果X節點有兩個子節點,返回后驅節點 if (X->GetNode(true) && X->GetNode(false)) return Successor(X->GetNode(false)); //如果X節點沒有子節點,返回空 else if (!X->GetNode(true) && !X->GetNode(false)) return nullptr; //如果X節點只有一個左節點 else if (X->GetNode(true)) return X->GetNode(true); //如果X節點只有一個右節點 else return X->GetNode(false); } ARedBlackNode* ARedBlackBST::Successor(ARedBlackNode* X) { //后驅節點是X節點下面的最小節點,它沒有左節點 ARedBlackNode* Temp = X; while (Temp->GetNode(true)) Temp = Temp->GetNode(true); return Temp; } void ARedBlackBST::DeleteNode(ARedBlackNode* X) { //繼承節點 ARedBlackNode* u = BSTReplace(X); //u和x都是黑色節點嗎? bool uxBlack = ((!u || u->GetColor() == Black) && (X->GetColor() == Black)); ARedBlackNode* Parent = X->GetParent(); //如果繼承節點u不存在,說明節點X沒子節點 if (!u) { //如果X是根節點,說明想刪根節點,且根節點下面沒節點 if (X == RootNode) RootNode = nullptr; //X是普通節點 else { //如果u,x都是黑色節點,且X沒子節點,那么刪除它將產生雙重黑色節點,需要消除它 if (uxBlack) { //刪除后,節點數更新 Parent->SetCount(Parent->GetCount() - 1); UpdateParentsCount(Parent); //X還沒刪除,X的節點數設為0 X->SetCount(0); FixDoubleBlack(X); } //如果uxBlack==false,說明X節點是紅色的,且沒子節點,直接刪除 else { //刪除后,節點數更新 Parent->SetCount(1); UpdateParentsCount(Parent); } //刪除這個節點后,父節點把這個節點清空 if (X->IsOnLeft()) Parent->SetNode(true, nullptr); else Parent->SetNode(false, nullptr); } //刪除節點 X->Destroy(); return; } //如果X只有一個子節點,那么只有一種可能性:X是黑色節點,u是紅色節點 if (!X->GetNode(true) || !X->GetNode(false)) { //如果X是根節點,且只有一個節點,直接刪除,子節點成為根節點 //這里是把子節點的值和X的值互換,然后刪除子節點u if (X == RootNode) { X->SetKey(u->GetKey()); //刪除后,沒有子節點了 X->SetNode(true, nullptr); X->SetNode(false, nullptr); u->Destroy(); //更新節點數 X->SetCount(1); } //如果X不是根節點,直接刪除X節點,u節點補位,並變成黑色節點 else { //子節點u補位 if (X->IsOnLeft()) Parent->SetNode(true, u); else Parent->SetNode(false, u); X->Destroy(); u->SetParent(Parent); u->SetColor(Black); //u的節點數是正確的,不用更新 //更新U的所有父節點的節點數 UpdateParentsCount(u); } return; } //如果來到這,說明X有兩個子節點,交換u,x的值,繼續刪除u節點 SwapKey(X, u); DeleteNode(u); } void ARedBlackBST::DeleteNode(int InputKey) { //先找到這個節點 ARedBlackNode* X = RootNode; while (X != nullptr) { //比較key的大小 int Temp = CompareTo(InputKey, X->GetKey()); //如果輸入的key比X的小,去X的左邊 if (Temp < 0) X = X->GetNode(true); //如果輸入的key比X的大,去X的右邊 else if (Temp > 0) X = X->GetNode(false); //如果相等,說明找到這個節點了,跳出循環 else break; } //如果X不存在,說明要刪除的節點不存在 if (!X) UKismetSystemLibrary::PrintString(this, "Not Such Node!"); //如果存在,進行刪除 DeleteNode(X); } void ARedBlackBST::FixDoubleBlack(ARedBlackNode* X) { //如果X是雙重黑色根節點,說明此時X有一個紅色子節點,如果是右紅色子節點,需要左旋。隨后返回 if (X == RootNode) { if (X->GetNode(false)) RotateLeft(X); return; } //獲取X的鄰居節點和父節點 ARedBlackNode* Sibling = X->GetSibling(); ARedBlackNode* Parent = X->GetParent(); //如果X不是根節點,則不可能沒有鄰居節點和父節點 if (!Sibling) return; if (!Parent) return; //如果鄰居節點是紅色節點,那么此節點肯定是左節點,右旋 if (Sibling->GetColor() == Red) { //父節點的父節點 ARedBlackNode* GrandParent = Parent->GetParent(); //如果祖父節點存在,需要更新節點聯系 if (GrandParent) { //如果父節點是左節點,把旋轉后的節點放回祖父節點的左節點處 if (Parent->IsOnLeft()) GrandParent->SetNode(true, RotateRight(Parent)); //反之,則放回祖父節點的右節點處 else GrandParent->SetNode(false, RotateRight(Parent)); } //如果祖父節點不存在,說明父節點是根節點,不需要更新祖父節點 else RotateRight(Parent); //為了消除雙重黑色節點,逆反轉顏色 Parent->SetColor(Black); //右旋之后,如果Parent的左子節點存在,把它變紅 if (Parent->GetNode(true)) Parent->GetNode(true)->SetColor(Red); return; } //鄰居節點是黑色 else { //鄰居節點有紅色子節點,這個節點肯定是左節點 if (Sibling->HasRedChild()) { //鄰居節點是左節點時 if (Sibling->IsOnLeft()) { //子節點變為黑色節點 Sibling->GetNode(true)->SetColor(Black); //記錄父節點是不是紅色的 bool RedParent = Parent->GetColor() == Red; //旋轉前,把父節點的顏色變為黑色 Parent->SetColor(Black); //父節點的父節點 ARedBlackNode* GrandParent = Parent->GetParent(); //如果祖父節點存在,需要更新節點聯系 if (GrandParent) { //如果父節點是左節點,把旋轉后的節點放回祖父節點的左節點處 if (Parent->IsOnLeft()) GrandParent->SetNode(true, RotateRight(Parent)); //反之,則放回祖父節點的右節點處 else GrandParent->SetNode(false, RotateRight(Parent)); } //如果祖父節點不存在,說明父節點是根節點,不需要更新祖父節點 else RotateRight(Parent); //如果舊父節點是紅色的,則成為了新父節點的鄰居節點也應該是紅色的 if (RedParent) Sibling->SetColor(Red); } //鄰居節點是右節點時 else { //記錄父節點是不是紅色的 bool RedParent = Parent->GetColor() == Red; //把紅色子節點記錄下來,最后消除完畢時,此節點變成了父節點 ARedBlackNode* RedChild = Sibling->GetNode(true); //旋轉前,把父節點的顏色變為黑色 Parent->SetColor(Black); //Sibling->GetNode(true)->SetColor(Parent->GetColor()); //先對鄰居節點右旋,右旋后,鄰居節點變成了紅色節點 Parent->SetNode(false, RotateRight(Sibling)); //紅色節點變回黑色節點 Sibling->SetColor(Black); //再對父節點左旋,注意,這里的左旋中,父節點的右節點是黑色節點,左旋后,它們還是黑色節點 //父節點的父節點 ARedBlackNode* GrandParent = Parent->GetParent(); //如果祖父節點存在,需要更新節點聯系 if (GrandParent) { //如果父節點是左節點,把旋轉后的節點放回祖父節點的左節點處 if (Parent->IsOnLeft()) GrandParent->SetNode(true, RotateLeft(Parent)); //反之,則放回祖父節點的右節點處 else GrandParent->SetNode(false, RotateLeft(Parent)); } //如果祖父節點不存在,說明父節點是根節點,不需要更新祖父節點 else RotateLeft(Parent); //如果舊父節點是紅色的,則新父節點也應該是紅色的 if (RedParent) RedChild->SetColor(Red); } } //鄰居節點有兩個黑色子節點或沒有子節點時 else { //進行逆反轉顏色 //不管父節點是否是紅色節點,鄰居節點都會變成紅色節點 Sibling->SetColor(Red); //如果父節點是紅色節點,直接變成黑色節點 if (Parent->GetColor() == Red) Parent->SetColor(Black); //如果父節點是黑色節點,變成雙重黑色節點,繼續消除它 FixDoubleBlack(Parent); } } } //交換兩個節點的值 void ARedBlackBST::SwapKey(ARedBlackNode* X, ARedBlackNode* Y) { int Temp; Temp = X->GetKey(); X->SetKey(Y->GetKey()); Y->SetKey(Temp); } void ARedBlackBST::UpdateParentsCount(ARedBlackNode* X) { if (!X) return; //X的所有父節點都要更新節點數 ARedBlackNode* G = X->GetParent(); while (G) { G->SetCount(1 + Size(G->GetNode(true)) + Size(G->GetNode(false))); G = G->GetParent(); } }