【C++】紅黑樹,“紅“與“黑”的較量

在這里插入圖片描述
各位大佬好,我是落羽!一個堅持不斷學習進步的大學生。
如果您覺得我的文章有所幫助,歡迎多多互三分享交流,一起學習進步!

也歡迎關注我的blog主頁: 落羽的落羽

一、紅黑樹的概念與規則

紅黑樹是一種更加特殊的平衡二叉搜索樹,它在每個節點上增加一個存儲位來表示 節點的顏色(紅色或黑色) ,并通過幾條規則確保樹在插入和刪除操作后仍然保持平衡。

紅黑樹有以下四條規則:

  1. 每個結點不是紅色就是黑色
  2. 根結點是黑色的
  3. 如果一個結點是紅色的,則它的兩個孩子必須都是黑色的,也就是說任意一條路徑上不會出現連續的紅色結點(黑色結點的孩子顏色不做要求)
  4. 對于任意一個結點,從結點到這棵樹所有空結點的簡單路徑上,均包含相同數量的黑色結點

依靠它的幾條規則,從同一結點出發紅黑樹沒有一條路徑會比其他路徑長出2倍,因而也是接近平衡的。這是因為,由于從根到空結點的每條路徑都有相同數量的黑色結點,所以極限場景下,理論最短路徑就是全是黑色結點的路徑(設長度為bh),理論最長路徑是一黑一紅間隔組成,長度就是2bh了。也就是說,紅黑樹中任意一條從根到空結點的路徑x,都有bh <= x <= 2bh,確保了紅黑樹最長路徑不超過最短路徑的2倍了。

假設N是紅黑樹中結點數量,h是最短路徑長度,那么2h -1 <= N <= 22h -1 ,推出h約等于logN,紅黑樹增刪查改最壞情況也就是走最長路徑2*logN,時間復雜度還是O(logN)。

這些規則保證了紅黑樹的平衡性,使得樹的高度保持在logN級別。相比于AVL樹,紅黑樹的平衡結構可以說更加“松散”,AVL樹嚴格要求了左右子樹高度差不超過1,但紅黑樹只要路徑長不超過2倍就行,但它們的效率還是相同的水平。

二、紅黑樹的實現

紅黑樹的結構

紅黑樹的基本結構,不需要AVL樹的平衡因子,而需要一個顏色成員:
enum Color
{RED,BLACK
};template<class K, class V>
struct RBTreeNode
{pair<K, V> _kv;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;Color _col;RBTreeNode(const pair<K,V>& kv):_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED){ }
};template<class K,class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public:private:Node* _root;
};

紅黑樹的插入

紅黑樹的插入新結點的過程是這樣的:
  • 首先按照二叉搜索樹的規則找到應插入的位置,插入后我們需要判斷是否符合紅黑樹的規則。
  • 如果是空樹插入,新增結點必須是黑色的,插入完成;如果是非空樹插入,新增結點必須是紅色的,因為如果非空樹插入了黑色結點就會破壞規則4,這條規則是很難維護的。
  • 非空樹插入紅色結點后,如果父親是黑色的,則不會破壞任何規則,插入完成。
  • 非空樹插入紅色結點后,如果父親是紅色的,就破壞了規則“紅色結點的孩子必須是黑色”,此時需要下面進一步分析。

接下來,我們具體分析最后一條情況,又有幾種場景。下面稱新增結點為c,c的父親為p,p的父親為g,p的兄弟為u。c是紅色的,p也是紅色的,那么g一定是黑色的,這三者的顏色是確定的,所以關鍵是看u的狀態(下圖中只表示出cpgu結點,省略其他子樹):

場景1:

u存在且為紅色。這種場景下,c保持紅色不變,使p和u都變成黑色,g變為紅色。這樣處理后,就能使包含p或u的路徑的黑色結點數量保持一致。

場景2:

u存在且為黑色 或 u不存在。u不存在,則c一定是新增結點;u存在且為黑,則c一定不是新增結點,而是新增結點插入在了c的子樹中通過場景1調整后使c變成了紅色。

這兩種情況下處理方式是一樣的,需要旋轉+變色處理,但是旋轉變色的方式,和AVL樹一樣仍需分情況討論:

  • p是g的左,c是p的左。以g為旋轉點右單旋。然后把p變黑,g變紅。如圖
  • p是g的右,c是p的右。以g為旋轉點左單旋。然后把p變黑,g變紅。如圖
  • p是g的左,c是p的右。**先以p為旋轉點左單旋,再以g為旋轉點右單旋(左右雙旋)。然后把c變黑,g變紅。**如圖
  • p是g的右,c是p的左。**先以p為旋轉點右單旋,再以g為旋轉點左單旋(右左雙旋)。然后把c變黑,g變紅。**如圖

紅黑樹的插入代碼實現:

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;cur->_parent = parent;if (parent->_kv.first < kv.first){parent->_right = cur;}else{parent->_left = cur;}while (parent && parent->_col == RED){Node* grandfather = parent->_parent;//p是g的左的情況if (parent == grandfather->_left){Node* uncle = grandfather->_right;//u存在且為紅色//變色if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}//u不存在或存在為黑色else //旋轉+變色{if (cur == parent->_left){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}//p是g的右的情況else{Node* uncle = grandfather->_left;//u存在且為紅色//變色if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}//u不存在或存在為黑色else //旋轉+變色{if (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return true;
}

紅黑樹的驗證

驗證我們的紅黑樹是否合格,也就需要檢查是否滿足了那四條規則
  • 規則1不用檢查,因為我們一開始就用了枚舉類型賦值
  • 規則2檢查根結點顏色即可
  • 規則3進行前序遍歷,遇到紅色結點就檢查它的父結點是不是紅色的
  • 規則4,首先用一個refNum記錄最左路徑的黑色結點數量,進行前序遍歷,遍歷過程中用一個變量blackNum記錄到當前結點的黑色結點數量,遇到黑色結點就++blackNum,走到空就計算出了一條路徑的黑色結點數量。此時若blackNum與refNum不同,則規則4不滿足。
bool Check(Node* root, int blackNum, const int refNum)
{if (root == nullptr){if (refNum != blackNum){cout << "存在黑色結點數量不相同的路徑!" << endl;return false;}return true;}if (root->_col == RED && root->_parent->_col == RED){cout << "存在連續的紅色結點!" << 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;}//參考值refNum記錄最左路徑的黑色結點數量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/news/915157.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/915157.shtml
英文地址,請注明出處:http://en.pswp.cn/news/915157.shtml

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

相關文章

【愚公系列】《MIoT.VC》001-認識、安裝 MIoT.VC 軟件

??【行業認證權威頭銜】 ? 華為云天團核心成員:特約編輯/云享專家/開發者專家/產品云測專家 ? 開發者社區全滿貫:CSDN博客&商業化雙料專家/阿里云簽約作者/騰訊云內容共創官/掘金&亞馬遜&51CTO頂級博主 ? 技術生態共建先鋒:橫跨鴻蒙、云計算、AI等前沿領域…

git:tag標簽遠程管理

git tag v1&#xff1a;在當前所在分支創建標簽v1git tag -a v2 -m release version&#xff1a;創建一個帶有附注的標簽git tag -d v2&#xff1a;刪除本地標簽git tag&#xff1a;查看標簽git push origin 標簽1 標簽2……&#xff1a;把多個標簽推送到遠程git push origin -…

力扣 hot100 Day49

105. 從前序與中序遍歷序列構造二叉樹 給定兩個整數數組 preorder 和 inorder &#xff0c;其中 preorder 是二叉樹的先序遍歷&#xff0c; inorder 是同一棵樹的中序遍歷&#xff0c;請構造二叉樹并返回其根節點。 //抄的 class Solution { private:unordered_map<int, i…

jvm-sandbox-repeater 錄制和回放

https://github.com/alibaba/jvm-sandbox-repeater/blob/master/docs/user-guide-cn.md 快速錄制自己應用 step0 安裝sandbox和插件到應用服務器 curl -s https://github.com/alibaba/jvm-sandbox-repeater/releases/download/v1.0.0/install-repeater.sh | sh step1 修改repe…

【C++底層剖析】++a vs a++:到底誰是左值,誰是右值?

在 C 編程中&#xff0c;我們經常使用 a 和 a 來實現自增操作。乍一看它們只是“先加還是后加”的語法糖&#xff0c;但你真的理解它們的底層機制、返回值類型和左值右值屬性嗎&#xff1f;1. a 和 a 的基礎區別表達式名稱語義返回值類型左值 / 右值a前置自增先將 a 加 1&#…

【世紀龍科技】汽車故障診斷與排除仿真教學軟件讓課堂更高效安全

隨著汽車產業向智能化、電動化快速轉型&#xff0c;職業院校汽修專業的教學模式正面臨全新挑戰。傳統實車實訓存在成本高、風險大、場景單一等問題&#xff0c;而行業對人才的要求卻越來越高——既需要扎實的理論基礎&#xff0c;又必須具備熟練的故障診斷能力。如何在保證安全…

網絡基礎9:按流負載均衡實驗(等價路由)

實驗eNS拓撲圖&#xff1a;1. 網絡拓撲與 IP 配置AR5&#xff1a;GE0/0/0: 192.168.1.1/24&#xff08;連接 AR6&#xff09;GE0/0/1: 192.168.3.1/24&#xff08;連接 AR8&#xff09;Loopback0: 1.1.1.1/32&#xff08;源地址&#xff09;AR6&#xff1a;GE0/0/0: 192.168.1.…

4G模塊 A7680發送中文短信到手機

命令說明 基礎AT指令 ATi顯示產品的標志信息 ATCIMI查詢IMSI ATCICCID從SIM卡讀取ICCID ATCGSN查詢產品序列號 ATCPIN查詢卡狀態 ATCSQ查詢信號強度 ATCGATT查詢當前PS域狀態 ATCREG查詢GPRS注冊狀態 ATCEREG查詢4G注冊狀態 ATCGPADDR查詢PDP地址 ATCMGF選擇短信格式 ATCMGS發…

深度學習-線性神經網絡

文章目錄線性回歸基本概念隨機梯度下降矢量化加速正態分布和平方損失極大似然估計線性回歸實現從0開始**torch.no_grad()的兩種用途****為什么需要 l.sum().backward()&#xff1f;**調用現成庫softmax回歸圖像數據集從0開始實現softmax利用框架API實現課程學習自李牧老師B站的…

【王樹森推薦系統】推薦系統漲指標的方法04:多樣性

漲指標的方法有哪些&#xff1f; 改進召回模型&#xff0c;添加新的召回模型改進粗排和精排模型提升召回&#xff0c;粗排&#xff0c;精排的多樣性特殊對待新用戶嗎&#xff0c;低活用戶等特殊人群利用關注&#xff0c;轉發&#xff0c;評論這三種交互行為 排序的多樣性 精排多…

1. Spring AI概述

一、前言 Spring AI 是由 Spring 團隊推出的開源項目&#xff0c;旨在為 Java 開發者提供簡潔、一致的 Spring 風格開發體驗&#xff0c;用于構建基于生成式人工智能&#xff08;GenAI&#xff09;和大型語言模型&#xff08;LLM&#xff09;的應用程序。它通過標準化抽象層簡…

[每日隨題10] DP - 重鏈剖分 - 狀壓DP

整體概述 難度&#xff1a;1600 →\rightarrow→ 2200 →\rightarrow→ 2600 P6005 [USACO20JAN] Time is Mooney G 標簽&#xff1a;DP 前置知識&#xff1a;鏈式前向星 難度&#xff1a;綠 1600 題目描述&#xff1a; 輸入格式&#xff1a; 輸出格式&#xff1a; 樣例輸…

【Ubuntu22.04】repo安裝方法

背景 repo是Google開發的用于基于git管理Android版本庫的一個工具&#xff0c;管理多個Git倉庫的工具&#xff0c;它可以幫助您在一個代碼庫中管理多個Git倉庫的代碼。其在鴻蒙操作系統中大量使用。下面我們就介紹repo在wsl中的安裝部署。 安裝方法 使用中國科技大學資源 腳本i…

Vue3的definePros和defineEmits

在 Vue 3 中&#xff0c;defineProps 和 defineEmits 是組合式 API 中用于定義組件的 props 和 事件 的方法&#xff0c;提供了一種更簡潔和明確的方式來管理組件的輸入和輸出。它們屬于 Composition API 的一部分&#xff0c;在 Vue 2 中通常使用 props 和 $emit 來實現。1. d…

【華為機試】122. 買賣股票的最佳時機 II

文章目錄122. 買賣股票的最佳時機 II描述示例 1示例 2示例 3提示解題思路核心觀察關鍵洞察算法實現方法1&#xff1a;貪心算法&#xff08;推薦&#xff09;方法2&#xff1a;動態規劃方法3&#xff1a;動態規劃&#xff08;空間優化&#xff09;方法4&#xff1a;波峰波谷法算…

Spring MVC @RequestParam注解全解析

RequestParam 注解詳解 RequestParam 是 Spring MVC 中最常用的注解之一&#xff0c;用于從 HTTP 請求中提取查詢參數&#xff08;Query String&#xff09;或表單數據。它主要處理 application/x-www-form-urlencoded 類型的請求&#xff08;如 GET 請求或 POST 表單提交&…

從零掌握XML與DTD實體:原理、XXE漏洞攻防

本文僅用于技術研究&#xff0c;禁止用于非法用途。 Author:枷鎖 文章目錄一、XML基礎1. 什么是XML&#xff1f;2. XML語法規則3. 數據類型二、DTD1. 認識DTD2. 聲明DTD3. DTD實體4. 如何防御XXE攻擊&#xff1f;5. 總結一、XML基礎 1. 什么是XML&#xff1f; XML &#xff1…

.NET 8 Release Candidate 1 (RC1)現已發布,包括許多針對ASP.NET Core的重要改進!

.NET 8 Release Candidate 1 (RC1)發布&#xff1a;ASP.NET Core重大改進來襲&#xff01; 近日&#xff0c;.NET 8 Release Candidate 1 (RC1)正式發布&#xff0c;這是在今年晚些時候計劃發布的最終 .NET 8 版本之前的兩個候選版本中的第一個。此版本包含了大部分計劃中的功…

Jenkins pipeline 部署docker通用模板

Jenkinsfile: Docker的NETWORK_NAME不要使用bridge默認網絡&#xff0c;要使用自定義的網絡如test默認 bridge 網絡&#xff1a;容器間不能用名字互相訪問&#xff0c;只能用 IP。自定義網絡&#xff1a;容器間可以用名字互相訪問&#xff0c;Docker 自動做了 DNS 解析。pipeli…

【每日算法】專題十五_BFS 解決 FloodFill 算法

1. 算法思想 Flood Fill 問題的核心需求 給定一個二維網格&#xff08;如像素矩陣&#xff09;、一個起始坐標 (x, y) 和目標顏色 newColor&#xff0c;要求&#xff1a; 將起始點 (x, y) 的顏色替換為 newColor。遞歸地將所有與起始點相鄰&#xff08;上下左右&#xff09; …