C++ 紅黑樹

目錄

1.紅黑樹的概念

2.紅黑樹的性質

3.紅黑樹節點的定義

?4.紅黑樹的插入操作

?5.數據測試


1.紅黑樹的概念

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


2.紅黑樹的性質

1. 每個結點不是紅色就是黑色
2. 根節點是黑色的
3. 如果一個節點是紅色的,則它的兩個孩子結點是黑色的
4. 對于每個結點,從該結點到其所有后代葉結點的簡單路徑上,均 包含相同數目的黑色結點
5. 每個葉子結點都是黑色的(此處的葉子結點指的是空結點)
思考:為什么滿足上面的性質,紅黑樹就能保證:其最長路徑中節點個數不會超過最短路徑節點個數的兩倍?

  • 最短路徑:由于性質4規定從任意節點到葉子節點的所有路徑都包含相同數量的黑色節點,因此由純黑色節點組成的路徑將是最短路徑。這是因為黑色節點是每條路徑上必須包含的,而且沒有其他顏色的節點可以“縮短”這條路徑。
  • 最長路徑:最長路徑將是由紅色和黑色節點交替組成的路徑。由于性質3禁止了連續紅色節點的出現,因此最長路徑將是黑色節點與紅色節點交替出現的情況。由于每條路徑上的黑色節點數量相同(性質4),紅色節點的插入不會增加路徑上黑色節點的數量,而是增加了路徑的總長度。
  • 路徑長度比較:在最長路徑中,每兩個黑色節點之間可能插入一個紅色節點,這使得最長路徑的長度大致為最短路徑(純黑色節點)的兩倍。這是因為在保持黑色節點數量相同的情況下,紅色節點的插入只是在黑色節點之間增加了額外的節點,而這些額外的紅色節點最多只能使路徑長度加倍。

3.紅黑樹節點的定義

enum Colour
{RED,BLACK
};template<class K, class V>
struct RBTreeNode
{RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;pair<K, V> _kv;Colour _col;RBTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED){}
};template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;private:Node* _root = nullptr;
};
  • 成員變量:
    • _left_right?和?_parent?分別指向節點的左子節點、右子節點和父節點。
    • _kv?是一個?pair<K, V>,存儲節點的鍵和值。
    • _col?表示節點的顏色,可以是?RED?或?BLACK
  • 構造函數:
    接收一個?pair<K, V>?作為參數,初始化節點的鍵值對,并將節點的顏色初始化為?RED。其他指針成員初始化為?nullptr

?4.紅黑樹的插入操作

????????我們在進行插入操作時,新節點默認是紅色。紅色節點的插入可能導致紅黑樹的性質被破壞,但通過將新節點設為紅色,我們可以更容易地通過顏色變換和旋轉來恢復平衡。具體來說,紅色節點的插入只會影響局部區域的平衡,而黑色節點的插入則可能影響整棵樹的平衡。

因為新節點的默認顏色是紅色,因此:如果其雙親節點的顏色是黑色,沒有違反紅黑樹任何
性質
,則不需要調整;但當新插入節點的雙親節點顏色為紅色時,就違反了性質三不能有連
在一起的紅色節點
,此時需要對紅黑樹分情況來討論:
約定:cur為當前節點,p為父節點,g為祖父節點,u為叔叔節點
情況一: cur為紅,p為紅,g為黑,u存在且為紅

情況二: cur為紅,p為紅,g為黑,u不存在/u存在且為黑

p為g的左孩子,cur為p的左孩子,則進行右單旋轉;相反,
p為g的右孩子,cur為p的右孩子,則進行左單旋轉


p、g變色--p變黑,g變紅
?

情況三: cur為紅,p為紅,g為黑,u不存在/u存在且為黑(雙旋)

p為g的左孩子,cur為p的右孩子,則針對p做左單旋轉;相反,
p為g的右孩子,cur為p的左孩子,則針對p做右單旋轉

則轉換成了情況2
針對每種情況進行相應的處理即可。

據以上情況寫代碼:

}cur = new Node(kv);cur->_col = RED; // 新增節點給紅色if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;// parent的顏色是黑色也結束while (parent && parent->_col == RED){// 關鍵看叔叔Node* grandfather = parent->_parent;if (parent == grandfather->_left){Node* uncle = grandfather->_right;// 叔叔存在且為紅,-》變色即可if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 繼續往上處理cur = grandfather;parent = cur->_parent;}else // 叔叔不存在,或者存在且為黑{if (cur == parent->_left){//     g  //   p   u// c RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//      g  //   p     u//      c RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else{Node* uncle = grandfather->_left;// 叔叔存在且為紅,-》變色即可if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 繼續往上處理cur = grandfather;parent = cur->_parent;}else // 叔叔不存在,或者存在且為黑{// 情況二:叔叔不存在或者存在且為黑// 旋轉+變色//      g//   u     p//            cif (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//		g//   u     p//      cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;}

代碼解釋:

  1. 初始化新節點:
    • 創建一個新的節點cur,并為其分配鍵值對kv
    • 將新節點的顏色設置為紅色(RED),因為在紅黑樹的規則中,新插入的節點默認為紅色。
    • 根據鍵值對的大小,將新節點插入到其父節點的左子樹或右子樹。
    • 設置新節點的父節點。
  2. 調整紅黑樹以保持其性質:
    • 如果父節點是黑色的,那么不需要進行任何調整,因為插入紅色節點不會違反紅黑樹的性質。
    • 如果父節點是紅色的,那么需要進行調整以恢復紅黑樹的性質。這是通過一個循環來完成的,該循環會持續進行,直到父節點不再是紅色或者已經進行了必要的調整。
  3. 調整過程:
    • 首先,根據父節點是其祖父節點的左子節點還是右子節點,分為兩種情況處理。
    • 在每種情況下,都會考慮叔叔節點的顏色和存在性。
    • 如果叔叔節點是紅色的,那么可以通過重新著色父節點、叔叔節點和祖父節點來恢復紅黑樹的性質,然后將當前節點移動到祖父節點,并更新父節點為祖父節點的父節點,繼續向上調整。
    • 如果叔叔節點是黑色的或者不存在,那么需要進行旋轉操作來恢復紅黑樹的性質。根據當前節點是父節點的左子節點還是右子節點,進行相應的旋轉操作,并重新著色節點。
  4. 結束調整:
    • 一旦退出調整循環,將根節點著色為黑色,以確保紅黑樹的根節點總是黑色的性質。

以下是旋轉邏輯的代碼示例:

	void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;subL->_right = parent;Node* ppNode = parent->_parent;parent->_parent = subL;if (parent == _root){_root = subL;_root->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;Node* ppNode = parent->_parent;parent->_parent = subR;if (parent == _root){_root = subR;_root->_parent = nullptr;}else{if (ppNode->_right == parent){ppNode->_right = subR;}else{ppNode->_left = subR;}subR->_parent = ppNode;}}

?5.數據測試

????????為了更好的測試數據,我們需要寫一個檢查一棵紅黑樹是否滿足紅黑樹的性質,以確保紅黑樹的正確性:

	bool Check(Node* root, int blackNum, const int refNum){if (root == nullptr){//cout << blackNum << endl;if (refNum != blackNum){cout << "存在黑色節點的數量不相等的路徑" << endl;return false;}return true;}if (root->_col == RED && root->_parent->_col == RED){cout << root->_kv.first << "存在連續的紅色節點" << endl;return false;}if (root->_col == BLACK){blackNum++;}return Check(root->_left, blackNum, refNum)&& Check(root->_right, blackNum, refNum);}

代碼解釋:

  1. 空節點檢查

    • 如果root是空指針(即已經到達了一個葉節點),函數會檢查當前路徑上的黑色節點數量blackNum是否與參考數量refNum相等。
    • 如果不相等,會輸出錯誤信息,并返回false,表示檢查失敗。
    • 如果相等,則返回true,表示這條路徑滿足紅黑樹的性質。
  2. 連續紅色節點檢查

    • 如果當前節點的顏色是紅色,并且其父節點的顏色也是紅色,那么會輸出錯誤信息,并返回false。因為紅黑樹的一個關鍵性質是不允許有兩個連續的紅色節點。
  3. 黑色節點計數

    • 如果當前節點的顏色是黑色,blackNum會遞增,以記錄從根節點到當前節點的路徑上黑色節點的數量。
  4. 遞歸檢查

    • 函數會遞歸地對左子樹和右子樹進行相同的檢查。
    • 如果左右子樹都滿足紅黑樹的性質(即遞歸調用都返回true),則整個子樹滿足性質,函數返回true
    • 如果有任何一個子樹不滿足性質,函數返回false

下面是紅黑樹的完整代碼:

enum Colour
{RED,BLACK
};template<class K, class V>
struct RBTreeNode
{RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;pair<K, V> _kv;Colour _col;RBTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED){}
};template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public:bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);cur->_col = RED; // 新增節點給紅色if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;// parent的顏色是黑色也結束while (parent && parent->_col == RED){// 關鍵看叔叔Node* grandfather = parent->_parent;if (parent == grandfather->_left){Node* uncle = grandfather->_right;// 叔叔存在且為紅,-》變色即可if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 繼續往上處理cur = grandfather;parent = cur->_parent;}else // 叔叔不存在,或者存在且為黑{if (cur == parent->_left){//     g  //   p   u// c RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//      g  //   p     u//      c RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else{Node* uncle = grandfather->_left;// 叔叔存在且為紅,-》變色即可if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 繼續往上處理cur = grandfather;parent = cur->_parent;}else // 叔叔不存在,或者存在且為黑{// 情況二:叔叔不存在或者存在且為黑// 旋轉+變色//      g//   u     p//            cif (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//		g//   u     p//      cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;subL->_right = parent;Node* ppNode = parent->_parent;parent->_parent = subL;if (parent == _root){_root = subL;_root->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;if (subRL)subRL->_parent = parent;subR->_left = parent;Node* ppNode = parent->_parent;parent->_parent = subR;if (parent == _root){_root = subR;_root->_parent = nullptr;}else{if (ppNode->_right == parent){ppNode->_right = subR;}else{ppNode->_left = subR;}subR->_parent = ppNode;}}void InOrder(){_InOrder(_root);cout << endl;}bool IsBalance(){if (_root->_col == RED){return false;}int refNum = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){++refNum;}cur = cur->_left;}return Check(_root, 0, refNum);}private:bool Check(Node* root, int blackNum, const int refNum){if (root == nullptr){//cout << blackNum << endl;if (refNum != blackNum){cout << "存在黑色節點的數量不相等的路徑" << endl;return false;}return true;}if (root->_col == RED && root->_parent->_col == RED){cout << root->_kv.first << "存在連續的紅色節點" << endl;return false;}if (root->_col == BLACK){blackNum++;}return Check(root->_left, blackNum, refNum)&& Check(root->_right, blackNum, refNum);}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_InOrder(root->_right);}private:Node* _root = nullptr;
};

數據測試:

void TestRBTree1()
{int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14,8, 3, 1, 10, 6, 4, 7, 14, 13 };RBTree<int, int> t1;for (auto e : a){if (e == 10){int i = 0;}t1.Insert({ e,e });cout << "Insert:" << e << "->" << t1.IsBalance() << endl;}t1.InOrder();cout << t1.IsBalance() << endl;
}

符合預期,代碼正確。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/15976.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/15976.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/15976.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C++基礎與深度解析 | 泛型算法 | bind | Lambda表達式

文章目錄 一、泛型算法1.泛型算法的分類2.迭代器分類 二、bind與lambda表達式1.bind2.lambda表達式 三、泛型算法的改進--ranges(c20) 一、泛型算法 C中的泛型算法是標準模板庫&#xff08;STL&#xff09;的一部分&#xff08;這里重點討論 C 標準庫中定義的算法&#xff0c;而…

【vue-cli搭建vue項目的過程2.x】

vue-cli搭建vue項目 vue-cli搭建vue項目安裝node安裝vue-cli腳手架并創建項目安裝 Ant Design Vue或element-ui(筆者使用Ant-design-vue組件&#xff0c;并全局引入)開發安裝三方庫包1、Package.json文件---引入如下package.json文件執行npm i或npm install命令即可下載如下依賴…

數據結構~~鏈式二叉樹

目錄 一、基本概念 鏈式存儲概念 二、鏈式二叉樹的結構 鏈式二叉樹結構 構建鏈式二叉樹 二叉樹的遍歷 二叉樹節點和高度等 二叉樹銷毀 三、鏈式二叉樹的練習 相同的樹 對稱二叉樹 另外一顆子樹 二叉樹前序遍歷 二叉樹遍歷 四、完整代碼 Tree.h Tree.c 五、總結 一…

Linux服務升級:Predixy 升級代理 Redis-cluster 集群

目錄 一、實驗 1.環境 2. 啟動Redis服務 3.Predixy 升級代理 Redis-cluster 集群 二、問題 1. Predixy進行set操作報錯 2.如何創建腳本啟動predixy 3.Redis代理對比 一、實驗 1.環境 &#xff08;1&#xff09;主機 表1 主機 系統版本節點軟件IP備注CentOS7.9Redis…

Springboot開發 -- Postman 調試類型詳解

引言 在 Spring Boot 應用開發過程中&#xff0c;接口測試是必不可少的一環。Postman 作為一款強大的 API 開發和測試工具&#xff0c;可以幫助開發者輕松構建、測試和管理 HTTP 請求。本文將為大家介紹如何在 Spring Boot 開發中使用 Postman 進行接口測試。 一、準備工作 安…

C/C++|malloc分配內存詳解

看本節前&#xff0c;希望讀者有linux內存分布的基本概念&#xff0c;可以閱讀這篇文章&#xff1a; 進程虛擬地址空間和函數調用棧 在本節中希望讀者可以一口氣閱讀完所有內容。 本博客內容全部來自小林coding&#xff1a;malloc 是如何分配內存的&#xff1f; 這里僅為筆記記…

Python-圖片旋轉360,保存對應圖片

#Author &#xff1a;susocool #Creattime:2024/5/25 #FileName:turn360 #Description: 會旋轉指定的圖像文件360度&#xff0c;并將每個旋轉后的圖像保存到指定目錄&#xff0c;文件名以旋轉角度命名。 from PIL import Imagedef rotate_and_save(image_path, output_dir) :# …

Linux/Ubuntu 中安裝 ZeroTier,實現內網穿透,2分鐘搞定

相信很多人都有遠程連接家中設備的需求&#xff0c;如遠程連接家中的NAS、Windows等服務&#xff0c;所以會涉及到一個內網穿透工具的使用&#xff0c;如果沒有公網IP的情況下&#xff0c;推薦大家使用ZeroTier&#xff0c;這是一款強大的內網穿透工具。 mac和windows版的操作…

Nginx-狂神說

Nginx概述 公司產品出現瓶頸&#xff1f; 我們公司項目剛剛上線的時候&#xff0c;并發量小&#xff0c;用戶使用的少&#xff0c;所以在低并發的情況下&#xff0c;一個jar包啟動應用就夠了&#xff0c;然后內部tomcat返回內容給用戶。 但是慢慢的&#xff0c;使用我們平臺…

HTTP 各版本差異

http1.0 它的特點是每次請球和響應完畢后都會銷毀TCP 連接。同時規走前一個響應完成后才發送下一個請求。這樣做有兩個問題&#xff1a; 無法復用連接了。 每次請求都要創建新的TCP連接&#xff0c;完成三次握手和四次揮手。網絡利用率低 隊頭阻塞 如果前一個請求被某種原因阻…

K8S認證|CKA題庫+答案| 13. sidecar 代理容器日志

目錄 13、使用 sidecar 代理容器日志 CKA v1.29.0模擬系統免費下載試用&#xff1a; 題目&#xff1a; 開始操作&#xff1a; 1&#xff09;、切換集群 2&#xff09;、生成yaml文件 3&#xff09;、官網找模板 4&#xff09;、編輯yaml文件 5&#xff09;、應用yaml…

車載電子電器架構 —— 智能座艙技術

車載電子電器架構 —— 智能座艙技術 我是穿拖鞋的漢子&#xff0c;魔都中堅持長期主義的汽車電子工程師。 老規矩&#xff0c;分享一段喜歡的文字&#xff0c;避免自己成為高知識低文化的工程師&#xff1a; 屏蔽力是信息過載時代一個人的特殊競爭力&#xff0c;任何消耗你的…

qt multiple definition of 報錯解決

qt編譯報了很多錯&#xff0c; multiple definition of xxx 原來一維設計文件ui 的問題 后來發現是pro中頭文件和cpp文件重寫了&#xff0c;導致重復編譯報的錯 解決方法&#xff1a;把重復的頭文件和cpp文件刪了就可以了。

如何解決0.1+0.2!=0.3的問題

var x 0.1; var y 0.2; var z x y // z 的結果為 0.30000000000000004 if (z 0.3) // 返回 false 可以用整數的乘除法來解決 var z (x * 10 y * 10) / 10; // z 的結果為 0.3

GEO數據挖掘-GEO背景知識+表達芯片分析思路

From生物技能樹 GEO數據挖掘第一節 &#xff08;pipeline&#xff09; 文章目錄 1.圖表分析2.GEO背景介紹及分析思路3.代碼分析流程4.復雜數據分析理論知識1.數據從哪里來2.有什么類型的數據可挖掘3.如何篩選基因&#xff08;分析方法&#xff09;在這里插入圖片描述 圖表介紹1…

Jenkins + github 自動化部署配置

1 Jenkins安裝 AWS EC2安裝Jenkins&#xff1a;AWS EC2 JDK11 Jenkins-CSDN博客 AWS EC2上Docker安裝Jenkins&#xff1a;https://blog.csdn.net/hhujjj2005/article/details/139078402 2 登錄jenkins http://192.168.1.128:8080/ $ docker exec -it d1851d9e3386 /bin/ba…

Multi-objective reinforcement learning approach for trip recommendation

Multi-objective reinforcement learning approach for trip recommendation A B S T R A C T 行程推薦是一項智能服務&#xff0c;為游客在陌生的城市提供個性化的行程規劃。 它旨在構建一系列有序的 POI&#xff0c;在時間和空間限制下最大化用戶的旅行體驗。 將候選 POI 添…

【Shell】sed編輯器實例

sed是用來解析和轉換文本的工具&#xff0c;它使用簡單&#xff0c;是簡潔的程序設計語言。 sed編輯器 &#xff08;一&#xff09; sed編輯器基礎1. 簡介2. sed的模式空間 &#xff08;二&#xff09;基本的sed編輯命令&#xff08;三&#xff09;sed命令實例1. 向文件中添加或…

MFC GDI 繪圖模式、映射模式、畫筆、筆、字體

一 GDI 繪圖模式&#xff08;RoP2 Mode&#xff09; 在使用VC MFC進行圖形程序編程時&#xff0c;常會用到GDI繪圖指令&#xff0c;而要做到繪圖時有橡皮筋動態效果&#xff0c;就需設置GDI繪圖模式。GDI繪圖模式有多種&#xff0c;如下&#xff1a; 常用R2_NOT模式來實…

Linux|操作系統|如何下載各個版本的centos操作系統

前言&#xff1a; centos做為一個現在比較常用的Linux社區版本&#xff0c;還是比較受歡迎的&#xff0c;那么&#xff0c;如何下載centos的安裝包&#xff0c;也就是centos的操作系統呢&#xff1f; 首先&#xff0c;我們應該知道硬件底層有aarch64&#xff0c;ppc64&#x…