【數據結構】—— 淺析紅黑樹及模擬實現

紅黑樹

紅黑樹的概念

  • 紅黑樹,是一種二叉搜索樹,但在每個結點上增加一個存儲位表示結點的顏色,可以是Red或Black。
  • 通過對任何一條從根到葉子的路徑上各個結點着色方式的限制,紅黑樹確保沒有一條路徑會比其他路徑長出倆倍,因而是接近平衡的。

紅黑樹

紅黑樹的性質

  1. 每個結點不是紅色就是黑色
  2. 根節點是黑色的
  3. 如果一個節點是紅色的,則它的兩個孩子結點是黑色的
  4. 對於每個結點,從該結點到其所有後代葉結點的簡單路徑上,均 包含相同數目的黑色結點
  5. 每個葉子結點都是黑色的(此處的葉子結點指的是空結點)

模擬實現紅黑樹

紅黑樹的定義

  template<class K,class V>
    struct RBSTreeNode
    {
    	RBSTreeNode(const pair<K, V>& kv)
    	:_left(nullptr)
    	, _right(nullptr)
    	, _parent(nullptr)
    	, _kv(kv)
    	, _col(RED)
    	{}
    	RBSTreeNode<K, V>* _left;
    	RBSTreeNode<K, V>* _right;
    	RBSTreeNode<K, V>* _parent;
    
    	pair<K, V> _kv;
    	color _col;
    };

紅黑樹的結點操作

  • 紅黑樹也屬於二叉搜索樹
  • 因此紅黑樹的插入操作和二叉搜索樹一樣,若樹爲空,則直接插入,若樹不爲空,則先找到插入位置,再插入節點**,插入節點的顏色默認爲紅色,破壞性質三**,因爲破壞性質三後更好調整紅黑樹使其重新符合性質。
  • 唯一不同的是紅黑樹必須根據紅黑樹的性質調整節點的顏色
  • 檢測新節點插入後,紅黑樹的性質是否造到破壞

因爲新節點的默認顏色是紅色,因此:如果其雙親節點的顏色是黑色,沒有違反紅黑樹任何性質,則不需要調整;但當新插入節點的雙親節點顏色爲紅色時,就違反了性質三不能有連在一起的紅色節點,此時需要對紅黑樹分情況來討論:

情況一:

  • cur爲新插入的節點,parent是grandfather的左孩子,uncle節點存在且顏色是紅色
  • 此時只需要將parent和uncle的顏色變爲黑色,把grandfather的顏色變爲紅色,再將grandfather給cur,繼續向上調整節點顏色即可
    情況一
    情況二:
  • cur爲新插入的節點,parent是grandfather的左孩子,uncle節點不存在或是顏色是黑色
  • cur是parent的左孩子,此時只需進行一個右單旋,之後將grandfather的顏色變爲紅色,parent的顏色變爲黑色,就可終止調整
    在這裏插入圖片描述
    情況三:
  • cur爲新插入的節點,parent是grandfather的左孩子,uncle節點不存在或是顏色是黑色
  • cur是parent的右孩子,此時只需進行一個左單旋轉換爲情況二後,按照情況二進行調整

情況三
情況四:

  • cur是新插入的節點,parent是grandfather的右孩子,uncle節點存在且顏色是紅色
  • 此時只需要將parent和uncle的顏色變爲黑色,把grandfather的顏色變爲紅色,再將grandfather給cur,繼續向上調整節點顏色即可
    在這裏插入圖片描述
    情況五:
  • cur爲新插入的節點,parent是grandfather的右孩子,uncle節點不存在或是顏色是黑色
  • cur是parent的右孩子,此時只需進行一個左單旋,之後將grandfather的顏色變爲紅色,parent的顏色變爲黑色,就可終止調整
    情況五

情況六:

  • cur爲新插入的節點,parent是grandfather的右孩子,uncle節點不存在或是顏色是黑色
  • cur是parent的左孩子,此時只需進行一個右單旋將情況轉換爲情況五繼續處理即可
    情況六

紅黑樹的代碼實現

  • 主要實現了紅黑樹的插入功能,以及驗證該樹是否滿足紅黑樹的所有性質
#pragma once

#include <iostream>
using namespace std;

enum color
{
	RED,
	BLACK
};

template<class K,class V>
struct RBSTreeNode
{
	RBSTreeNode(const pair<K, V>& kv)
	:_left(nullptr)
	, _right(nullptr)
	, _parent(nullptr)
	, _kv(kv)
	, _col(RED)
	{}
	RBSTreeNode<K, V>* _left;
	RBSTreeNode<K, V>* _right;
	RBSTreeNode<K, V>* _parent;

	pair<K, V> _kv;
	color _col;
};

template<class K, class V>
class RBTree
{
	typedef RBSTreeNode<K, V> Node;
public:
	RBTree()
		:_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)
		{
			parent = cur;
			if (kv.first < cur->_kv.first)
				cur = cur->_left;
			else if (kv.first > cur->_kv.first)
				cur = cur->_right;
			else
				return false;//如果樹中已經有該元素,則插入失敗
		}

		//找到插入位置,插入節點
		//插入的節點顏色爲紅色,破壞紅黑樹的性質3,更好處理
		cur = new Node(kv);
		cur->_col = RED;
		if (kv.first < parent->_kv.first)
		{
			parent->_left = cur;
			cur->_parent = parent;
		}
		else
		{
			parent->_right = cur;
			cur->_parent = parent;
		}

		//插入節點成功後,檢查紅黑樹的性質有沒有被破壞
		//若是有則要進行節點的顏色調整以滿足紅黑樹性質
		//若是父節點存在且父節點的顏色爲紅色則需要調整,否則滿足紅黑樹性質
		while (parent && parent->_col == RED)
		{
			// 注意:grandFather一定存在
			// 因爲parent存在,且不是黑色節點,則parent一定不是根,則其一定有雙親
			Node* grandfather = parent->_parent;
			
			//1、父節點是祖父節點的左孩子
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				//1、叔叔節點存在且叔叔節點的顏色爲紅色
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					cur = grandfather;
					parent = cur->_parent;
				}

				//2、叔叔節點不存在或者叔叔節點的顏色爲黑色
				else
				{
					//1、如果cur是parent的右孩子,此時需要進行左單旋將情況轉換爲情況2
					if (parent->_right == cur)
					{
						RotateL(parent);
						swap(cur, parent);
					}

					//1、如果cur是parent的z左孩子,此時只需進行一個右單旋,並將parent的顏色變爲黑,grandparent的顏色置紅
					RotateR(grandfather);
					grandfather->_col = RED;
					parent->_col = BLACK;
					break;
				}

			}

			//2、父節點是祖父節點的右孩子
			else
			{
				Node* uncle = grandfather->_left;
				//1、叔叔節點存在且叔叔節點的顏色爲紅色
				if (uncle && uncle->_col == RED)
				{
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					cur = grandfather;
					parent = cur->_parent;
				}

				//2、叔叔節點不存在或者叔叔節點的顏色爲黑色
				else
				{
					//1、若是cur爲parent的左孩子,先進行一個右單旋轉換爲情況二一起處理
					if (parent->_left == cur)
					{
						RotateR(parent);
						swap(cur, parent);
					}

					//2、若是cur爲parent的右孩子,進行一個左單旋,並將parent的顏色變爲黑,grandparent的顏色置紅
					RotateL(grandfather);
					grandfather->_col = RED;
					parent->_col = BLACK;
					break;
				}
			}
		}

		//旋轉完成之後,將根節點的顏色置成黑色
		_root->_col = BLACK;
		return true;
	}

	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;
		Node* pparent = parent->_parent;

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		subR->_left = parent;
		parent->_parent = subR;

		if (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (pparent->_left == parent)
			{
				pparent->_left = subR;
				subR->_parent = pparent;
			}
			else
			{
				pparent->_right = subR;
				subR->_parent = pparent;
			}
		}
	}

	//右單旋
	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		Node* pparent = parent->_parent;

		parent->_left = subLR;
		if (subLR)//置parent->_left的時候可以不管subLR是否爲空,但是若是subLR爲空取其parent就會出錯
		{
			subLR->_parent = parent;
		}

		subL->_right = parent;
		parent->_parent = subL;

		if (pparent == _root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (pparent->_left == parent)
			{
				pparent->_left = subL;
				subL->_parent = pparent;
			}
			else
			{
				pparent->_right = subL;
				subL->_parent = pparent;
			}

		}
	}


	void Inorder()
	{
		_Inorder(_root);
	}

	void _Inorder(Node* root)
	{
		if (root == nullptr)
			return;
		_Inorder(root->_left);
		cout << root->_kv.first << ":" << root->_kv.second << endl;
		_Inorder(root->_right);
	}

	bool IsValidRBTree()
	{
		Node* pRoot = _root;

		// 空樹也是紅黑樹
		if (nullptr == pRoot)
			return true;

		// 檢測根節點是否滿足情況
		if (BLACK != pRoot->_col)
		{
			cout << "違反紅黑樹性質二:根節點必須爲黑色" << endl;
			return false;
		}

		// 獲取任意一條路徑中黑色節點的個數
		size_t blackCount = 0;
		Node* pCur = pRoot;
		while (pCur)
		{
			if (BLACK == pCur->_col)
				blackCount++;
			pCur = pCur->_left;
		}

		// 檢測是否滿足紅黑樹的性質,k用來記錄路徑中黑色節點的個數
		size_t k = 0;
		return _IsValidRBTree(pRoot, k, blackCount);
	}

	bool _IsValidRBTree(Node* root, size_t k, const size_t blackCount)
	{
		if (nullptr == root)
			return true;

		// 統計黑色節點的個數
		if (BLACK == root->_col)
			k++;

		// 檢測當前節點與其雙親是否都爲紅色
		Node* parent = root->_parent;
		if (parent && RED == parent->_col && RED == root->_col)
		{
			cout << "違反性質三:沒有連在一起的紅色節點" << endl;
			return false;
		}

		// 如果root是因子節點,檢測當前路徑中黑色節點的個數是否有問題
		if (nullptr == root->_left&& nullptr == root->_right)
		{
			if (k != blackCount)
			{
				cout << "違反性質四:每條路徑中黑色節點的個數必須相同" << endl;
				return false;
			}
		}

		//遞歸判斷左右子樹都滿足紅黑樹的性質
		return _IsValidRBTree(root->_left, k, blackCount) &&
			_IsValidRBTree(root->_right, k, blackCount);
	}

private:
	Node* _root;
};


void TestRBTree()
{
	int a[] = { 5, 3, 15, 10, 8, 7, 17, 16 };
	RBTree<int, int> rt;

	for (auto& e : a)
	{
		rt.Insert(make_pair(e, e));
	}

	cout << rt.IsValidRBTree() << endl;

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