目錄
一、紅黑樹的概念
二、紅黑樹的性質
2.1 紅黑樹與AVL樹的比較
三、紅黑樹的實現
3.1 紅黑樹節點的定義
3.2 數據的插入
3.2.1 紅黑樹的調整思路
3.2.1.1?cur為紅,f為紅,g為黑,u存在且為紅
3.2.1.2?cur為紅,f為紅,g為黑,u不存在/u存在且為黑
3.2.1.2.1 g、f、cur構成一條直線
3.2.1.2.2?g、f、cur構成一條折線
3.2.2 調整部分的代碼實現
3.3 紅黑樹的驗證
3.4 測試代碼
四、紅黑樹實現完整代碼
一、紅黑樹的概念
紅黑樹,是一種二叉搜索樹,但在每個結點上增加一個存儲位表示結點的顏色,可以是Red或Black。 通過對任何一條從根到葉子的路徑上各個結點著色方式的限制,紅黑樹確保沒有一條路 徑會比其他路徑長出倆倍,因而是接近平衡的。
二、紅黑樹的性質
● 每個結點不是紅色就是黑色
● 根節點是黑色的
● 如果一個節點是紅色的,則它的兩個孩子結點是黑色的(不允許出現連續的紅色節點)
● 對于每個結點,從該結點到其所有后代葉結點的簡單路徑上,均包含相同數目的黑色結點(從根節點到每個葉子節點的空孩子路徑上有相同數目的黑色節點)
● 每個葉子結點的空孩子節點都是黑色的
根據上述的五條性質,我們可以發現一個紅黑樹中如果有N個黑色節點,則根節點到任意一個葉子節點的距離:最短為㏒⑵N,最長為2㏒⑵N
2.1 紅黑樹與AVL樹的比較
我們來看到下面的紅黑樹:
對于這棵紅黑樹,如果將其看成一個AVL樹,是需要進行旋轉的,但是在紅黑樹結構中卻不需要
所以紅黑樹是近似平衡的,在搜索效率上會略遜AVL樹一些,但是紅黑樹在結構上不要求絕對的平衡,這就造成插入相同的數據紅黑樹翻轉的次數少于AVL樹
實際使用中,在經常進行增刪的場景下紅黑樹性能比AVL樹更優,并且紅黑樹實現比較簡單,所以實際運用中紅黑樹更多
三、紅黑樹的實現
3.1 紅黑樹節點的定義
enum Colour
{RED,BLACK
};template<class Key, class Val>
struct RBTreeNode
{RBTreeNode<Key, Val>* _left;RBTreeNode<Key, Val>* _right;RBTreeNode<Key, Val>* _parent;//多一個指針指向其父節點,方便我們的后續操作pair<Key, Val> _kv;Colour _col;//顏色標識RBTreeNode(const pair<Key, Val>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_col(RED)//構造時,優先將節點的顏色置為紅色{}
};
在這里提一下為什么要默認將節點的顏色置為紅色:
在我們向紅黑樹中插入一個新節點時,如果將該節點置為黑色,就肯定會影響紅黑樹性質中的第四條:對于每個結點,從該結點到其所有后代葉結點的簡單路徑上,均包含相同數目的黑色結點
例如:
我們現在在上面這棵樹中,不管在哪個葉子節點的下方插入一個黑色的新增節點,從根節點到插入的節點的空孩子的路徑上的黑色節點數目會變為4,而從根節點到其他葉子節點的空孩子的路徑上的黑色節點數目會都為3
所以我們將新增節點的顏色置為紅色就一定不會違反第四條紅黑樹性質,但是第三條呢?如果插入節點的父節點是紅色的怎么辦?
怎么辦我們后面再說,反正總歸比置為黑色一定會違反第四條性質好吧
3.2 數據的插入
由于紅黑樹也是平衡二叉搜索樹的一種,我們在插入數據時也要找到合適的位置進行插入:
template<class Key, class Val>
class RBTree
{typedef RBTreeNode<Key, Val> Node;
public:bool Insert(const pair<Key,Val>& kv){Node* cur = _root, * parent = nullptr;while (cur)//找到合適的位置{if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else{cout << "插入的值重復" << endl;return false;}}cur = new Node(kv);cur->_parent = parent;//將插入的節點連接上二叉樹if (parent == nullptr){_root = cur;}else if (kv.first < parent->_kv.first){parent->_left = cur;}else{parent->_right = cur;}return true;}private:Node* _root = nullptr;
};
插入到合適的位置之后,我們還要檢查是否破壞了紅黑樹的結構(由于我們插入的是紅色節點,所以只會出現兩個紅色節點連續的情況),如果出現了該情況,我們就要對其進行分類討論并解決:
3.2.1 紅黑樹的調整思路
下面我們將出現異常情況的兩個節點的那個子節點叫做cur,cur的父節點叫做father(簡稱f),father的父節點叫做grandfather(簡稱g),father的兄弟節點叫做uncle(簡稱u)
例如:
接下來,我們分類討論:
3.2.1.1?cur為紅,f為紅,g為黑,u存在且為紅
下面畫出的情況表示的是抽象出的情況:A、B、C、D、E都是滿足構成紅黑樹的子樹
對于這種情況我們先將f和u節點變黑,再將g節點變紅即可:
調整完后,要記得再向上檢查g節點的父節點是否為紅色哦~(如果g節點為整棵紅黑樹的根,最后要將其顏色置為黑)
3.2.1.2?cur為紅,f為紅,g為黑,u不存在/u存在且為黑
3.2.1.2.1 g、f、cur構成一條直線
對于這種情況:若f為g的左孩子,cur為f的左孩子,則進行右單旋:
再將f變黑,g變紅:
相反, f為g的右孩子,cur為f的右孩子,則進行左單旋轉:
再將f變黑,g變紅:
3.2.1.2.2?g、f、cur構成一條折線
f為g的左孩子,cur為f的右孩子,則做左右雙旋,旋轉完后將cur節點顏色置黑、g節點顏色置紅:
相反, f為g的右孩子,cur為f的左孩子,則做右左雙旋,旋轉完后將cur節點顏色置黑、g節點顏色置紅:
對于旋轉操作還不熟悉的同學可以看到這里:【數據結構高階】AVL樹
3.2.2 調整部分的代碼實現
template<class Key, class Val>
class RBTree
{typedef RBTreeNode<Key, Val> Node;
public:bool Insert(const pair<Key, Val>& kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node* cur = _root, * parent = nullptr;while (cur)//找到合適的位置{if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else{cout << "插入的值重復" << endl;return false;}}cur = new Node(kv);//將插入的節點連接上二叉樹if (kv.first < parent->_kv.first){parent->_left = cur;}else{parent->_right = 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)//cur為紅,p為紅,g為黑,u存在且為紅{parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//繼續向上更新cur = grandfather;parent = cur->_parent;}elsecur為紅,p為紅,g為黑,u不存在/u存在且為黑{if (cur == parent->_left)//cur在p的左邊,p也在g的左邊,構成一條直線{//右單旋RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else//cur在p的右邊,p在g的左邊,構成一條折線{//左右雙旋RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;//調整完跳出}}else{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED)//cur為紅,p為紅,g為黑,u存在且為紅{parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//繼續向上更新cur = grandfather;parent = cur->_parent;}elsecur為紅,p為紅,g為黑,u不存在/u存在且為黑{if (cur == parent->_right)//cur在p的右邊,p也在g的右邊,構成一條直線{//左單旋RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else//cur在p的左邊,p在g的右邊,構成一條折線{//右左雙旋RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;//調整完跳出}}}_root->_col = BLACK;//確保即便進行過調整后根節點顏色為黑return true;
}private:void RotateL(Node* parent)//左單旋{Node* subR = parent->_right;Node* subRL = subR->_left;Node* pparent = parent->_parent;parent->_right = subRL;//更新parent的右節點if (subRL)//防止該節點為空{subRL->_parent = parent;//更新subRL的父節點}parent->_parent = subR;//更新parent的父節點subR->_left = parent;//subR的左子樹置為parentsubR->_parent = pparent;//更新subR的父節點if (pparent == nullptr)//旋轉的是整棵樹{_root = subR;//更新根節點}else//將旋轉后的子樹鏈接上整個二叉樹{if (pparent->_left == parent){pparent->_left = subR;}else{pparent->_right = subR;}}}void RotateR(Node* parent)//右單旋{Node* subL = parent->_left;Node* subLR = subL->_right;Node* pparent = parent->_parent;parent->_left = subLR;//更新parent的左節點if (subLR)//防止該節點為空{subLR->_parent = parent;//更新subLR的父節點}parent->_parent = subL;//更新parent的父節點subL->_right = parent;//subL的右子樹置為parentsubL->_parent = pparent;//更新subL的父節點if (pparent == nullptr)//旋轉的是整棵樹{_root = subL;//更新根節點}else//將旋轉后的子樹鏈接上整個二叉樹{if (pparent->_left == parent){pparent->_left = subL;}else{pparent->_right = subL;}}}private:Node* _root = nullptr;
};
3.3 紅黑樹的驗證
下面我們來寫段代碼來驗證一課樹是不是紅黑樹:
template<class Key, class Val>
class RBTree
{typedef RBTreeNode<Key, Val> Node;
public:bool IsBalance(){if (_root && _root->_col == RED){cout << "根節點顏色是紅色" << endl;return false;}int benchmark = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK)++benchmark;cur = cur->_left;}// 連續紅色節點return _Check(_root, 0, benchmark);}private:bool _Check(Node* root, int blackNum, int benchmark)//計算每條路徑上黑色節點的個數{if (root == nullptr){if (benchmark != blackNum){cout << "某條路徑黑色節點的數量不相等" << endl;return false;}return true;}if (root->_col == BLACK){++blackNum;}if (root->_col == RED&& root->_parent&& root->_parent->_col == RED){cout << "存在連續的紅色節點" << endl;return false;}return _Check(root->_left, blackNum, benchmark)&& _Check(root->_right, blackNum, benchmark);}private:Node* _root = nullptr;
};
3.4 測試代碼
void Test_RBTree()
{const size_t N = 50000;RBTree<int, int> t;for (size_t i = 0; i < N; ++i){size_t x = rand() + i;t.Insert(make_pair(x, x));}t.InOrder();cout << t.IsBalance();
}int main()
{Test_RBTree();return 0;
}
測試效果:
四、紅黑樹實現完整代碼
#include<iostream>using namespace std;enum Colour
{RED,BLACK
};template<class Key, class Val>
struct RBTreeNode
{RBTreeNode<Key, Val>* _left;RBTreeNode<Key, Val>* _right;RBTreeNode<Key, Val>* _parent;//多一個指針指向其父節點,方便我們的后續操作pair<Key, Val> _kv;Colour _col;//顏色標識RBTreeNode(const pair<Key, Val>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_col(RED)//默認構造時,優先將節點的顏色置為紅色{}
};template<class Key, class Val>
class RBTree
{typedef RBTreeNode<Key, Val> Node;
public:bool Insert(const pair<Key, Val>& kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node* cur = _root, * parent = nullptr;while (cur)//找到合適的位置{if (kv.first < cur->_kv.first){parent = cur;cur = cur->_left;}else if (kv.first > cur->_kv.first){parent = cur;cur = cur->_right;}else{cout << "插入的值重復" << endl;return false;}}cur = new Node(kv);//將插入的節點連接上二叉樹if (kv.first < parent->_kv.first){parent->_left = cur;}else{parent->_right = 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)//cur為紅,p為紅,g為黑,u存在且為紅{parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//繼續向上更新cur = grandfather;parent = cur->_parent;}elsecur為紅,p為紅,g為黑,u不存在/u存在且為黑{if (cur == parent->_left)//cur在p的左邊,p也在g的左邊,構成一條直線{//右單旋RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else//cur在p的右邊,p在g的左邊,構成一條折線{//左右雙旋RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;//調整完跳出}}else{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED)//cur為紅,p為紅,g為黑,u存在且為紅{parent->_col = BLACK;uncle->_col = BLACK;grandfather->_col = RED;//繼續向上更新cur = grandfather;parent = cur->_parent;}elsecur為紅,p為紅,g為黑,u不存在/u存在且為黑{if (cur == parent->_right)//cur在p的右邊,p也在g的右邊,構成一條直線{//左單旋RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else//cur在p的左邊,p在g的右邊,構成一條折線{//右左雙旋RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;//調整完跳出}}}_root->_col = BLACK;//確保即便進行過調整后根節點顏色為黑return true;
}void InOrder()//中序遍歷{_InOrder(_root);cout << endl;}bool IsBalance(){if (_root && _root->_col == RED){cout << "根節點顏色是紅色" << endl;return false;}int benchmark = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK)++benchmark;cur = cur->_left;}// 連續紅色節點return _Check(_root, 0, benchmark);}private:void RotateL(Node* parent)//左單旋{Node* subR = parent->_right;Node* subRL = subR->_left;Node* pparent = parent->_parent;parent->_right = subRL;//更新parent的右節點if (subRL)//防止該節點為空{subRL->_parent = parent;//更新subRL的父節點}parent->_parent = subR;//更新parent的父節點subR->_left = parent;//subR的左子樹置為parentsubR->_parent = pparent;//更新subR的父節點if (pparent == nullptr)//旋轉的是整棵樹{_root = subR;//更新根節點}else//將旋轉后的子樹鏈接上整個二叉樹{if (pparent->_left == parent){pparent->_left = subR;}else{pparent->_right = subR;}}}void RotateR(Node* parent)//右單旋{Node* subL = parent->_left;Node* subLR = subL->_right;Node* pparent = parent->_parent;parent->_left = subLR;//更新parent的左節點if (subLR)//防止該節點為空{subLR->_parent = parent;//更新subLR的父節點}parent->_parent = subL;//更新parent的父節點subL->_right = parent;//subL的右子樹置為parentsubL->_parent = pparent;//更新subL的父節點if (pparent == nullptr)//旋轉的是整棵樹{_root = subL;//更新根節點}else//將旋轉后的子樹鏈接上整個二叉樹{if (pparent->_left == parent){pparent->_left = subL;}else{pparent->_right = subL;}}}void _InOrder(Node* root){if (root == NULL)//如果是空樹就直接結束{return;}_InOrder(root->_left);//先遞歸遍歷其左子樹cout << root->_kv.first << " ";//再遍歷其根節點_InOrder(root->_right);//最后遞歸遍歷其右子樹}bool _Check(Node* root, int blackNum, int benchmark){if (root == nullptr){if (benchmark != blackNum){cout << "某條路徑黑色節點的數量不相等" << endl;return false;}return true;}if (root->_col == BLACK){++blackNum;}if (root->_col == RED&& root->_parent&& root->_parent->_col == RED){cout << "存在連續的紅色節點" << endl;return false;}return _Check(root->_left, blackNum, benchmark)&& _Check(root->_right, blackNum, benchmark);}private:Node* _root = nullptr;
};