【C++篇】紅黑樹的實現

目錄

前言:

一,紅黑樹的概念

1.1,紅黑樹的規則

1.2,紅黑樹的最長路徑?

1.3,紅黑樹的效率分析

?二,紅黑樹的實現

2.1,紅黑樹的結構

2.2,紅黑樹的插入

2.2.1,大致過程?

2.2.2,情況1:變色處理?

?2.2.3,情況2:單旋+變色

2.2.4,情況3:雙旋+變色

2.3,紅黑樹插入代碼的實現

2.4,紅黑樹的驗證

三,整體代碼

四,測試代碼


前言:

本篇會用到上篇【AVL樹的實現】中的旋轉知識。

一,紅黑樹的概念

????????紅黑樹是一顆二叉搜索樹,它的每一個節點增加一個存儲為來表示節點的顏色。可以是紅色或者黑色。它通過對從根開始到葉子節點的每條路徑上各個節點顏色的約束,確保最長路徑不會超過最短路徑的2倍,從而實現平衡的。

1.1,紅黑樹的規則

1,每個節點不是紅色就是黑色。

2,根節點是黑色的。

3,紅色節點的兩個孩子只能是 黑色節點或者是空節點。也就是說不能出現連續的紅色節點

4,對于任意一個節點,從該節點開始,到葉子節點的所有路徑上,均包含相同數量的黑色節點。

以上 都是紅黑樹,滿足紅黑樹的規則。

1.2,紅黑樹的最長路徑?

1,由第四條規則可知,從根節點開始的每條路徑上,黑色節點的數量相同。所以在極端場景下,一顆紅黑樹,它的最短路徑就是節點全為黑色的路徑。假設紅黑樹的每條路徑黑色節點數量都為b,那么最短路徑的節點數量為b.

2,由 第三條規則可知,一條路徑上不能由連續的紅色節點,最長路徑是由一黑一紅間隔組成的,所以最長路徑為2*b。

3,而對于一顆紅黑樹,最長和最短路徑不一定存在。我們可以得出對于任意一顆紅黑樹,它的任意 一條路徑長度x都是,b<=x<=2*b.

1.3,紅黑樹的效率分析

假設N是紅黑樹節點的數量,h是最短路徑的長度,最長路徑不超過2*h

可以得到2^h-1<= N <= 2^(2*h)-1,推出h大致為logN,也就意味著紅黑樹的增刪查該最壞走2*logN,時間復雜度O(logN).

?二,紅黑樹的實現

2.1,紅黑樹的結構

enum color
{
?? ?Red,
?? ?Black
};

template<class k,class v>
struct RBTreeNode
{
?? ?RBTreeNode(const pair<k,v>& kv)
?? ??? ?:_left(nullptr)
?? ??? ?,_right(nullptr)
?? ??? ?,_parent(nullptr)
?? ??? ?,_kv(kv)
?? ?{}
?? ?RBTreeNode<k, v>* _left;
?? ?RBTreeNode<k, v>* _right;
?? ?RBTreeNode<k, v>* _parent;
?? ?pair<k, v> _kv;
?? ?color _col;
};

template<class k,class v>
class RBTree
{
?? ?typedef RBTreeNode<k, v> Node;
public:

? ?//...

private:

?? ?Node* _root=nullptr;
};
?

2.2,紅黑樹的插入

2.2.1,大致過程?

1,插入一個值需要按照搜索樹的規則進行插入,再判斷插入后是否滿足紅黑樹的規則。

2,如果是空樹插入,新增節點就是黑色節點。如果是非空樹插入,新增節點就必須是紅色節點,因為如果插入黑色節點,就一定會破壞規則4,而插入紅色節點是有可能會破壞規則3,而且對于規則3來說,解決方法比規則4的解決方法容易。

3,非空樹插入后,如果父節點是黑色節點,則沒有違反任何規則,插入結束。

4,非空樹插入后,如果父節點是紅色節點,則違反規則3,進一步分析。

2.2.2,情況1:變色處理?

由上圖可知,c為紅,p為紅,g為黑,u存在且為紅。在這種情況下,我們需要將p和u變黑,g變紅,g成為新的c,繼續向上更新。

分析:

????????因為p和u都是紅色的,g是黑色的。把p和u變黑,左邊子樹路徑各增加一個黑色節點,g再變紅,相當于g所在路徑的黑色節點數量不變,同時解決了c和p連續紅節點的問題。

????????需要繼續往上跟新是因為g是紅色,如果g的父親還是紅色,就需要繼續處理;如果g的父親是黑色,則處理結束;如果g是整棵樹的根,再將g變成黑色即可。?

?2.2.3,情況2:單旋+變色

c為紅,p為紅,u不存在或者u存在且u為黑色。在這種情況下,就需要進行單旋+變色處理

分析:

u不存在時,c一定是新增節點。

u存在且為黑色時,c一定不是新增節點,是在c的子樹中插入,符號情況1,經過情況1后,c被調整為紅色的。

?上圖展示的是右單旋的場景,下面是根據右單旋,畫出的左單旋大致圖,與右單旋過程相似:

?總結:經過單旋+變色后,我們可以看到p做了子樹新的根,且p是黑色的,所以不管p的父親是黑色的還是紅色的,都滿足紅黑樹的規則,所以此時,就不需要往上更新了。

2.2.4,情況3:雙旋+變色

c為紅,p為紅,g為黑,u不存在或者u存在且為黑,u不存在,則c一定是新增結點,u存在且為黑,則 c一定不是新增,c之前是黑色的,是在c的子樹中插入,符合情況1,變色將c從黑色變成紅色,更新上來的。

?上圖展示的是左右雙旋+變色,同樣右左雙旋類似:

同樣經過雙旋+變色后,c成為新的根,且c為黑色,所以也不需要繼續向上更新了。

2.3,紅黑樹插入代碼的實現

bool Insert(const pair<k, v>& kv)
{//插入根節點,color->Blackif (_root == nullptr){_root = new Node(kv);_root->_col = Black;return true;}//根據搜索樹的規則查找插入位置Node* cur = _root;Node* parent = nullptr;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;elseparent->_left = cur;cur->_parent = parent;//顏色處理+旋轉while (parent&& parent->_col == Red){//gNode* grandfather = parent->_parent;if (parent == grandfather->_left){//p為g的左邊//    g//  p   uNode* uncle = grandfather->_right;//叔叔存在且為紅,情況1if (uncle && uncle->_col == Red){//變色parent->_col = Black;uncle->_col = Black;grandfather->_col = Red;//繼續向上處理cur = grandfather;parent = cur->_parent;}else{//情況2,3//叔叔不存在或者叔叔為黑//u為黑,則c之前是黑的//u不存在,則c是新插入的if (cur == parent->_left){//右單旋場景//   g//  p   u// cRotateR(grandfather);//變色parent->_col = Black;grandfather->_col = Red;}else{//左右雙旋場景//    g//  p   u//     cRotateL(parent);RotateR(grandfather);//變色cur->_col = Black;grandfather->_col = Red;}//不需要進行向上更新break;}}else{//p為g的右邊//   g// u   pNode* uncle = grandfather->_left;if (uncle && uncle->_col == Red){//情況1//變色parent->_col = Black;uncle->_col = Black;grandfather->_col = Red;cur = grandfather;parent = cur->_parent;}else{//情況2,3if (cur == parent->_right){//左單旋場景//  g// u   p//       cRotateL(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;Node* pparent = parent->_parent;if (subLR)subLR->_parent = parent;parent->_left = subLR;subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;_root->_parent = nullptr;}else{if (pparent->_left == parent)pparent->_left = subL;elsepparent->_right = subL;subL->_parent = pparent;}
}
//左單旋
void RotateL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;Node* pparent = parent->_parent;parent->_right = subRL;if (subRL)subRL->_parent = parent;parent->_parent = subR;subR->_left = parent;if (parent == _root){_root = subR;_root->_parent = nullptr;}else{if (pparent->_left == parent)pparent->_left = subR;elsepparent->_right = subR;subR->_parent = pparent;}
}

2.4,紅黑樹的驗證

我們只需驗證形成的樹是否滿足紅黑樹的四條規則即可。

其中,規則1和規則 2可以直接檢查。

對于規則3,我們可以進行前序遍歷,遍歷到一個節點,判斷該節點的顏色,再判斷它的兩個孩子的顏色,這樣做太麻煩了。我們可以反過來,遍歷到一個節點,如果他是紅色的,判斷它的父親節點是否為黑色。

對于規則4,我們可以先從根開始找到一條路徑上黑色節點的個數refNum,再對整棵樹進行前序遍歷,用變量 blackNum記錄黑色節點的個數,當遍歷到空的時候,與refNum比較即可。

bool Check(Node* root, int BlackNum, int num)
{if (root == nullptr){if (BlackNum != num)return false;return true;}if (root->_col == Red && root->_parent->_col == Red)return false;if (root->_col == Black)BlackNum++;return Check(root->_left, BlackNum, num) && Check(root->_right, BlackNum, num);
}
//驗證
bool isbalance()
{if (_root == nullptr)return true;if (_root->_col == Red)return false;int num = 0;Node* cur = _root;while (cur){if (cur->_col == Black)num++;cur = cur->_left;}return Check(_root, 0, num);
}

三,整體代碼

enum color
{Red,Black
};template<class k,class v>
struct RBTreeNode
{RBTreeNode(const pair<k,v>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv){}RBTreeNode<k, v>* _left;RBTreeNode<k, v>* _right;RBTreeNode<k, v>* _parent;pair<k, v> _kv;color _col;
};template<class k,class v>
class RBTree
{typedef RBTreeNode<k, v> Node;
public:bool Insert(const pair<k, v>& kv){//插入根節點,color->Blackif (_root == nullptr){_root = new Node(kv);_root->_col = Black;return true;}//根據搜索樹的規則查找插入位置Node* cur = _root;Node* parent = nullptr;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;elseparent->_left = cur;cur->_parent = parent;//顏色處理+旋轉while (parent&& parent->_col == Red){//gNode* grandfather = parent->_parent;if (parent == grandfather->_left){//p為g的左邊//    g//  p   uNode* uncle = grandfather->_right;//叔叔存在且為紅,情況1if (uncle && uncle->_col == Red){//變色parent->_col = Black;uncle->_col = Black;grandfather->_col = Red;//繼續向上處理cur = grandfather;parent = cur->_parent;}else{//情況2,3//叔叔不存在或者叔叔為黑//u為黑,則c之前是黑的//u不存在,則c是新插入的if (cur == parent->_left){//右單旋場景//   g//  p   u// cRotateR(grandfather);//變色parent->_col = Black;grandfather->_col = Red;}else{//左右雙旋場景//    g//  p   u//     cRotateL(parent);RotateR(grandfather);//變色cur->_col = Black;grandfather->_col = Red;}//不需要進行向上更新break;}}else{//p為g的右邊//   g// u   pNode* uncle = grandfather->_left;if (uncle && uncle->_col == Red){//情況1//變色parent->_col = Black;uncle->_col = Black;grandfather->_col = Red;cur = grandfather;parent = cur->_parent;}else{//情況2,3if (cur == parent->_right){//左單旋場景//  g// u   p//       cRotateL(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;Node* pparent = parent->_parent;if (subLR)subLR->_parent = parent;parent->_left = subLR;subL->_right = parent;parent->_parent = subL;if (parent == _root){_root = subL;_root->_parent = nullptr;}else{if (pparent->_left == parent)pparent->_left = subL;elsepparent->_right = subL;subL->_parent = pparent;}}//左單旋void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;Node* pparent = parent->_parent;parent->_right = subRL;if (subRL)subRL->_parent = parent;parent->_parent = subR;subR->_left = parent;if (parent == _root){_root = subR;_root->_parent = nullptr;}else{if (pparent->_left == parent)pparent->_left = subR;elsepparent->_right = subR;subR->_parent = pparent;}}void Inorder(){_Inorder(_root);}int Height(){return _Height(_root);}int size(){return _size(_root);}Node* Find(const k& key){Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;}else if (cur->_kv.first > key){cur = cur->_left;}else{return cur;}}return nullptr;}//驗證bool isbalance(){if (_root == nullptr)return true;if (_root->_col == Red)return false;int num = 0;Node* cur = _root;while (cur){if (cur->_col == Black)num++;cur = cur->_left;}return Check(_root, 0, num);}
private:bool Check(Node* root, int BlackNum, int num){if (root == nullptr){if (BlackNum != num)return false;return true;}if (root->_col == Red && root->_parent->_col == Red)return false;if (root->_col == Black)BlackNum++;return Check(root->_left, BlackNum, num) && Check(root->_right, BlackNum, num);}int _size(Node* root){if (root == nullptr)return 0;return _size(root->_left) + _size(root->_right) + 1;}int _Height(Node* root){if (root == nullptr)return 0;int leftHeight = _Height(root->_left);int rightHeight = _Height(root->_right);return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;}void _Inorder(Node* root){if (root == nullptr)return;_Inorder(root->_left);cout << root->_kv.first << ":" << root->_kv.second << endl;_Inorder(root->_right);}Node* _root=nullptr;
};

四,測試代碼

void TestRBTree1()
{RBTree<int, int> t;// 常規的測試用例int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };// 特殊的帶有雙旋場景的測試用例//int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };for (auto e : a){t.Insert({ e, e });}t.Inorder();cout << t.isbalance() << endl;
}
int main()
{TestRBTree1();return 0;
}

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

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

相關文章

如何在谷歌瀏覽器中設置自定義安全警告

隨著網絡環境的日益復雜&#xff0c;瀏覽器的安全問題也愈發引人關注。谷歌瀏覽器作為一款廣泛使用的瀏覽器&#xff0c;其自定義安全警告功能為用戶提供了更加個性化和安全的瀏覽體驗。本文將詳細介紹如何在谷歌瀏覽器中設置自定義安全警告&#xff0c;幫助用戶更好地保護自己…

Spring 6 第1章——概述

一.Spring是什么 Spring是一款主流的Java EE輕量級&#xff08;體積小、不需要依賴其它組件&#xff09;開源框架Spring的目的是用于簡化Java企業級應用的開發難度和開發周期Spring的用途不僅限于服務端的開發&#xff0c;從簡單性、可測試性和松耦合的角度而言&#xff0c;任…

C語言預處理藝術:編譯前的魔法之旅

大家好&#xff0c;這里是小編的博客頻道 小編的博客&#xff1a;就愛學編程 很高興在CSDN這個大家庭與大家相識&#xff0c;希望能在這里與大家共同進步&#xff0c;共同收獲更好的自己&#xff01;&#xff01;&#xff01; 本文目錄 引言正文一、預處理的作用與流程&#xf…

基于Springboot + vue實現的旅游網站

&#x1f942;(???)您的點贊&#x1f44d;?評論&#x1f4dd;?收藏?是作者創作的最大動力&#x1f91e; &#x1f496;&#x1f4d5;&#x1f389;&#x1f525; 支持我&#xff1a;點贊&#x1f44d;收藏??留言&#x1f4dd;歡迎留言討論 &#x1f525;&#x1f525;&…

docker-compose和docker倉庫

一、docker-compose 1.概述 docker-compose是一個自動編排工具&#xff0c;可以根據dockerfile自動化部署docker容器。 主要功能 配置定義 使用YAML文件&#xff08;通常命名為docker - compose.yml&#xff09;來描述應用程序的服務、網絡和卷等配置。 容器編排 可以同時…

MAC AndroidStudio模擬器無網絡

先確認PC端是正常訪問網絡的&#xff1b; 模擬器端修改Wifi設置&#xff1a;設置 - 網絡和互聯網 - WALN設置 按照上圖修改&#xff1b; IP設置&#xff1a;從DHCP修改為靜態&#xff0c;IP地址&#xff1a;10.0.2.16 &#xff0c;網關&#xff1a;10.0.2.2 &#xff0c; DNS…

Wireshark 使用教程:網絡分析從入門到精通

一、引言 在網絡技術的廣闊領域中&#xff0c;網絡協議分析是一項至關重要的技能。Wireshark 作為一款開源且功能強大的網絡協議分析工具&#xff0c;被廣泛應用于網絡故障排查、網絡安全檢測以及網絡協議研究等諸多方面。本文將深入且詳細地介紹 Wireshark 的使用方法&#x…

Java 面試題 - ArrayList 和 LinkedList 的區別,哪個集合是線程安全的?

Java 面試題 - ArrayList 和 LinkedList 的區別&#xff0c;哪個集合是線程安全的&#xff1f; 在 Java 開發中&#xff0c;ArrayList和LinkedList是兩個常用的集合類&#xff0c;它們在數據結構和性能上有諸多不同&#xff0c;同時線程安全性也各有特點。深入理解這些差異&am…

nvim 打造成可用的IDE(2)

上一個 文章寫的太長了&#xff0c; 后來再寫東西 就一卡一卡的&#xff0c;所以新開一個。 主要是關于 bufferline的。 之前我的界面是這樣的。 這個圖標很不舒服有。 后來發現是在這里進行配置。 我也不知道&#xff0c;這個配置 我是從哪 抄過來的。 測試結果&#xff1…

升級 SpringBoot3 全項目講解 — 為什么 SpringBoot3 應該拋棄 Maven,搭配 Gradle 來使用?

學會這款 &#x1f525;全新設計的 Java 腳手架 &#xff0c;從此面試不再怕&#xff01; 隨著 Spring Boot 3 的發布&#xff0c;許多開發者開始考慮如何將現有項目升級到最新版本。Spring Boot 3 帶來了許多新特性&#xff0c;包括對 Java 17 的支持、更好的性能優化以及對 G…

Java學習筆記(二十三)

1 CacheEvict CacheEvict是Spring框架中用于清空緩存的注解。以下是對CacheEvict注解的詳細介紹&#xff1a; 1.1 作用 CacheEvict注解的主要作用是刪除緩存中的數據。在方法執行后或執行前&#xff08;根據配置&#xff09;&#xff0c;它可以清空指定的緩存項或整個緩存區…

如何優化Elasticsearch大文檔查詢?

記錄一次業務復雜場景下DSL優化的過程 背景 B端商城業務有一個場景就是客戶可見的產品列表是需要N多閘口及各種其它邏輯組合過濾的&#xff0c;各種閘口數據及產品數據都是存儲在ES的(有的是獨立索引&#xff0c;有的是作為產品屬性存儲在產品文檔上)。 在實際使用的過程中&a…

openCvSharp 計算機視覺圖片找茬

一、安裝包 <PackageReference Include"OpenCvSharp4" Version"4.10.0.20241108" /> <PackageReference Include"OpenCvSharp4.runtime.win" Version"4.10.0.20241108" /> 二、準備兩張圖片 三、編寫代碼 using OpenCv…

實戰:FRP內網穿透部署-支持ssh、web訪問

目錄 1 準備工作2 公網服務器部署server端2.1 frps.ini配置 3 內網客戶端部署client端3.1 frpc.ini配置&#xff08;內網服務器01&#xff09;3.2 frpc.ini配置&#xff08;內網服務器02&#xff09; 4 服務啟動腳本4.1 公網服務器 server4.2 內網服務器 client 2 systemctl常見…

Uniapp中實現加載更多、下拉刷新、返回頂部功能

一、加載更多&#xff1a; 在到達底部時&#xff0c;將新請求過來的數據追加到原來的數組即可&#xff1a; import {onReachBottom } from "dcloudio/uni-app";const pets ref([]); // 顯示數據function network() {uni.request({url: "https://api.thecatap…

C# 多線程 Task TPL任務并行

先總結一下 之前發展過程的要點 1&#xff1a; 為了保證多線程正確順序執行 線程同步 2&#xff1a; 為了節省操作系統線程資源 線程池 異步 方式管理 正常來講 使用這倆個要點 進行使用 多線程可以滿足開發使用需求 但是 新的問題產生了 那就是 多個異步操作 需要編寫大量的代…

C++單例模式的設計

單例模式&#xff08;Singleton Pattern&#xff09;是一種設計模式&#xff0c;用于確保一個類只有一個實例&#xff0c;并提供一個全局訪問點來訪問該實例。在C中&#xff0c;單例模式通常用于管理全局資源或共享狀態。 以下是C中實現單例模式的幾種常見方式&#xff1a; 懶…

HBASE學習(一)

1.HBASE基礎架構&#xff0c; 1.1 參考&#xff1a; HBase集群架構與讀寫優化&#xff1a;理解核心機制與性能提升-CSDN博客 1.2問題&#xff1a; 1.FLUSH對hbase的影響 2. HLog和memstore的區別 hlog中存儲的是操作記錄&#xff0c;比如寫、刪除。而memstor中存儲的是寫入…

Flutter:封裝ActionSheet 操作菜單

演示效果圖 action_sheet_util.dart import package:ducafe_ui_core/ducafe_ui_core.dart; import package:flutter/material.dart; import package:demo/common/index.dart;class ActionSheetUtil {/// 底部操作表/// [context] 上下文/// [title] 標題/// [items] 選項列表 …

【Rust練習】28.use and pub

練習題來自&#xff1a;https://practice-zh.course.rs/crate-module/use-pub.html 1 使用 use 可以將兩個同名類型引入到當前作用域中&#xff0c;但是別忘了 as 關鍵字. use std::fmt::Result; use std::io::Result;fn main() {}利用as可以將重名的內容取別名&#xff1a;…