C++實(shí)現(xiàn)紅黑樹(shù)核心插入實(shí)例代碼
一、紅黑樹(shù)概念介紹
概念:
紅黑樹(shù),也是一種二叉搜索樹(shù),它是在每個(gè)結(jié)點(diǎn)上增加一個(gè)存儲(chǔ)位表示結(jié)點(diǎn)的顏色,可以是紅或黑,然后通過(guò)對(duì)任何一條從根到葉子的路徑上各個(gè)結(jié)點(diǎn)著色方式的限制,保證了沒(méi)有一條路徑會(huì)能超過(guò)其他路徑的倆倍,因而是近似平衡的。map和set的底層數(shù)據(jù)結(jié)構(gòu)就是用紅黑樹(shù)來(lái)封裝的。
性質(zhì):
1.根節(jié)點(diǎn)是黑色的
2.不能出現(xiàn)連續(xù)的紅色節(jié)點(diǎn)
3. 每條路徑上有相同數(shù)量的黑色節(jié)點(diǎn)
4. 每個(gè)葉子(空節(jié)點(diǎn))結(jié)點(diǎn)都是黑色
5. 每個(gè)結(jié)點(diǎn)不是紅色就是黑色
滿足上面的性質(zhì),紅黑樹(shù)就能保證:最長(zhǎng)路徑中節(jié)點(diǎn)個(gè)數(shù)不會(huì)超過(guò)最短路徑節(jié)點(diǎn)個(gè)數(shù)的2倍。
優(yōu)勢(shì):
假設(shè)全部的黑色節(jié)點(diǎn)有N個(gè) 最短路徑長(zhǎng)度是logN 整棵樹(shù)的節(jié)點(diǎn)數(shù)量:[N,2N],最長(zhǎng)路徑長(zhǎng)度:2logN。假設(shè)10億個(gè)節(jié)點(diǎn),AVL:最多查找30次左右;RB:最多查找60次左右。
綜合而言,其實(shí)對(duì)于查找大量數(shù)據(jù)30次和60次沒(méi)太大差別,而紅黑樹(shù)不要求絕對(duì)平衡,只需保證最長(zhǎng)路徑不超過(guò)最短路徑的2倍,相對(duì)來(lái)說(shuō)降低了插入和旋轉(zhuǎn)的次數(shù),所以在經(jīng)常進(jìn)行增刪的結(jié)構(gòu)中性能比AVL樹(shù)更優(yōu)。
二、紅黑樹(shù)模擬實(shí)現(xiàn)
(1)紅黑樹(shù)節(jié)點(diǎn)
enum Color { RED=0, BLACK, }; template<class K,class V> struct RbTreeNode { RbTreeNode<K, V>* _left; RbTreeNode<K, V>* _right; RbTreeNode<K, V>* _parent; pair<K, V>_kv; Color _col; RbTreeNode(const pair<K, V>& kv) :_left(nullptr) , _right(nullptr) , _parent(nullptr) , _kv(kv) , _col(RED) {} };
里面同樣用三叉鏈,要有左孩子右孩子,父節(jié)點(diǎn),以及pair類的類型來(lái)存儲(chǔ)kv兩個(gè)相同或不同類型的值(當(dāng)我們使用make_pair函數(shù)創(chuàng)建一個(gè)pair對(duì)象,該函數(shù)會(huì)自動(dòng)推斷參數(shù)類型),并在里面增加一個(gè)枚舉,用來(lái)表示一個(gè)節(jié)點(diǎn)不是紅色就是黑色。關(guān)于構(gòu)造函數(shù)里面的顏色初始化為黑色還是紅色,在插入會(huì)講解。
(2)紅黑樹(shù)插入分析(核心)
插入和前面的AVL插入部分是一樣的,在里面如果是根節(jié)點(diǎn),那就把跟點(diǎn)為置為黑色。
那這有一個(gè)問(wèn)題就是節(jié)點(diǎn)構(gòu)造函數(shù)里面的一個(gè)問(wèn)題:我們?cè)谏暾?qǐng)一個(gè)新節(jié)點(diǎn)時(shí),寧愿新增紅色還是黑色?
因?yàn)椴还苁窃瞿姆N顏色,本質(zhì)上是違反規(guī)則三還是違反規(guī)則四的問(wèn)題,如果插入黑色,一定違反規(guī)則四,但它代價(jià)太大,違反規(guī)則三就不一定,因?yàn)樗母腹?jié)點(diǎn)可能為紅色也可能為黑色,如果在黑色節(jié)點(diǎn)下面插入就沒(méi)影響,反之就會(huì)影響。
對(duì)于上面的做法會(huì)出現(xiàn)下面的兩大情況,第一GPC為一一條直線,第二GPC為折線。
如圖:
G為祖父節(jié)點(diǎn),P為父親節(jié)點(diǎn),C為當(dāng)前節(jié)點(diǎn),U為叔父節(jié)點(diǎn)
1、先解決當(dāng)GPC為一條直線的時(shí)候:
(1)如果U存在且為紅色,P必須變黑,U也得變黑,左右都增加了黑,那就把G變紅,這樣久能保持黑色節(jié)點(diǎn)不變,因?yàn)镚現(xiàn)在變成紅了,但是G的父節(jié)點(diǎn)如果是紅色的還得繼續(xù)處理 ,那就把G當(dāng)成C節(jié)點(diǎn),繼續(xù)算它的祖父,再看U,如果U是紅再把它變成黑。
它的所對(duì)應(yīng)的抽象圖以及具象圖:
(2)如果U不存在或者存在且為黑又是兩種情況
如果U不存在,再增加紅節(jié)點(diǎn),且有連續(xù)的紅色節(jié)點(diǎn),有可能會(huì)超過(guò)它的最短路徑,只能旋轉(zhuǎn)來(lái)降高度。把P變黑,把G給P的左,再把P作為左,同時(shí)把原來(lái)的G變紅,這里就是左單旋加G和P變色。
如下例圖:
它所對(duì)應(yīng)的抽象及具象圖:
如果U存在且為黑色
U存在且為黑是由第一種請(qǐng)況變化而來(lái),這里發(fā)生了右單旋,P的左孩子作為G的左孩子,而G又作為P的右孩子,且P變?yōu)楹?,G變?yōu)榧t。
對(duì)于上面的U不存在或者存在為黑兩種情況進(jìn)行總結(jié): P為G的左孩子時(shí),C為P的左孩子進(jìn)行右單旋轉(zhuǎn),如果P為G的右孩子,C為P的右孩子,進(jìn)行左單旋轉(zhuǎn)。P變?yōu)楹谏?,G變?yōu)榧t色。
旋轉(zhuǎn)的目的就是為了防止最長(zhǎng)路徑超過(guò)最短路徑的2倍。GPC為一條線的時(shí)候就是左單旋轉(zhuǎn)或右單旋。
2、再解決GPC為折線的情況
(1)U存在且為黑
出現(xiàn)這種折線情況的原因是由第一種情況先變色而產(chǎn)生,然后p變成g的左,c變成p的右,c為紅,p為紅,g為黑,接著我們先以p為軸進(jìn)行左旋轉(zhuǎn)進(jìn)行降低高度,在以g為軸進(jìn)行右旋,且g變紅,c變黑,因?yàn)檫@樣才能保證不出現(xiàn)連續(xù)紅結(jié)點(diǎn)且保證每個(gè)路徑黑色數(shù)量一樣。以上實(shí)際進(jìn)行了雙旋。
(2)U不存在,直接右單旋
(3)插入代碼思路(如何快速寫(xiě)插入算法)
(一)先看左面這種大情況
插入C1肯定為紅節(jié)點(diǎn),P1如果是紅,就需要開(kāi)始作處理:先利用三叉鏈,從P1得出G1,進(jìn)入核心環(huán)節(jié):左面的這種情況是P1是G1的左孩子時(shí),里面又要有分兩種情況:
(1)如果是U存在且為紅,我們把P1和U變?yōu)楹冢珿1變?yōu)榧t,更新C1到G1的位置,繼續(xù)往上調(diào)整;
(2)如果是U不存在或黑(是一種情況),如果U不存在就不能變黑,而且就算存在為黑說(shuō)明是一定是由繼續(xù)往上調(diào)整的那一步(情況(1))而來(lái)的即繼續(xù)往上處理,我們還是是增加了節(jié)點(diǎn),就可能導(dǎo)致最長(zhǎng)路徑超過(guò)最短路徑的2倍或者一直用情況(1)的方式最終導(dǎo)致每條路徑黑色數(shù)量不相等。這時(shí)候我們就要通過(guò)旋轉(zhuǎn)方式降低高度加變色來(lái)解決。所以在這里又分兩種:G1P1C1為直線和G1P1C1為折線:
如果是C1P1在一條直線上即C1是P1的左孩子,我們以G1為軸進(jìn)行右單旋(哪邊高往哪邊降),G1要變紅,P1要變成黑,因?yàn)镚1下來(lái)了P1作為根,不能出現(xiàn)連續(xù)的紅節(jié)點(diǎn)P要變黑,又因?yàn)楸WC黑色數(shù)量相等,G1要變紅。如果是C1和P1,G1是折線的時(shí)候即C1是P1的右孩子,這時(shí)候需要先以P1為軸進(jìn)行左單旋,再以G1為軸進(jìn)行右單旋,最終C1做了根節(jié)點(diǎn),G1是右孩子,因?yàn)镻1是紅,C1是紅不能出現(xiàn)連續(xù)紅節(jié)點(diǎn),需把C1變黑,但是還要保證黑色數(shù)量相等,就把G1變紅。最后結(jié)束break,只有情況(1)才會(huì)繼續(xù)向上來(lái)回處理。
(二)再看右面這種大情況
和(一)情況類似
P2是G2的右孩子,同理那旋轉(zhuǎn)方式就根上面相反。
所以我們的插入函數(shù):
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); if (parent->_kv.first > kv.first) { parent->_left = cur; } else { parent->_right = cur; } cur->_parent = parent; while (parent && parent->_col == RED) { Node* grandf = parent->_parent; if (grandf->_left == parent) { Node* unc = grandf->_right; if (unc && unc->_col == RED)//u存在且為紅,變色處理 { parent->_col = BLACK; unc->_col = BLACK; grandf->_col = RED; cur = grandf;//繼續(xù)往上調(diào)整 parent = cur->_parent; } else//u不存在或者黑 { if (parent->_left == cur) { RotateR(grandf); parent->_col = BLACK; grandf->_col = RED; } else { RotateL(parent); RotateR(grandf); cur->_col = BLACK; grandf->_col = RED; } break; } } else { Node* unc = grandf->_left; if (unc && unc->_col == RED) { parent->_col = BLACK; unc->_col = BLACK; grandf->_col = RED; cur = grandf; parent = cur->_parent; } else { if (parent->_right == cur) { RotateL(grandf); parent->_col = BLACK; grandf->_col = RED; } else { RotateR(parent); RotateL(grandf); cur->_col = BLACK; grandf->_col = RED; } break; } } } _root->_col = BLACK; return true; }
(4)判斷平衡函數(shù)
bool IsBalance() { if (_root && _root->_col == RED) { cout << "根節(jié)點(diǎn)顏色錯(cuò)誤" << endl; return false; } int Bsign = 0; Node* cur = _root; while (cur) { if (cur->_col == BLACK) { ++Bsign; } cur = cur->_left; } return _check(_root, 0, Bsign); } bool _check(Node* root, int Bnum, int Bsign) { if (root == nullptr) { if (Bnum != Bsign) { cout << "黑色節(jié)點(diǎn)數(shù)量不相等" << endl; return false; } return true; } if (root->_col == BLACK) { ++Bnum; } if (root->_col == RED && root->_parent && root->_parent->_col == RED) { cout << "連續(xù)紅色節(jié)點(diǎn)" << endl; return false; } return _check(root->_left,Bnum,Bsign) && _check(root->_right, Bnum, Bsign); } void _Destroy(Node* root) { if (root == nullptr) { return; } _Destroy(root->_left); _Destroy(root->_right); delete root; }
首先不能以最長(zhǎng)路徑不超過(guò)最短路徑2倍為準(zhǔn)則判斷平衡,它只是結(jié)果。
還是要以前面說(shuō)的規(guī)則:
第一個(gè):先檢查根是不是黑色的;
第二個(gè):再檢查連續(xù)的紅色節(jié)點(diǎn):如果是紅色的就去檢查兒子,這種方式不行,因?yàn)楹⒆涌赡懿淮嬖?,可以改為它的父親,如果是紅一定有父親,如果父親為紅就存在連續(xù)紅色節(jié)點(diǎn)直接返回假;
還有一個(gè):最重要的規(guī)則:判斷黑色節(jié)點(diǎn)的數(shù)量是否相等,我們需要先記錄一下黑色數(shù)量,我們可以先獲取一條路徑的黑色數(shù)量Bsign,然后以它為標(biāo)準(zhǔn),如何讓每條路徑以它標(biāo)準(zhǔn)判斷是否相等?我們可以在參數(shù)里面的形參傳值Bnum,往下遞歸就會(huì)往下傳,下一層++不會(huì)影響上一層++,因?yàn)橄乱粚邮巧弦粚拥目截?,只要有一條不相等就返回假。
(5)查找函數(shù)
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; }
從根開(kāi)始走,如果比它大,就往右邊走,如果比它小,就往左邊走,如果相等說(shuō)明找到,否則返回空。
(6)測(cè)試函數(shù)
void Test() { int arr[] = { 33, 22, 6, 1, 3, 5, 66, 7, 16, 14, 16, 3, 7, 11, 9, 36, 18, 14, 15 }; RbTree<int, int> t; for (auto n : arr) { t.Insert(make_pair(n, n)); } t.Inorder(); cout << endl; cout << t.IsBalance() << endl; cout << "高度:"<<t.height() << endl; cout << endl; cout << "隨機(jī)數(shù)據(jù)測(cè)試:" << endl; srand(time(0)); const size_t N = 100000; RbTree<int, int> r; for (size_t i = 0; i < N; ++i) { size_t x = rand() + i; r.Insert(make_pair(x, x)); } cout << r.IsBalance() << endl; cout <<"高度:"<< r.height() << endl; }
(7)測(cè)試結(jié)果
三、紅黑樹(shù)源代碼
(1)RbTree.h
#pragma once #include<iostream> #include<utility> #include<ctime> using namespace std; enum Color { RED=0, BLACK, }; template<class K,class V> struct RbTreeNode { RbTreeNode<K, V>* _left; RbTreeNode<K, V>* _right; RbTreeNode<K, V>* _parent; pair<K, V>_kv; Color _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: ~RbTree() { _Destroy(_root); _root = nullptr; } 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); if (parent->_kv.first > kv.first) { parent->_left = cur; } else { parent->_right = cur; } cur->_parent = parent; while (parent && parent->_col == RED) { Node* grandf = parent->_parent; if (grandf->_left == parent) { Node* unc = grandf->_right; if (unc && unc->_col == RED)//u存在且為紅,變色處理 { parent->_col = BLACK; unc->_col = BLACK; grandf->_col = RED; cur = grandf;//繼續(xù)往上調(diào)整 parent = cur->_parent; } else//u不存在或者黑 { if (parent->_left == cur) { RotateR(grandf); parent->_col = BLACK; grandf->_col = RED; } else { RotateL(parent); RotateR(grandf); cur->_col = BLACK; grandf->_col = RED; } break; } } else { Node* unc = grandf->_left; if (unc && unc->_col == RED) { parent->_col = BLACK; unc->_col = BLACK; grandf->_col = RED; cur = grandf; parent = cur->_parent; } else { if (parent->_right == cur) { RotateL(grandf); parent->_col = BLACK; grandf->_col = RED; } else { RotateR(parent); RotateL(grandf); cur->_col = BLACK; grandf->_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; } void Inorder() { _InOrder(_root); } int height() { return _Height(_root); } bool IsBalance() { if (_root && _root->_col == RED) { cout << "根節(jié)點(diǎn)顏色錯(cuò)誤" << endl; return false; } int Bsign = 0; Node* cur = _root; while (cur) { if (cur->_col == BLACK) { ++Bsign; } cur = cur->_left; } return _check(_root, 0, Bsign); } private: void RotateL(Node* parent)//左單旋 { Node* suR = parent->_right;//11的右孩子即22 Node* suRL = suR->_left;//11的右孩子的左孩子即b Node* ppnode = parent->_parent;//維護(hù)父結(jié)點(diǎn)的父結(jié)點(diǎn) parent->_right = suRL;//11右孩子鏈接b if (suRL)//如果b不為空 suRL->_parent = parent;//更新雙親結(jié)點(diǎn) suR->_left = parent;//22上提,左孩子鏈接11 parent->_parent = suR;//更新雙親結(jié)點(diǎn) if (ppnode == nullptr) { _root = suR; _root->_parent = nullptr; } else { if (ppnode->_left == parent) { ppnode->_left = suR; } else { ppnode->_right = suR; } suR->_parent = ppnode; } } void RotateR(Node* parent)//右單旋 { Node* suL = parent->_left; Node* suLR = suL->_right; parent->_left = suLR; if (suLR) suLR->_parent = parent; Node* ppnode = parent->_parent; suL->_right = parent; parent->_parent = suL; if (parent == _root) { _root = suL; _root->_parent = nullptr; } else { if (ppnode->_left == parent) { ppnode->_left = suL; } else { ppnode->_right = suL; } suL->_parent = ppnode; } } void _InOrder(Node* root) { if (root == nullptr) { return; } _InOrder(root->_left); cout << root->_kv.first << " "; _InOrder(root->_right); } int _Height(Node* root) { if (root == NULL) return 0; int leftH = _Height(root->_left); int rightH = _Height(root->_right); return leftH > rightH ? leftH + 1 : rightH + 1; } bool _check(Node* root, int Bnum, int Bsign) { if (root == nullptr) { if (Bnum != Bsign) { cout << "黑色節(jié)點(diǎn)數(shù)量不相等" << endl; return false; } return true; } if (root->_col == BLACK) { ++Bnum; } if (root->_col == RED && root->_parent && root->_parent->_col == RED) { cout << "連續(xù)紅色節(jié)點(diǎn)" << endl; return false; } return _check(root->_left,Bnum,Bsign) && _check(root->_right, Bnum, Bsign); } void _Destroy(Node* root) { if (root == nullptr) { return; } _Destroy(root->_left); _Destroy(root->_right); delete root; } private: Node* _root; }; void Test() { int arr[] = { 33, 22, 6, 1, 3, 5, 66, 7, 16, 14, 16, 3, 7, 11, 9, 36, 18, 14, 15 }; RbTree<int, int> t; for (auto n : arr) { t.Insert(make_pair(n, n)); } t.Inorder(); cout << endl; cout << t.IsBalance() << endl; cout << "高度:"<<t.height() << endl; cout << endl; cout << "隨機(jī)數(shù)據(jù)測(cè)試:" << endl; srand(time(0)); const size_t N = 100000; RbTree<int, int> r; for (size_t i = 0; i < N; ++i) { size_t x = rand() + i; r.Insert(make_pair(x, x)); } cout << r.IsBalance() << endl; cout <<"高度:"<< r.height() << endl; }
(2)Test.cpp
#include"RbTree.h" int main() { Test(); return 0; }
總結(jié)
到此這篇關(guān)于C++實(shí)現(xiàn)紅黑樹(shù)核心插入的文章就介紹到這了,更多相關(guān)C++紅黑樹(shù)核心插入內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- C++詳細(xì)實(shí)現(xiàn)紅黑樹(shù)流程詳解
- C++實(shí)現(xiàn)紅黑樹(shù)應(yīng)用實(shí)例代碼
- C++?STL容器詳解之紅黑樹(shù)部分模擬實(shí)現(xiàn)
- C++數(shù)據(jù)結(jié)構(gòu)之紅黑樹(shù)的實(shí)現(xiàn)
- C++超詳細(xì)分析紅黑樹(shù)
- C++數(shù)據(jù)結(jié)構(gòu)紅黑樹(shù)全面分析
- C++?RBTree紅黑樹(shù)的性質(zhì)與實(shí)現(xiàn)
- C++使用一棵紅黑樹(shù)同時(shí)封裝出map和set實(shí)例代碼
- C++紅黑樹(shù)應(yīng)用之手搓set和map
相關(guān)文章
詳談C++ socket網(wǎng)絡(luò)編程實(shí)例(2)
這篇文章主要為大家介紹了C++ socket網(wǎng)絡(luò)編程實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2021-11-11Qt實(shí)現(xiàn)蘋(píng)果狀態(tài)切換按鈕
這篇文章主要為大家詳細(xì)介紹了Qt實(shí)現(xiàn)蘋(píng)果狀態(tài)切換按鈕,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-08-08C++實(shí)現(xiàn)LeetCode(75.顏色排序)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(75.顏色排序),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07Qt使用事件與定時(shí)器實(shí)現(xiàn)字幕滾動(dòng)效果
我們經(jīng)常能夠在外面看到那種滾動(dòng)字幕,那么本文就拿Qt來(lái)做一個(gè)吧,本文將使用事件與定時(shí)器實(shí)現(xiàn)字幕滾動(dòng)的效果,感興趣的小伙伴可以了解一下2023-06-06C語(yǔ)言鏈表實(shí)現(xiàn)學(xué)生成績(jī)管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言鏈表實(shí)現(xiàn)學(xué)生成績(jī)管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07