【數據結構】紅黑樹(如何實現及怎樣判斷)

      紅黑樹是一顆二叉搜索樹,它在每個節點上增加了一個存儲位來表示節點的顏色,可以是red或black。通過對任何一條從根節點到葉子節點的簡單路徑上的顏色來約束,紅黑樹保證了最長路徑不超過最短路經的兩倍,因此近似於平衡

紅黑樹的規則:

1、每個節點不是紅色就是黑色的。

2、根結點是黑色的。

3、如果一個節點是紅色的,則它的兩個子結點是黑色的。即每條路徑上不能存在兩個連續的紅節點。

4、對每個節點,從該節點到其他節點的簡單路徑上,均包含相同數目的黑色節點

紅黑樹節點RBTreeNode的實現,利用三叉鏈(left、right、parent)、key、value及顏色col。

enum colour
{
	RED,
	BLACK,
};
template<class K, class V>
struct RBTreeNode
{
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	K _key;
	V _value;
	colour _col;
	RBTreeNode(const K& key = K(), const V& value = V())
		:_left(NULL)
		, _right(NULL)
		, _parent(NULL)
		, _key(key)
		, _value(value)
		, _col(RED)//初始化插入節點的顏色爲紅色,不影響黑節點個數
	{}
};

一、紅黑樹的插入

      紅黑樹的插入類似於二叉搜索樹,但是每次插入後都到要注意是否滿足紅黑樹的規則,特別是規則3和規則4。如果不滿足就需要調整樹的結構,下面對插入節點時分成以下幾種情況:

注:cur(插入節點)parent(cur的父親節點)grandfather(parent的父親節點)uncle(父親的兄弟節點)

1、根節點root爲空,直接插入新節點並給root,設置根節點的顏色爲BLACK。

2、根節點root不爲空,找到插入節點的位置並插入節點cur。cur節點是紅色,若parent是紅節點,則需要進行調整。

      存在以下三種情況:

      情況一:cur爲紅,parent爲紅,grandfather爲黑,uncle存在且爲紅。

     調整方案,如下如所示:

   

      情況二:cur爲紅,parent爲紅,grandfather爲黑,uncle不存在或者uncle爲黑。

                     左單旋:parent爲grantfather的右孩子,cur爲parent的右孩子。

                     右單旋: parent爲grantfather的左孩子,cur爲parent的左孩子。

      調整方案,下面以右單旋進行分析,如下圖所示:

     

       左單旋轉類似右單旋轉的實現過程。

      情況三:cur爲紅,parent爲紅,grantfather爲黑,uncle不存在或者uncle爲黑。 

                    左右單旋:parent爲grantfather的右孩子,cur爲parent的左孩子。

                    右左單旋:parent爲grantfather的左孩子,cur爲parent的右孩子。

      調整方案,下面以做右單旋進行分析,如下圖所示:

    

      右左旋轉的實現類似左單旋轉的實現過程。

具體實現如下:

RBTree()
 :_root(NULL)
{}
bool Insert(const K& key, const V& value)
{
 //1、_root爲空時,插入節點是根節點
 if (_root == NULL)
 {
  _root = new Node(key, value);
  _root->_col = BLACK;
  return true;
 }
 Node* parent = NULL;
 Node* cur = _root;
 while (cur)//找到插入節點的位置
 {
  if (cur->_key > key)
  {
   parent = cur;
   cur = cur->_left;
  }
  else if (cur->_key < key)
  {
   parent = cur;
   cur = cur->_right;
  }
  else
   return false;
 }
 cur = new Node(key, value);//插入節點
 if (parent->_key > cur->_key)
 {
  parent->_left = cur;
  cur->_parent = parent;
 }
 if (parent->_key < cur->_key)
 {
  parent->_right = cur;
  cur->_parent = parent;
 }
 //2、出現兩個連續紅節點,進行調整
 while (cur != _root && parent->_col == RED)//此條件說明存在parent節點,parent存在父親節點
 {
  Node* grandfather = parent->_parent;
  if (parent == grandfather->_left)
  {
   Node* uncle = grandfather->_right;
   if (uncle && uncle->_col == RED)//情況一:uncle爲RED
   {
    grandfather->_col = RED;
    parent->_col = BLACK;
    uncle->_col = BLACK;
   }
   else //情況二、三:uncle爲BLACK或不存在(右單旋或左右單旋)
   {//先考慮左右單旋,先進行左單旋,轉化爲情況二,再進行右單旋
    if (cur == parent->_right)
    {
     _RotateL(parent);//左旋不需要變顏色
    }
    _RotateR(grandfather);
   }
  }
  else
  {
   Node* grandfather = parent->_parent;
   if (parent == grandfather->_right)
   {
    Node* uncle = grandfather->_left;
    if (uncle && uncle->_col == RED)//情況一:uncle爲RED
    {
     grandfather->_col = RED;
     parent->_col = BLACK;
     uncle->_col = BLACK;
    }
    else //情況二、三:uncle爲BLACK或不存在(左單旋或右左單旋)
    {
     if (cur == parent->_left)
     {
      _RotateR(parent);//右旋不需要變顏色
     }
     _RotateL(grandfather);
    }
   }
  }
  cur = grandfather;
  parent = cur->_parent;
  _root->_col = BLACK;
 }
}
void _RotateL(Node* parent)
{
 Node* SubR = parent->_right;
 Node* SubRL = SubR->_left;
 parent->_right = SubRL;
 if (SubRL)
  SubRL->_parent = parent;
 SubR->_parent = parent->_parent;
 SubR->_left = parent;
 parent->_parent = SubR;
 //變色
 SubR->_col = BLACK;//情況二中:parent變黑,grandfather變紅
 parent->_col = RED;
 parent = SubR;
 if (parent->_parent == NULL)
  _root = parent;
 else
 {
  Node* ppNode = parent->_parent;
  if (ppNode->_key > parent->_key)
   ppNode->_left = parent;
  else
   ppNode->_right = parent;
 }
}
void _RotateR(Node* parent)
{
 Node* SubL = parent->_left;
 Node* SubLR = SubL->_right;
 parent->_left = SubLR;
 if (SubLR)
  SubLR->_parent = parent;
 SubL->_parent = parent->_parent;
 SubL->_right = parent;
 parent->_parent = SubL;
 //變色
 SubL->_col = BLACK;//情況二中:parent變黑,grandfather變紅
 parent->_col = RED;
 parent = SubL;
 if (parent->_parent == NULL)
  _root = parent;
 else
 {
  Node* ppNode = parent->_parent;
  if (ppNode->_key > parent->_key)
   ppNode->_left = parent;
  else
   ppNode->_right = parent;
 }
}

二、紅黑樹的判斷

1、根結點是否滿足紅黑樹規則,是否爲黑色。

2、每條路徑的黑色節點相等。統計出一條路徑的黑色節點的個數,然後與其他路徑黑色節點個數進行比較。

3、不存在連續的紅色節點,判斷紅色節點的父親節點是否爲紅色。

具體實現如下:

bool Check()
{
	if (_root->_col == RED)
		return false;
	int count = 0;//統計出一條路徑的黑色節點的個數
	int num = 0;//需要與count比較的其他路徑黑色節點個數
	Node* cur = _root;
	while (cur)
	{
		if (cur->_col == BLACK)
			count++;
		cur = cur->_left;
	}
	return _Check(_root, count, num);
}
bool _Check(Node* root, int BlackNum, int CurBlackNum)
{
 if (root == NULL)
  return true;
 if (root->_col == RED && root->_parent->_col == RED)//存在兩個連續的紅節點
  return false;
 if (root->_col == BLACK)//黑節點就CurBlackNum++
  CurBlackNum++;
 if (root->_left == NULL && root->_right == NULL)
 {
  if (CurBlackNum == BlackNum)
   return true;
  else//黑色節點不相等返回false
   return false;
 }
 return _Check(root->_left, BlackNum, CurBlackNum) 
  && _Check(root->_right, BlackNum, CurBlackNum);//進行左右遞歸
}

紅黑樹的效率:

1、最壞情況下,紅黑樹高度不超過2lgN

最壞的情況就是,紅黑樹中除了最左側路徑全部是由3-node節點組成,即紅黑相間的路徑長度是全黑路徑長度的2倍

2、紅黑樹的平均高度大約爲lgN

紅黑樹的運用(高效的二叉搜索樹)

紅黑樹這種數據結構應用十分廣泛,在多種編程語言中被用作符號表的實現,如:

紅黑樹和AVL樹的比較

1、紅黑樹和AVL樹都是高效的平衡二叉樹,增刪查改的時間複雜度都是O(lg(N))

2、紅黑樹的不追求完全平衡,保證最長路徑不超過最短路徑的2倍,相對而言,降低了旋轉的要求,所以性能優於AVL樹,所以實際運用中紅黑樹更多。紅黑樹是一種特殊的二叉查找樹,他的查找方法也和二叉查找樹一樣,不需要做太多更改,但是由於紅黑樹比一般的二叉查找樹具有更好的平衡,所以查找起來更快。

以上爲個人學習的一些總結,如有紕漏,請多多指教。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章