紅黑樹(Red-BlackBalancedSearchTree)
1.什麼是紅黑樹
紅黑樹本質上是二叉搜尋樹的改良版,因此,對二叉搜尋樹不瞭解的,建議先去看一下二叉搜尋樹。
二叉搜尋樹有個嚴重的缺陷:樹本身並不平衡,很容易造成部分分支過長,而部分分支過短的情況,從而影響到了搜尋速度。
二叉搜尋樹的一個極端例子:
如果把一個遞增的陣列按順序放進二叉搜尋樹,就會出現上圖的情況,要搜尋某個值就得查N次。
紅黑樹就可以解決這個問題,且能保證在大部分情況下,樹都是平衡的。
(紅黑樹圖中的空節點只是為了表示另一個節點是左節點還是右節點。)
要深入理解紅黑樹的含義,就需要先了解2-3搜尋樹。
2.2-3搜尋樹
2-3搜尋樹也是二叉搜尋樹改良版,也能保證樹的平衡。這裡只介紹2-3搜尋樹,不用程式碼實現它。
如上圖,2-3搜尋樹有兩種節點:有兩個分支的節點和有三個分支的節點。
有兩個分支的節點中,左子節點比父節點小,右子節點比父節點大。
有三個分支的節點中,左子節點比父節點裡的所有數小,右子節點比父節點所有數大,中子節點介於父節點的兩個數之間,如下圖:
c<a<d<b<e
插入數值
從例子介紹思路:
現有2-3搜尋樹如下圖。
如果要插入值13,首先從根節點開始比較,13<15,故13走向左節點。
13>10,但由於左節點沒有右子節點,因此10和13成為有三個分支的節點。如下圖。
如果再插入值14,首先從根節點開始比較,14<15,故14走向左節點。
14>13,但由於此節點沒有右子節點,因此10,13,14成為新節點,但這個含有3個值的節點只是臨時的,會經歷下一段變化。
含有3個值的節點A中,處於中間值的13會移到此節點的父節點中。即根節點變成含有13,15的新節點。節點A分裂成兩個只含1個值的節點。如下圖:
如果再插入值25,首先從根節點開始比較,25>15,故25走向右節點。
25>20,但由於右節點沒有右子節點,因此20和25成為有三個分支的節點。如下圖。
如果再插入值30,首先從根節點開始比較,30>15,故30走向右節點。
30>25,但由於右節點沒有右子節點,因此20,25,30成為新節點;
然後25移到父節點,20,30分別成為新節點,如下圖:
然後含有13,15,25的節點分裂,15成為新的根節點,13,25是15的子節點,如下圖:
如此類推。
2-3搜尋樹介紹到這,下面將介紹紅黑樹。
3.紅黑樹
紅黑樹與2-3搜尋樹本質上很接近。紅黑樹有兩種節點:紅色節點與黑色節點。
紅色節點表示此節點與它的父節點的聯絡是紅色的。
黑色節點表示此節點與它的父節點的聯絡是黑色的。
如果把紅色相連的兩個節點看成一個節點,則變成了2-3搜尋樹中的含有三個分支的節點,如下圖:
(紅黑樹圖中的空節點只是為了表示另一個節點是左節點還是右節點。)
如果我們對根節點為19的2-3搜尋樹插入值34,則會變成下圖:(從左圖逐步演變成右圖)
如果我們對根節點為19的紅黑樹插入值34,按照紅黑樹的遊戲規則,會變成下圖:(從左圖逐步演變成右圖)
上圖演變過程按順序經歷了左旋、右旋、反轉顏色、左旋三種變化。(紅黑樹只有這三種變化。)
由上圖對比可知,2-3搜尋樹是把中間值34推到了30和35的上一層;紅黑樹是把中間值34推到了35的上一層。其實,紅黑樹就是2-3搜尋樹的改進版,因為2-3搜尋樹有兩種節點,所以實現起來相當複雜,紅黑樹就相對簡單多了。
我們只需瞭解了這三種變化是怎麼操作的和2-3搜尋樹的變化原理,就能理解紅黑樹的原理。
紅黑樹每次插入的新節點都是紅色的!而這個新節點比二叉搜尋樹的節點只多了一個名為Color的bool變數。紅色Red為true,黑色Black為false。
紅黑樹節點程式碼:
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 (Left) LeftNode = NewNode; else { RightNode = NewNode; } } 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; };
下面,我們將介紹這三種變化:
左旋:
遊戲規則一:在紅黑樹中,所有紅色的聯絡都是向左的。
所以當插入的新值與父節點形成的紅色聯絡向右時,我們需要通過左旋來把它糾正。
也是從例子入手:
這裡發現了一個向右的紅色聯絡,需要左旋。我們關注的物件是25和34。
左旋後,25和34還是紅色聯絡,但34在上面,25成為了34的左節點。
34本來的左節點30是介於25和34之間的。由於25成為了34的左節點,34的右節點不變,34節點的左右子節點滿負荷,節點30遊離了出來。
25的左節點不變,25的右節點空出來了。節點30補到了25的右節點處。變成下圖:
實現程式碼:
ARedBlackNode* ARedBlackBST::RotateLeft(ARedBlackNode* h) { //X節點是h的右節點。 //左旋的結果是: //1.X成為了h的父節點,h是X的左節點。 //2.X原來的左節點變成了h的右節點,其它節點不變,h和X之間的聯絡還是紅色。 ARedBlackNode* X = h->GetNode(false); h->SetNode(false, X->GetNode(true)); X->SetNode(true, h); 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"); return X; }
右旋:
按道理來講,由於在紅黑樹中,所有紅色的聯絡都是向左的。右旋看起來是沒必要的。
但是,遊戲規則二:在紅黑樹中,兩個紅色的聯絡不能連續出現。
即這種情況:
這個規則從2-3搜尋樹中理解是:30,34,35形成了一個含有3個值的臨時節點,需要把中間值擠到上一層去。
為了不造成誤解,我們用a,b放入上述例子中(a>35,34<b<35)。這裡假設存在整數b滿足這個要求。
右旋時,我們關注的是第一個紅色聯絡,即這裡的34和35。
如左旋類似的:
右旋後,35和34還是紅色聯絡,但34在上面,35成為了34的右節點。
34本來的右節點b是介於35和34之間的。由於35成為了34的右節點,34的左節點不變,34節點的左右子節點滿負荷,節點b遊離了出來。
35的右節點不變,35的左節點空出來了。節點b補到了35的左節點處。變成下圖:
實現程式碼:
ARedBlackNode* ARedBlackBST::RotateRight(ARedBlackNode* h) { //X節點是h的左節點。 //左旋的結果是: //1.X成為了h的父節點,h是X的右節點。 //2.X原來的右節點變成了h的左節點,其它節點不變,h和X之間的聯絡還是紅色。 ARedBlackNode* X = h->GetNode(true); h->SetNode(true, X->GetNode(false)); X->SetNode(false, h); 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"); return X; }
反轉顏色:
遊戲規則三:一個節點與左節點和右節點的聯絡不能同時為紅色。
即這種情況:
反轉顏色很簡單,從2-3搜尋樹中理解是:30,34,35形成了一個含有3個值的臨時節點,需要把中間值擠到上一層去。
我們只需要把這兩個紅色聯絡改為黑色,並把34和34的父節點的聯絡改為紅色即可。(注意:由於根節點沒父節點,所以根節點永遠是黑色。)
如圖:
實現程式碼:
void ARedBlackBST::FlipColors(ARedBlackNode* h) { //反轉顏色:如果一個節點的左右聯絡都是紅色,則將它們變為黑色,此節點與父節點的聯絡變為紅色 h->SetColor(Red); h->GetNode(true)->SetColor(Black); h->GetNode(false)->SetColor(Black); h->SetValue(""); }
4.紅黑樹除了刪除功能之外的各種功能實現
瞭解了紅黑樹的3種變化後,實現構建紅黑樹的難度就不高了。我們只需在插入新值時,判斷一下節點是否需要左旋、右旋或者反轉顏色,如果需要則進行相關操作即可。
插入新值的實現程式碼:
void ARedBlackBST::Put(int Newkey) { RootNode = Put(RootNode, Newkey); } 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); //以上三種情況排序是故意的,因為左旋後可能需要右旋,右旋後需要反轉 //以上三種情況可能會分別觸發,也可能會連續觸發 return h; }
由於紅黑樹本質上是二叉搜尋樹,所以二叉搜尋樹的所有功能都可以在紅黑樹中使用。其中要特別注意的是 :
1. 給定一個keyX,求有多少個節點的key比X小(即求Rank(X))
2. 二叉搜尋樹的刪除功能
由於左旋和右旋都會改變節點的位置,所以進行左旋和右旋時,需要更新相關節點的Count(此節點下面共有多少個節點(包括自己))。如果每個節點的Count是準確的,那麼求Rank(X)也能正常執行。
二叉搜尋樹的刪除功能在紅黑樹中雖然可以用,但會破壞紅黑樹的結構。紅黑樹有自己的一套刪除功能,由於比較複雜,寫在這會導致篇幅過長,因此寫在下一篇隨筆裡。
紅黑樹的其它功能程式碼實現基本上可以從二叉搜尋樹的程式碼中複製過來,這裡不重複講。具體實現可以從下面的完整程式碼中尋找,如果功能實現思路不理解的,可以重溫一下二叉搜尋樹。
5.節點的路線列印
這個功能屬於除錯用功能,與紅黑樹的具體實現無關。
由於程式輸出的只是一個數組,我們怎麼知道紅黑樹是否正確建立呢?
為此,我在每個節點中,記錄了它們的路線位置。如根節點為root;根節點的左節點為root->Left;根節點的左節點為root->Right;根節點的左節點的左節點為root->Left->Left。
實現思路:每個節點的路線=父節點的路線+此節點是父節點的左節點還是右節點
把整個紅黑樹按層次排序後,從上到下逐一更新路線。
實現程式碼:
void ARedBlackBST::UpdateRouteString() { RouteString = ""; LevelOrderNodeArray.Empty(); UpdateRouteString(RootNode); } 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 { FString TempString; TempString.Append(T->GetParent()->GetValue()); TempString.Append("->Right"); T->SetValue(TempString); } } //如果父節點不存在,說明是根節點 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)); } } }
6.完整的全部程式碼
節點.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 (Left) LeftNode = NewNode; else { RightNode = NewNode; } } 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); 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 < 100; i++) { Put(Stream.RandRange(0, 100)); } Put(40); //中序排列 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++) { UKismetSystemLibrary::PrintString(this, FString::FromInt(i) + ": " + FString::FromInt(LevelOrderNodeArray[i]->GetKey()) + " " + LevelOrderNodeArray[i]->GetValue() + " Count: "+ FString::FromInt(LevelOrderNodeArray[i]->GetCount())); } } // 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之間的聯絡還是紅色。 ARedBlackNode* X = h->GetNode(false); h->SetNode(false, X->GetNode(true)); X->SetNode(true, h); 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"); return X; } ARedBlackNode* ARedBlackBST::RotateRight(ARedBlackNode* h) { //X節點是h的左節點。 //左旋的結果是: //1.X成為了h的父節點,h是X的右節點。 //2.X原來的右節點變成了h的左節點,其它節點不變,h和X之間的聯絡還是紅色。 ARedBlackNode* X = h->GetNode(true); h->SetNode(true, X->GetNode(false)); X->SetNode(false, h); 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"); 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); } 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); //以上三種情況排序是故意的,因為左旋後可能需要右旋,右旋後需要反轉 //以上三種情況可能會分別觸發,也可能會連續觸發 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); } 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 { FString TempString; TempString.Append(T->GetParent()->GetValue()); TempString.Append("->Right"); T->SetValue(TempString); } } //如果父節點不存在,說明是根節點 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)); } } }