【C++】紅黑樹(詳解)

文章目錄

  • 上文鏈接
  • 一、什么是紅黑樹
  • 二、紅黑樹的性質
    • 1. 顏色規則
    • 2. 紅黑樹的規則為什么可以控制平衡
    • 3. 紅黑樹的效率
  • 三、紅黑樹的整體結構
  • 四、紅黑樹的插入
    • 1. 空樹的插入
    • 2. 插入節點的父親為黑色
    • 3. 插入節點的父親為紅色
      • (1) 叔叔為紅色:變色
      • (2) 叔叔為空或為黑色:旋轉 + 變色
        • ① LL 型:右單旋 + 變色
        • ② RR 型:左單旋 + 變色
        • ③ LR 型:左右雙旋 + 變色
        • ④ RL 型:右左雙旋 + 變色
    • 4. 代碼部分
  • 五、紅黑樹的查找與刪除
  • 六、紅黑樹的平衡驗證

上文鏈接

  • 【C++】AVL樹(詳解)

一、什么是紅黑樹

我們之前學習過了二叉搜索樹和 AVL 樹,紅黑樹和 AVL 樹一樣,也是一棵自平衡的二叉搜索樹,AVL 樹是通過控制左右子樹的高度差不超過 1 來保持平衡,而紅黑樹則是為每個結點增加一個存儲位來表示結點的顏色,可以是紅色或者黑色。 通過對任何一條從根到葉子的路徑上各個結點的顏色進行約束來保持樹的左右兩邊的相對平衡。紅黑樹確保沒有一條路徑會比其他路徑長出 2 倍,即 2 * 最短路徑 >= 最長路徑,因而是接近平衡的。

如下圖所示就是一棵紅黑樹:

請添加圖片描述


二、紅黑樹的性質

1. 顏色規則

紅黑樹為何能夠確保沒有一條路徑會比其他路徑長出 2 倍呢?是因為它有如下四個規則:

  1. 每個結點不是紅色就是黑色;
  2. 根結點是黑色的;
  3. 如果一個結點是紅色的,則它的兩個孩子結點必須是黑色的,也就是說任意一條路徑不會有連續的紅色結點;
  4. 對于任意一個結點,從該結點到其所有空結點的簡單路徑上,均包含相同數量的黑色結點。

請添加圖片描述

說明:《算法導論》等書籍上補充了一條每個葉子結點 (NIL) 都是黑色的規則。他這里所指的葉子結點不是傳統的意義上的葉子結點,而是我們說的空結點,有些書籍上也把 NIL 叫做外部結點。NIL 是為了方便準確的標識出所有路徑,《算法導論》在后續講解實現的細節中也忽略了 NIL 結點,所以我們知道一下這個概念即可。

請添加圖片描述

有了上面的描述,我們可以總結出紅黑樹的性質:

  • 左根右:由于它是二叉搜索樹,所以左子樹的值 < 根 < 右子樹的值;
  • 根葉黑:根節點和葉子節點 (NIL) 都是黑色的;
  • 不紅紅:一條路徑上不會出現兩個連續的紅色節點;
  • 黑路同:任意兩條路徑上的黑色節點個數相同。

因此,紅黑樹的性質可以簡記為:左根右,根葉黑,不紅紅,黑路同


2. 紅黑樹的規則為什么可以控制平衡

為什么有了上面四條規則就可以確保紅黑樹中沒有一條路徑會比其他路徑長出 2 倍呢?

黑路同可知,從根到空結點的每條路徑都有相同數量的黑色結點,所以極端場景下,最短路徑就是全為黑色結點的路徑,假設最短路徑長度為 h。

又由不紅紅可知,任意一條路徑不會有連續的紅色結點,所以極端場景下,最長的路徑就是一黑一紅間隔組成,那么最長路徑的長度就為 2 * h。

綜上所述,在最極端的情況下,紅黑樹中的最長路徑也才剛好是最短路徑的兩倍,所以對于任意一棵紅黑樹來說 2 * 最短路徑 >= 最長路徑,因此沒有一條路徑會比其他路徑長出 2 倍。


3. 紅黑樹的效率

請添加圖片描述
黑樹與 AVL 樹的增刪查改的效率都是在同一個檔次,時間復雜度都是 O(log?N)O(\operatorname{log}N)O(logN)

在插入和刪除方面,由于 AVL 樹的平衡條件相對嚴格一些,因此需要頻繁地進行旋轉操作,效率比紅黑樹略低;

在查找方面,紅黑樹不像 AVL 樹那樣保持嚴格的平衡,因此在查找時的效率會比 AVL 樹略低。


三、紅黑樹的整體結構

#pragma once
// 枚舉值表示顏色 
enum Colour
{RED,BLACK
};// 這里我們默認按 key-value 結構實現
template<class K, class V>
struct RBTreeNode
{// 這里更新控制平衡也要加入 parent 指針 pair<K, V> _kv;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;Colour _col;RBTreeNode(const pair<K, V>& kv):_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr){}
};template<class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public:// ...private:Node* _root = nullptr;
};

四、紅黑樹的插入

1. 空樹的插入

當我們對一棵空的紅黑樹進行插入時,直接插入并把其顏色設置為黑色即可。

(下面所討論的插入操作都是建立在樹非空的情況之上)


2. 插入節點的父親為黑色

首先我們需要明確一點,在紅黑樹非空的情況下,我們插入一個節點的顏色必定是紅色

這是因為如果我們把插入節點的顏色設置為黑色的話,那么此時這棵紅黑樹就必然會違反黑路同的性質,之后想要把每條路上的黑色節點數量調整為一樣多的話就會非常麻煩。所以從這個角度來看,樹非空的情況下我們插入的節點都設置為紅色

此時如果它的父親是黑色,那么將不會違反紅黑樹的任何一條性質,那么插入結束。


3. 插入節點的父親為紅色

(1) 叔叔為紅色:變色

我們插入的節點為紅色,此時如果它的父親也為紅色,那么就會違反不紅紅的性質,那么此時必然我們會讓父親變為黑色,但這也意味著父親這條路上多了一個黑色節點,違反了黑路同,我們需要調節黑色節點的數量。此時我們可以讓父親的父親 (爺爺) 變為紅色 (由于父親為紅,所以爺爺變之前必然為黑),再讓叔叔節點變為黑色,這樣一來,我們就可以滿足不紅紅黑路同兩個性質了。

但是,此時由于爺爺變紅,可能又會導致出現兩個紅色節點相鄰的情況,因為爺爺的父親可能為紅色。此時我們只需要把爺爺當作新插入的節點,重復上面的操作即可。下面是圖片演示:

注:下圖中用 c (cur) 表示插入節點,用 p (parent) 表示父節點,用 g (grandparent) 表示爺爺節點,用 u (uncle) 表示叔叔節點。

請添加圖片描述

一直重復這樣的操作,直到不違反紅黑樹的性質或者到根為止。注意到根的時候需要把根變為黑色


(2) 叔叔為空或為黑色:旋轉 + 變色

① LL 型:右單旋 + 變色

父親節點為紅色,叔叔節點為空或為黑色時,我們需要先進行旋轉操作。注意前面我們說過空節點 (NIL) 一定是黑色,所以為空或者為黑色其實是一種情況。具體怎么旋轉呢?

如果此時插入節點的父親節點在爺爺節點的左邊 (left),插入節點在父親節點的左邊 (left),那么我們把這種情況成為 LL 型。如果是 LL 型,那么需要將以爺爺節點為根的子樹進行右單旋操作,旋轉之后將爺爺和父親節點進行變色 (紅變黑,黑變紅)。這樣操作之后,將不會違反紅黑樹的性質,插入結束。

請添加圖片描述

這種旋轉 + 變色的操作不僅會出現在單純的插入過程中,也有可能出現在我們上面講的情況 (1) 的過程中。即在情況 (1) 的向上變色更新過程中發現 cur 的叔叔顏色為黑就需要進行旋轉 + 變色。

請添加圖片描述


② RR 型:左單旋 + 變色

如果此時插入節點的父親節點在爺爺節點的右邊 (right),插入節點在父親節點的右邊 (right),那么我們把這種情況成為 RR 型。如果是 RR 型,那么需要將以爺爺節點為根的子樹進行左單旋操作,旋轉之后將爺爺和父親節點進行變色 (紅變黑,黑變紅)。這樣操作之后,將不會違反紅黑樹的性質,插入結束。

可以看到這種情況與上面的 LL 型幾乎一樣,就是一個對稱的關系,這里就不畫圖演示了。


③ LR 型:左右雙旋 + 變色

如果此時插入節點的父親節點在爺爺節點的左邊 (left),插入節點在父親節點的右邊 (right),那么我們把這種情況成為 LR 型。如果是 LR 型,那么需要將以爺爺節點為根的子樹進行左右雙旋操作,旋轉之后將 cur 和爺爺節點進行變色,插入結束。

請添加圖片描述


④ RL 型:右左雙旋 + 變色

如果此時插入節點的父親節點在爺爺節點的右邊 (right),插入節點在父親節點的左邊 (left),那么我們把這種情況成為 RL 型。如果是 RL 型,那么需要將以爺爺節點為根的子樹進行右左雙旋操作,旋轉之后將 cur 和爺爺節點進行變色,插入結束。

這種情況與 LR 型完全對稱。


4. 代碼部分

// 旋轉代碼的實現跟 AVL 樹非常類似的,只是不需要更新平衡因子
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;// 如果新增節點的父親為紅色,那么需要進行平衡調整// 直到調整到根或者父親為黑色為止,因為判斷條件有兩個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;  // 更新 cur 繼續向上處理parent = cur->_parent;}// 如果叔叔為黑色或者為空,那么需要旋轉 + 變色else{// 如果插入在父親節點的左邊,此時為 LL 型if (cur == parent->_left){// 右單旋 + 變色RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}// LR 型else{// 左右雙旋 + 變色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{// RR 型if (cur == parent->_right){// 左單旋 + 變色RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}// RL 型else{// 右左雙旋 + 變色RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}// 停止更新break;}}}// 變色更新到根節點時有可能根為紅色,需要將根變為黑色_root->_col = BLACK;return true;
}

五、紅黑樹的查找與刪除

紅黑樹的查找與正常二叉搜索樹的查找邏輯一樣。

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;
}

紅黑樹的刪除操作這里不做重點講解,這個操作會比插入稍復雜一些,具體操作可以參考《算法導論》或者《STL源碼剖析》中的講解。


六、紅黑樹的平衡驗證

如何判斷一棵紅黑樹是否合格呢?這里獲取最長路徑和最短路徑,檢查最長路徑不超過最短路徑的 2 倍是不可行的,因為就算滿足這個條 件,紅黑樹也可能顏色不滿足規則,當前暫時沒出問題,后續繼續插入還是會出問題的。所以我們還是去檢查 4 點規則,滿足這 4 點規則,一定能保證最長路徑不超過最短路徑的 2 倍。

  1. 枚舉顏色類型,保證顏色不是黑色就是紅色;
  2. 檢查根的顏色是否為黑色即可;
  3. 不紅紅:前序遍歷檢查,請添加圖片描述
    遇到紅色結點查孩子是否是紅色這樣不太方便,因為孩子有兩個,且不一定存在,但是反過來檢查父親的顏色就方便多了;
  4. 黑路同:前序遍歷,遍歷過程中用形參記錄根到當前節點路徑的 blackNum (黑色結點數量),前序遍歷遇到黑色結點就 ++blackNum,走到空就計算出了一條路徑的黑色結點數量。再用任意一條路徑黑色結點數量作為參考值,依次比較即可。
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);
}bool IsBalance()
{if (_root == nullptr)return true;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);
}

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

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

相關文章

AI提升SEO關鍵詞效果新策略

內容概要 在2025年&#xff0c;人工智能&#xff08;AI&#xff09;技術正全面革新搜索引擎優化&#xff08;SEO&#xff09;的關鍵詞優化模式。通過智能分析用戶搜索意圖與語義關聯&#xff0c;AI能夠精準匹配關鍵詞并進行高效布局。本文將深入探討AI驅動的關鍵詞策略升級方案…

手動安裝的node到nvm吧版本管理的過程。

前言 本文記錄個人在使用nvm包管理器安裝node 14版本 npm安裝失敗&#xff0c;進行手動安裝的node到nvm吧版本管理的過程。 安裝node 14 時 npm總是安裝失敗&#xff0c;如下圖 通過手動下載對于版本 node下載地址 下載解壓點擊所需的版本下載后解壓 修改解壓后的文件夾名稱…

Python爬蟲實戰:構建Widgets 小組件數據采集和分析系統

1. 引言 1.1 研究背景 在當今數字化時代,Widgets 作為用戶界面的基本組成元素,廣泛應用于移動應用、網站和桌面軟件中,其設計質量直接影響用戶體驗。隨著市場競爭的加劇,了解市場上各類 Widgets 產品的特征、價格區間、用戶評價等信息,對于產品設計和商業決策具有重要價…

1.1 Internet簡介

1.網絡, 計算機網絡, 互聯網 2.不同的角度認識Internet1.網絡, 計算機網絡, 互聯網 網絡表示連接兩點以上的通路系統比如:a.你家到鄰居家的小路 -> 一個小網絡b.一個村子的所有道路 -> 一個更大的網絡c.送外賣的小哥騎車走的路線 -> 一個配送網絡計算機網絡表示專門傳…

pytest使用allure測試報告

&#x1f345; 點擊文末小卡片&#xff0c;免費獲取軟件測試全套資料&#xff0c;資料在手&#xff0c;漲薪更快 選用的項目為Selenium自動化測試Pytest框架實戰&#xff0c;在這個項目的基礎上說allure報告。 allure安裝 首先安裝python的allure-pytest包 pip install allu…

PortSwigger靶場之SQL injection with filter bypass via XML encoding通關秘籍

一、題目分析該實驗室的庫存查詢功能存在 SQL 注入漏洞。查詢結果為這些信息會出現在應用程序的響應中&#xff0c;因此您可以利用“聯合”攻擊來從其他表中獲取數據。該數據庫中有一個“用戶”表&#xff0c;該表包含了已注冊用戶的用戶名和密碼。要解決&#xff0c;需進行一次…

Cocos游戲中自定義按鈕組件(BtnEventComponent)的詳細分析與實現

概述在Cocos游戲開發中&#xff0c;按鈕交互是用戶界面中最基礎且重要的組成部分。原生的Cocos Button組件雖然功能完善&#xff0c;但在復雜的游戲場景中往往無法滿足多樣化的交互需求。本文將詳細分析一個功能強大的按鈕組件BtnEventComponent&#xff0c;它提供了多種交互模…

終于完成William F. Egan所著的Practical RF System Design的中文版學習資料

終于完成William F. Egan所著的Practical RF System Design的中文版學習資料 該文檔聚焦RF系統的分析與設計。書中先介紹系統設計流程、書籍結構及工具&#xff08;如電子表格、測試與仿真&#xff09;&#xff0c;接著圍繞RF系統關鍵參數展開&#xff1a;講解增益計算&#xf…

嵌入式Linux驅動開發:蜂鳴器驅動

嵌入式Linux驅動開發&#xff1a;蜂鳴器驅動 1. 引言 本文檔詳細記錄了基于i.MX6ULL處理器的蜂鳴器驅動開發過程。內容涵蓋驅動的理論基礎、代碼實現、設備樹配置以及用戶空間應用程序的編寫。本文檔嚴格遵循用戶提供的代碼和文檔&#xff0c;確保理論與實踐的緊密結合。本文檔…

Qt中的鎖和條件變量和信號量

Qt中的鎖和條件變量和信號量 C11中引入智能指針用來解決鎖忘記釋放的問題 代碼如下&#xff1a; void Thread::run() {for(int i0;i<50000;i){QMutexLocker locker(&mutex);//mutex.lock();num;//mutex.unlock();} }大括號結束的時候&#xff0c;生命周期踩結束&#xf…

智能電視MaxHub恢復系統

公司的MaxHub智能電視又出故障了。 去年硬件故障返廠&#xff0c;花了8600多元。 這次看上去是軟件故障。開機后藍屏報錯。 按回車鍵&#xff0c;電視重啟。 反復折騰幾次&#xff0c;自行修復執行完畢&#xff0c;終于可以進入系統了。 只不過進入windows10后&#xff0c;圖…

TensorFlow 面試題及詳細答案 120道(71-80)-- 性能優化與調試

《前后端面試題》專欄集合了前后端各個知識模塊的面試題,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,SQL,Linux… 。 前后端面試題-專欄總目錄 文章目錄 一、本文面試題目錄 71. 如何優化TensorFlow模…

數據結構 第三輪

以看嚴蔚敏老師的教材為主&#xff0c;輔以其他輔導書&#xff1a;王道&#xff0c;新編數據結構&#xff0c;學校講義 線性結構&#xff1a;線性表、串、隊列、棧、數組和廣義表 樹形結構、網狀結構&#xff1a;圖 查找、排序 動態內存管理和文件 緒論 8-29 數據&#xf…

[新啟航]新啟航激光頻率梳 “光量子透視”:2μm 精度破除遮擋,完成 130mm 深孔 3D 建模

摘要&#xff1a;本文介紹新啟航激光頻率梳的 “光量子透視” 技術&#xff0c;該技術憑借獨特的光量子特性與測量原理&#xff0c;以 2μm 精度破除深孔遮擋&#xff0c;成功完成 130mm 深孔的 3D 建模&#xff0c;為深孔三維形態的精確獲取提供了創新解決方案&#xff0c;推動…

MongoDB /redis/mysql 界面化的數據查看頁面App

MongoDB、Redis 和 MySQL 都有界面化的數據查看工具&#xff0c;以下是相關介紹&#xff1a; MongoDB 輸入MongoDB的賬號密碼即可讀取數據&#xff0c;可訪問數據。 MongoDB Compass&#xff1a;這是 MongoDB 官方提供的 GUI 管理工具&#xff0c;支持 Windows、Mac 和 Linux 等…

Spring Boot 實戰:接入 DeepSeek API 實現問卷文本優化

本文結合 Spring Boot 項目&#xff0c;介紹如何接入 DeepSeek API&#xff0c;自動優化問卷文本&#xff0c;并給出完整示例代碼及詳細注釋。一、項目目標 目標是實現一個 REST 接口&#xff0c;將原始問卷文本提交給 DeepSeek API&#xff0c;然后返回優化后的文本給前端。 接…

opencv實現輪廓繪制和選擇

前面學習了opencv中圖像的一些處理&#xff0c;但對于opencv我們更多的還是對圖像做出一些判斷和識別&#xff0c;所以下面開始學習圖像的識別。 原圖&#xff1a; 一 圖像輪廓的識別 import cv2 pencv2.imread(pen.png,0) ret,new_pencv2.threshold(pen,120,255,cv2.THRESH_…

【Linux】Docker洞察:掌握docker inspect命令與Go模板技巧

&#x1f468;?&#x1f393;博主簡介 &#x1f3c5;CSDN博客專家 ??&#x1f3c5;云計算領域優質創作者 ??&#x1f3c5;華為云開發者社區專家博主 ??&#x1f3c5;阿里云開發者社區專家博主 &#x1f48a;交流社區&#xff1a;運維交流社區 歡迎大家的加入&#xff01…

知料覓得-新一代AI搜索引擎

本文轉載自&#xff1a;知料覓得-新一代AI搜索引擎 - Hello123工具導航 ** 一、&#x1f50d; 初識知料覓得&#xff1a;你的 AI 搜索新伙伴 知料覓得是一款融合了前沿人工智能技術的智能搜索引擎&#xff0c;它旨在徹底改變我們獲取信息的方式。不同于傳統搜索引擎只給你一堆…

高性能網絡轉發中的哈希表技術選型與實踐

引言 在現代網絡編程中,處理大量并發連接是一個常見而重要的挑戰。特別是在中間件、代理服務器和負載均衡器等場景中,如何高效地管理數萬個并發連接并實現數據轉發,對系統性能有著至關重要的影響。本文將圍繞一個具體的網絡轉發場景,深入探討三種不同的哈希表實現(hsearc…