紅黑樹的旋轉原理和模擬實現
我們瞭解到AVL樹雖然效率很高,但是它是通過多次的旋轉纔到達一個絕對的平衡,旋轉的消耗其實也很大。因此開始引入近似平衡的一棵樹----紅黑樹(RBTree)。紅黑樹每一個節點不是紅色的就是黑色的,它保證了最長路徑不超過最短路徑的二倍。
其實一般來說使用紅黑樹會比AVL樹更多,因爲雖然AVL樹是更加平衡的,但是它的平衡是通過更多次的旋轉得到的,旋轉的時候消耗還是很大的。而紅黑樹是近似平衡的,它的旋轉比AVL樹要少。但是有人可能會有疑問,那紅黑樹的搜索效率沒有AVL樹高啊?其實他倆是差不多的,因爲AVL樹是O(logN),而紅黑樹是O(2logN),假如是十億個數據的話,AVL樹用30次查找,那麼紅黑樹也就是60次查找,對於現在的計算機CPU計算速度來說是沒有多大影響的,所以用紅黑樹用的會多一些。
紅黑樹的特點:
- 根節點一定是黑色的
- 一個紅色節點的孩子只能是黑色的
- 每一條路徑上的黑色節點的個數相同
- 每個節點不是紅色的就是黑色的
- 每個葉子結點都是黑色的(此處的葉子結點指的是空結點)
根據這五個特點我們就可以推斷出最長路徑不超過最短路徑的二倍。因爲如果一顆樹是紅黑樹,那麼它的最短路徑就是全黑節點的那一條路徑,最長路徑就是一個黑色一個紅色這樣紅黑相間的路徑。
紅黑樹節點的定義
enum Color//枚舉顏色
{
RED,
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;//顏色
};
紅黑樹的插入
紅黑樹的插入需要這麼幾步:
- 如果是空樹,那麼直接讓root等於這個新開節點,然後把這個節點的顏色變爲黑色
- 如果不是空樹,那麼就先去找要插入的位置,如果要插入的值比該節點小就去左邊,比該節點大就去右邊
- 找到位置之後將該節點連接到樹中,然後將這個新插入的節點設置爲紅色(爲什麼是設置爲紅色呢? 因爲如果插入一個黑色節點的話,那麼就會破壞紅黑樹的每條路徑上的黑色節點數目相同的這條性質,因爲在你插入的這條路徑上就會多出來一個黑色節點,那麼就會影響到其他的路徑,影響範圍很大。但是如果插入的是紅色節點,那麼如果它的父親是黑色就非常完美,不用對樹進行修改,只有當它的父親是紅色才需要進行修改,因爲兩個紅色的節點不能連接再一起。所以說如果插入黑色節點,必然一定會破壞所以路徑,而插入紅色的節點是有可能會破壞這條路徑。)
- 如果插入這個紅色節點之後父親也是紅色,則對這顆樹進行調整
如果要進行調整的的話分爲以下幾種情況,我們約定一下起名規則如下:
cur:新插入的節點
parent:新插入節點的父親節點
uncle:新插入節點的叔叔節點
grandfather:新插入節點的祖父節點
情況一:插入節點爲紅色,父親爲紅色,叔叔存在且爲紅色
如下圖,新插入節點cur是紅色,父親也是紅色,叔叔也是紅色,那麼祖父必然是一個黑色節點,那麼把父親和叔叔變成黑色,把祖父變成紅色,然後這顆樹上每條路徑的黑色節點就都是一樣的了。
但是這個步驟完成之後還需要繼續往上判斷,因爲如果把祖父變成了紅色,祖父還不是根節點的話,萬一祖父的父親也是紅色節點就需要再次調整了,如下圖。
最後根據需要,如果grandfather爲根節點的話直接把根變成黑色(因爲根節點一定是黑色的)。
情況二:插入節點(左邊)爲紅色,父親爲紅,叔叔不存在/叔叔爲黑
如果插入節點紅色,父親也是紅色,那麼祖父一定是黑色,此時如果沒有叔叔節點的話,那就要進行旋轉,如圖情況,對這顆樹進行右單旋,然後在進行變色,把祖父變成紅色,把父親變成黑色。
如果插入節點紅色,父親也是紅色,那麼祖父一定是黑色,此時如果叔叔節點爲黑,那麼應該怎麼辦呢?如果遇到叔叔節點爲黑時,那一定是通過情況一演變上來的。如下圖,當向上更新時,發現叔叔節點爲黑,那麼我們以parent爲中心進行右單旋,把parent的左樹變成grandfather的左樹,然後再把grandfather變成parent的右樹。
情況三:插入節點(右邊)爲紅色,父親爲紅,叔叔不存在/叔叔爲黑
插入節點爲紅,父親如果爲紅,祖父必然爲黑,如果此時叔叔節點不存在,那麼需要先以parent爲中心進行左單旋,然後再交換cur和parent的指向(遵循我們制定的約定),然後再以grandfather爲中心右單旋。
如果插入節點爲紅,父親爲紅,祖父必然爲紅,此時叔叔節點爲黑,那麼必然是情況一演變而來的,首先以parent爲中心進行左單旋,然後再交換parent和cur的指向,再以grandfather爲中心進行右單旋,最後再講parent變爲黑色,grandfather變爲紅色。如下圖:
注:以上博主畫的都是父親在左邊叔叔在右邊的情況,反之叔叔在左邊父親在右邊也是一樣的啦
然後博主就不畫了吧 |ू・ω・` ),畫圖太費眼睛啦!博主上面的圖畫了好久好久!
博主可能有強迫症!然後哇的一聲就哭了 ヘ(;´Д`ヘ)。
紅黑樹的左單旋右單旋和AVL樹的左單旋右單旋一毛一樣,還有遍歷也是一毛一樣!!!
好的!那麼我們一起來看代碼 ~ 嘻嘻嘻 ~
RBTree.h
#pragma once
#include <iostream>
using namespace std;
enum Color
{
RED,
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:
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->_left;
}
else if (cur->_kv.first < kv.first)
{
parent = cur;
cur = cur->_right;
}
else
return false;
}
//新開節點,連接起來
cur = new Node(kv);
cur->_col = RED;
if (parent->_kv.first > kv.first)
{
parent->_left = cur;
cur->_parent = parent;
}
else
{
parent->_right = cur;
cur->_parent = parent;
}
//進行調整樹
while(parent && parent->_col == RED)//parent存在且爲紅色
{
Node* grandfather = parent->_parent;
if (parent == grandfather->_left)//父親在左邊,叔叔在右邊
{
Node* uncle = grandfather->_right;
if (uncle && uncle->_col == RED)//叔叔存在且爲紅
{
//變色
parent->_col = BLACK;
uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else//叔叔不存在或者爲黑
{
//先處理雙旋
if (cur == parent->_right)
{
RotateL(parent);
swap(cur, parent);
}
//單旋+變色
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
break;
}
}
else//grandfather->right == parent 父親在右邊,叔叔在左邊
{
Node* uncle = grandfather->_left;
if (uncle && uncle->_col == RED)//叔叔存在且爲紅
{
parent->_col = BLACK;
uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
else//叔叔不存在或者爲黑
{
//先處理雙旋
if (cur == parent->_left)
{
RotateR(parent);
swap(cur, parent);
}
//單旋+變色
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
break;
}
}
}
_root->_col = BLACK;
return true;
}
void RotateL(Node* parent)
{
Node* subR = parent->_right;
Node* subRL = subR->_left;
parent->_right = subRL;
if (subRL)
subRL->_parent = parent;
subR->_left = parent;
Node* ppnode = parent->_parent;
parent->_parent = subR;
if (_root == parent)
{
_root = subR;
}
else
{
if (ppnode->_left == parent)
ppnode->_left = subR;
else
ppnode->_right = subR;
}
subR->_parent = ppnode;
}
void RotateR(Node* parent)
{
Node* subL = parent->_left;
Node* subLR = subL->_right;
parent->_left = subLR;
if (subLR)
subLR->_parent = parent;
subL->_right = parent;
Node* ppnode = parent->_parent;
parent->_parent = subL;
if (parent == _root)
{
_root = subL;
}
else
{
if (ppnode->_left == parent)
ppnode->_left = subL;
else
ppnode->_right = subL;
}
subL->_parent = ppnode;
}
void _Inorder(Node* root)
{
if (root == nullptr)
return;
_Inorder(root->_left);
cout << root->_kv.first << " ";
_Inorder(root->_right);
}
void Inorder()
{
_Inorder(_root);
cout << endl;
}
private:
Node* _root = nullptr;
};
void TestRBTree()
{
RBTree<int, int> t;
int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };
for (auto e : a)
{
t.Insert(std::make_pair(e, e));
}
t.Inorder();
}
Test.cpp
#include "RBTree.h"
int main()
{
TestRBTree();
system("pause");
return 0;
}