1.紅黑樹的概念
紅黑樹,是一種二叉搜索樹,但在每個結點上增加一個存儲位表示結點的顏色,可以是Red或Black。 通過對任何一條從根到葉子的路徑上各個結點着色方式的限制,紅黑樹確保沒有一條路徑會比其他路徑長出兩倍,因而是接近平衡的。
2.紅黑樹的性質
- 每個節點不是紅色就是黑色
- 根結點是黑色的
- 如果一個結點是紅色的,則它的兩個孩子是黑色的
- 對於每個節點,從該節點到其後代葉結點的簡單路徑上,均包含相同數目的黑結點。
- 每個葉子結點的都是黑色的(此處葉子結點指的是空節點)
思考:爲什麼滿足上面的性質,紅黑樹就能保證:其最長路徑中節點個數不會超過最短路徑節點個數的兩倍?
答案:最短的路徑上節點的顏色全部都爲黑色;最長的路徑則爲黑紅交叉的路徑,其上有與最短路徑的黑節點數目相同的黑節點數和紅節點數目。所以我們按照紅黑樹性質所建立的紅黑樹的最長路徑必然不會超過最短路徑的兩倍!
3.紅黑樹結點的定義
//節點的顏色
enum Color{RED,BLACK};
//結點的定義
template<class V>
struct RBTreeNode
{
RBTreeNode(const V& val = V(), Color color = RED)
:_pLeft(nullptr)
, _pRight(nullptr)
, _pParent(nullptr)
, _val(val)
,_color(color)
{}
RBTreeNode<V>* _pLeft; //節點的左孩子
RBTreeNode<V>* _pRight; //節點的右孩子
RBTreeNode<V>* _pParent; //節點的雙親
V _val; //結點的值域
Color _color; //節點的顏色
};
爲什麼插入的新節點默認是紅色的?
原因是:插入黑節點必然會影響所有路徑都含有相同數目的黑色節點這一原則,較難維護!
4.紅黑樹的結構
我們常用的STL容器,如map/multimap、set/multimap等容器的底層都是通過紅黑樹實現的,爲了後續實現這些關聯式容器,紅黑樹的實現中增加一個頭結點,因爲跟節點必須爲黑色,爲了與根節點進 行區分,將頭結點給成紅色,並且讓頭結點的 pParent 域指向紅黑樹的根節點,pLeft域指向紅黑樹中小的節點,_pRight域指向紅黑樹中大的節點,如下:
template<class V>
class RBTree
{
typedef RBTreeNode<V> Node;
public:
typedef RBTreeIterator<V> iterator;
public:
RBTree()
{
_pHead = new Node;
_pHead->_pLeft = _pHead;
_pHead->_pRight = _pHead;
}
private:
Node*& GetRoot()
{
return _pHead->_pParent;
}
private:
Node* _pHead;
};
5.紅黑樹的插入(重點)
紅黑樹是在二叉搜索樹的基礎上加上其平衡限制條件,因此紅黑樹的插入可分爲兩步:
1.按照二叉搜索樹規則插入新的結點
2. 檢測新節點插入後,紅黑樹的性質是否造到破壞
因爲新節點的默認顏色是紅色,因此:如果其雙親節點的顏色是黑色,沒有違反紅黑樹任何性質,則不 需要調整;但當新插入節點的雙親節點顏色爲紅色時,就違反了性質三不能有連在一起的紅色節點,此 時需要對紅黑樹分情況來討論:
一、父節點是祖父節點的左孩子
1.uncle結點存在且爲紅色
①當前節點cur是parent的左孩子
②當前節點cur是parent的右孩子
上面兩種情況,均是將cur 的父親節點和叔叔節點的顏色變爲黑色,此時這兩條路徑上均多了一個黑色結點,因此將祖父結點的顏色變爲紅色。此時將cur指向祖父結點的位置,p指向祖父父親節點的位置。繼續向上更新。
2.uncle的顏色是黑色 或者 uncle爲NULL
①cur是p的左孩子,右單旋
將p的結點變爲黑色,g結點變爲紅色,然後對g結點進行右旋。
②cur是parent的右孩子,先左後右雙旋
先將cur結點進行左旋,轉換爲第二種情況,此時與上面的其情況相同,將cur的顏色變爲黑,g的顏色變爲紅,然後對g進行右旋。
二、父節點是祖父節點的右孩子
1.uncle的顏色是紅色
①cur是parent的右孩子
②cur是parent的左孩子
2.uncle的顏色是黑色 或者 uncle爲NULL
①cur是parent的右孩子
②cur是parent的左孩子
5.紅黑樹的驗證
紅黑樹的檢測分爲兩步:
1. 檢測其是否滿足二叉搜索樹(中序遍歷是否爲有序序列)
2. 檢測其是否滿足紅黑樹的性質
看下面的驗證代碼:
//是否有效的紅黑樹,通過性質進行檢查
bool IsValidRBTree()
{
Node* pRoot = GetRoot();
if (nullptr == pRoot)
return true;
//性質二:
if (BLACK != pRoot->_color)
{
cout << "違反性質二:根節點的顏色不是黑色" << endl;
return false;
}
//性質三:不能有相連的紅色結點
//獲取一條路徑中黑色結點的個數
Node* pCur = pRoot;
size_t blackCount = 0;
while (pCur)
{
if (BLACK == pCur->_color)
++blackCount;
pCur = pCur->_pLeft;
}
// 性質四:每條路徑裏面的黑色結點數目相同
size_t pathCount = 0;
return _IsValidRBTree(pRoot, blackCount, pathCount);
}
bool _IsValidRBTree(Node* pRoot, size_t blackCount,size_t pathCount)
{
if (nullptr == pRoot)
return true;
if (pRoot->_color == BLACK)
pathCount++;
Node* pParent = pRoot->_pParent;
if (pParent != _pHead && RED == pParent->_color && pRoot->_color == RED)
{
cout << "違反了性質三,有連在一起的紅色結點" << endl;
return false;
}
if (nullptr == pRoot->_pLeft && pRoot->_pRight == nullptr)
{
if (pathCount != blackCount)
{
cout << "違反性質四:路徑中黑色結點得個數不相同" << endl;
return false;
}
}
return _IsValidRBTree(pRoot->_pLeft, blackCount, pathCount)&&
_IsValidRBTree(pRoot->_pRight, blackCount, pathCount);
}
6.紅黑樹的刪除
紅黑樹的刪除相對比較複雜,有興趣的大家可以下去學習學習,這裏我們就不做討論了。
大家可以參考下面別人的連接進行學習:
7.紅黑樹的插入、刪除時間複雜度
因爲每一個紅黑樹也是一個特化的二叉查找樹,
因此紅黑樹上的只讀操作與普通二叉查找樹上的只讀操作相同。
然而,在紅黑樹上進行插入操作和刪除操作會導致不再符合紅黑樹的性質。
恢復紅黑樹的屬性需要少量(O(log n))的顏色變更(實際是非常快速的)和
不超過三次樹旋轉(對於插入操作是兩次)。
雖然插入和刪除很複雜,但操作時間仍可以保持爲 O(log n) 次。
與AVL樹的比較:
紅黑樹和AVL樹都是高效的平衡二叉樹,增刪改查的時間複雜度都是O( logN),紅黑樹不追求絕對平衡,其 只需保證最長路徑不超過最短路徑的2倍,相對而言,降低了插入和旋轉的次數,所以在經常進行增刪的結構 中性能比AVL樹更優,而且紅黑樹實現比較簡單,所以實際運用中紅黑樹更多。
下面給大家分享一下我寫的一份完整得實現代碼,希望幫助大家理解:
#pragma once
#include<iostream>
using namespace std;
enum Color{RED,BLACK};
template<class V>
struct RBTreeNode
{
RBTreeNode(const V& val = V(), Color color = RED)
:_pLeft(nullptr)
, _pRight(nullptr)
, _pParent(nullptr)
, _val(val)
,_color(color)
{}
RBTreeNode<V>* _pLeft;
RBTreeNode<V>* _pRight;
RBTreeNode<V>* _pParent;
V _val;
Color _color;
};
template<class V>
class RBTreeIterator
{
typedef RBTreeNode<V> Node;
typedef RBTreeIterator<V> Self;
public:
RBTreeIterator(Node* pNode = nullptr)
:_pNode(pNode)
{}
//讓迭代器具有指針的功能
V& operator* ()
{
return _pNode->_val;
}
V* operator->()
{
return &(operator*());
}
//移動
Self operator++()
{
InCreament();
return *this;
}
Self operator++(int)
{
Self temp(*this);
InCreament();
return temp;
}
//移動
Self operator--()
{
DeCreament();
return *this;
}
Self operator--(int)
{
Self temp(*this);
DeCreament();
return temp;
}
//比較
bool operator!=(const Self& s) const
{
return _pNode != s._pNode;
}
bool operator== (const Self& s) const
{
return _pNode == s._pNode;
}
void InCreament()
{
if (_pNode->_pRight)
{
_pNode = _pNode->_pRight;
while (_pNode->_pLeft)
_pNode = _pNode->_pLeft;
}
else
{
Node* pParent = _pNode->_pParent;
while (_pNode == pParent->_pRight)
{
_pNode = pParent;
pParent = _pNode->_pParent;
}
//_pNdoe在根節點的位置,並且根節點沒有右孩子
if(_pNode->_pRight != pParent )
_pNode = _pNode->_pParent;
}
}
void DeCreament()
{
//_pNode在end,end--取到最大值
if (_pNode->_pParent->_pParent == _pNode && _pNode->_color == RED)
{
_pNode = _pNode->_pRight;
}
if (_pNode->_pLeft)
{
_pNode = _pNode->_pLeft;
while (_pNode->_pRight)
{
_pNode = _pNode->_pRight;
}
}
else
{
Node* pParent = _pNode->_pParent;
while (_pNode == pParent->_pLeft)
{
_pNode = pParent;
pParent = _pNode->_pParent;
}
_pNode = pParent;
}
}
private:
Node* _pNode;
};
template<class V>
class RBTree
{
typedef RBTreeNode<V> Node;
public:
typedef RBTreeIterator<V> iterator;
public:
RBTree()
{
_pHead = new Node;
_pHead->_pLeft = _pHead;
_pHead->_pRight = _pHead;
}
iterator begin()
{
return iterator(_pHead->_pLeft);
}
iterator end()
{
return iterator(_pHead);
}
bool Insert(const V& val)
{
Node*& pRoot = _pHead->_pParent;
if (nullptr == pRoot)
{
pRoot = new Node(val,BLACK);
pRoot->_pParent = _pHead;
}
else
{
//按照二叉搜索樹得性質插入結點
//找待插入節點再二叉搜索樹中的位置
Node* pCur = pRoot;
Node* pParent = _pHead;
while (pCur)
{
pParent = pCur;
if (val < pCur->_val)
pCur = pCur->_pLeft;
else if (val > pCur->_val)
pCur = pCur->_pRight;
else
return false;
}
//插入新節點
pCur = new Node(val);
if (val < pParent->_val)
pParent->_pLeft = pCur;
else
pParent->_pRight = pCur;
pCur->_pParent = pParent;
//pParent的顏色是紅色,一定違反紅黑樹的性質
while (pParent != _pHead && RED == pParent->_color)
{
Node* grandFather = pParent->_pParent;
if (pParent == grandFather->_pLeft)
{
Node* uncle = grandFather->_pRight;
//情況一:叔叔節點存在且爲紅色
if (uncle && RED == uncle->_color)
{
pParent->_color = BLACK;
uncle->_color = BLACK;
grandFather->_color = RED;
pCur = grandFather;
pParent = pCur->_pParent;
}
else
{
//情況二:叔叔節點不存在或者存在且爲黑色
//情況三:pCur是pParent的右孩子
if (pCur == pParent->_pRight)
{
RotateL(pParent);
swap(pParent, pCur);
}
//情況二:
grandFather->_color = RED;
pParent->_color = BLACK;
RotateR(grandFather);
}
}
else
{
Node* uncle = grandFather->_pLeft;
if (uncle && RED == uncle->_color)
{
pParent->_color = BLACK;
uncle->_color = BLACK;
grandFather->_color = RED;
pCur = grandFather;
pParent = pCur->_pParent;
}
else
{
if (pCur == pParent->_pLeft)
{
RotateR(pCur);
swap(pParent, pCur);
}
pParent->_color = BLACK;
grandFather->_color = RED;
RotateL(grandFather);
}
}
}
}
_pHead->_pLeft = LeftMost();
_pHead->_pRight = RightMost();
pRoot->_color = BLACK;
return true;
}
void Inorder()
{
return _Inorder(GetRoot());
}
//是否有效的紅黑樹,通過性質進行檢查
bool IsValidRBTree()
{
Node* pRoot = GetRoot();
if (nullptr == pRoot)
return true;
//性質二:
if (BLACK != pRoot->_color)
{
cout << "違反性質二:根節點的顏色不是黑色" << endl;
return false;
}
//性質三:不能有相連的紅色結點
//獲取一條路徑中黑色結點的個數
Node* pCur = pRoot;
size_t blackCount = 0;
while (pCur)
{
if (BLACK == pCur->_color)
++blackCount;
pCur = pCur->_pLeft;
}
// 性質四:每條路徑裏面的黑色結點數目相同
size_t pathCount = 0;
return _IsValidRBTree(pRoot, blackCount, pathCount);
}
private:
bool _IsValidRBTree(Node* pRoot, size_t blackCount,size_t pathCount)
{
if (nullptr == pRoot)
return true;
if (pRoot->_color == BLACK)
pathCount++;
Node* pParent = pRoot->_pParent;
if (pParent != _pHead && RED == pParent->_color && pRoot->_color == RED)
{
cout << "違反了性質三,有連在一起的紅色結點" << endl;
return false;
}
if (nullptr == pRoot->_pLeft && pRoot->_pRight == nullptr)
{
if (pathCount != blackCount)
{
cout << "違反性質四:路徑中黑色結點得個數不相同" << endl;
return false;
}
}
return _IsValidRBTree(pRoot->_pLeft, blackCount, pathCount)&&
_IsValidRBTree(pRoot->_pRight, blackCount, pathCount);
}
Node* LeftMost()
{
Node* pCur = GetRoot();
if (nullptr == pCur)
return _pHead;
while (pCur->_pLeft)
pCur = pCur->_pLeft;
return pCur;
}
Node* RightMost()
{
Node* pCur = GetRoot();
if (nullptr == pCur)
return _pHead;
while (pCur->_pRight)
pCur = pCur->_pRight;
return pCur;
}
void RotateL(Node* pParent)
{
Node* pSubR = pParent->_pRight;
Node* pSubRL = pSubR->_pLeft;
pParent->_pRight = pSubRL;
if(pSubRL)
pSubRL->_pParent = pParent;
pSubR->_pLeft = pParent;
Node* pPParent = pParent->_pParent;
pParent->_pParent = pSubR;
pSubR->_pParent = pPParent;
if (pPParent == _pHead)
{
_pHead->_pParent = pSubR;
}
else
{
if (pParent == pPParent->_pLeft)
pPParent->_pLeft = pSubR;
else
pPParent->_pRight = pSubR;
}
}
void RotateR(Node* pParent)
{
Node* pSubL = pParent->_pLeft;
Node* pSubLR = pSubL->_pRight;
pParent->_pLeft = pSubLR;
if (pSubLR)
pSubLR->_pParent = pParent;
pSubL->_pRight = pParent;
Node* pPParent = pParent->_pParent;
pParent->_pParent = pSubL;
pSubL->_pParent = pPParent;
if (_pHead == pPParent)
{
_pHead->_pParent = pSubL;
}
else
{
if (pParent == pPParent->_pLeft)
pPParent->_pLeft = pSubL;
else
pPParent->_pRight = pSubL;
}
}
void _Inorder(Node* pRoot)
{
if (pRoot)
{
_Inorder(pRoot->_pLeft);
cout << pRoot->_val << " ";
_Inorder(pRoot->_pRight);
}
}
private:
Node*& GetRoot()
{
return _pHead->_pParent;
}
private:
Node* _pHead;
};
關於AVL樹以及二叉平衡樹的細節大家可以看我之前的博客:
AVL樹---- 詳解及其實現
二叉搜索樹的實現