1. 程式人生 > 其它 >紅黑樹詳解

紅黑樹詳解

技術標籤:資料結構

  • 基礎知識

紅黑樹是一種二叉搜尋樹,是AVL樹的改進版。
紅黑樹的生長是自底向上的,跟2-3樹類似
為啥引入紅黑樹:
1、二插搜尋樹會出現退化成連結串列的情況,其時間複雜度為O(n)
2、為防止二叉搜尋樹會退化成連結串列,引入了對左右子樹的高度差有嚴格顯示的AVL樹。
AVL樹的缺點:
因為對左右子樹的高度差有嚴格的規定,所以其在插入和刪除時會出現旋轉,導致效能下降。
這才有了對平衡概念不是很嚴格的紅黑樹

紅黑樹與AVL樹的比較
1. AVL樹的時間複雜度優於紅黑樹,但是對於現在的計算機,這種差別可以忽略
2. 紅黑樹的插入刪除比AVL樹更便於控制操作。
3. 紅黑樹整體效能略優於AVL樹。(紅黑樹旋轉情況少於AVL樹)。這點是非常重要的

4. 如果是在查詢很多增刪少的情況下 AVL 樹還是優於紅黑樹的,如果增刪比較頻繁,那紅黑樹絕對是完美的一種選擇

紅黑樹的特性:
1. 根節點是【黑色】
2. 每個節點是【黑色】或者是【紅色】
3. 【紅色】節點的兩個子節點一定都是【黑色】
4. 每個葉子節點(NIL)都是【黑色】
5. 任意一個節點到葉子節點的路徑上所包含的【黑色】節點的數量是相同的---這個也稱之為【黑色完美平衡】

  • 插入

下圖中插入節點為I,父節點為P,爺爺節點為PP,叔叔節點為U。

1、紅黑樹為null,則新插入節點作為根節點,變色為黑色

2、插入節點的父節點為黑色,直接插入即可

3、插入節點的父節點為紅色

3.1插入節點的父節點為插入節點的爺爺節點的左孩子

3.1.1插入節點的叔叔節點存在,且為紅色
將新插入節點的父節點和其叔叔節點設定為黑色即可,爺爺節點設定為紅色,將爺爺節點設定為當前節點

如果pp為根節點,則其仍未黑色,此時為增加黑色的格式
3.1.2 叔叔節點不存在(其實叔叔節點不可能為黑色,因為在新插入節點之前,父節點是紅色,破壞了黑色平衡,故叔叔節點一定是不存在的)
(1)插入節點為其父節點的左孩子
將父節點設定為黑色,爺爺節點設定為紅色,對其爺爺節點進行右旋,當前節點設定為P


(2)插入節點為其父節點的右孩子
先對父節點進行左旋,將插入節點設定為黑色,爺爺節點設定為紅色,然後對其爺爺節點進行右旋,I設定為當前點

3.2插入節點的父節點為插入節點的爺爺節點的右孩子
3.2.1 插入節點的叔叔節點存在,且為紅色
將新插入節點的父節點和其叔叔節點設定為黑色即可,爺爺節點設定為紅色,將爺爺節點設定為當前節點


3.2.2 叔叔節點不存在(其實叔叔節點不可能為黑色,因為在新插入節點之前,父節點是紅色,破壞了黑色平衡,故叔叔節點一定是不存在的)
(1)插入節點為其父節點的右孩子
將父節點設定為黑色,爺爺節點設定為紅色,對其爺爺節點進行左旋


(2)插入節點為其父節點的左孩子
先對其父節點進行右旋,將插入節點設定為黑色,爺爺節點設定為紅色,然後對其爺爺節點進行左旋

//RBTree.h
#pragma once
/*
紅黑樹
是一種二叉搜尋樹,是AVL樹的改進版。
紅黑樹的生長是自底向上的,跟2-3樹類似
為啥引入紅黑樹:
1、二插搜尋樹會出現退化成連結串列的情況,其時間複雜度為O(n)
2、為防止二叉搜尋樹會退化成連結串列,引入了對左右子樹的高度差有嚴格顯示的AVL樹。
	AVL樹的缺點:
	因為對左右子樹的高度差有嚴格的規定,所以其在插入和刪除時會出現旋轉,導致效能下降。
這才有了對平衡概念不是很嚴格的紅黑樹

紅黑樹與AVL樹的比較
1. AVL樹的時間複雜度優於紅黑樹,但是對於現在的計算機,這種差別可以忽略
2. 紅黑樹的插入刪除比AVL樹更便於控制操作。
3. 紅黑樹整體效能略優於AVL樹。(紅黑樹旋轉情況少於AVL樹)。這點是非常重要的
4. 如果是在查詢很多增刪少的情況下 AVL 樹還是優於紅黑樹的,如果增刪比較頻繁,那紅黑樹絕對是完美的一種選擇

紅黑樹的特性:
1. 根節點是【黑色】
2. 每個節點是【黑色】或者是【紅色】
3. 【紅色】節點的兩個子節點一定都是【黑色】
4. 每個葉子節點(NIL)都是【黑色】
5. 任意一個節點到葉子節點的路徑上所包含的【黑色】節點的數量是相同的---這個也稱之為【黑色完美平衡】
*/

#include <iomanip>
#include <iostream>

enum RBTreeColor{RED, BLACK};

template <typename T>
struct RBTreeNode
{
	RBTreeColor color;
	RBTreeNode *pLeft;
	RBTreeNode *pRight;
	RBTreeNode *pParent;//會出現找父節點變色或者旋轉的情況
	T data;

	RBTreeNode(RBTreeColor c, RBTreeNode *left, RBTreeNode *right, RBTreeNode *parent, T data) :
		color(c), pLeft(left), pRight(right), pParent(parent), data(data) {}
};

template <typename T>
class RBTree
{
public:
	RBTree()
	{
		pRoot = nullptr;
	}
	~RBTree()
	{
		Destroy();
	}

	//增
	void Insert(T data);
	//刪
	void Delete();
	//查

	//遍歷
	void PreTravel();

	//銷燬
	void Destroy();

	//列印
	void Print();

private:
	void InsertNode(RBTreeNode<T> *&pNode, T data);//插入
	void LeftRotate(RBTreeNode<T> *&pNode);//左旋
	void RightRotate(RBTreeNode<T> *&pNode);//右旋
	void InsertNodeAmend(RBTreeNode<T> *&pNode, RBTreeNode<T> *pNewNode);//修正為一顆紅黑樹
	void Print(RBTreeNode<T> *pNode, T data, int dirction);//data表示節點值, direction表示左節點(-1)還是右節點(1)

	void PreTravel(RBTreeNode<T> *pNode);//前序遍歷

	void Destroy(RBTreeNode<T> *pNode);

	RBTreeNode<T> *pRoot;

	inline void SetBlack(RBTreeNode<T> *pNode)
	{
		pNode->color = BLACK;
	}

	inline void SetRed(RBTreeNode<T> *pNode)
	{
		pNode->color = RED;
	}
};

template<typename T>
void RBTree<T>::Insert(T data)
{
	InsertNode(pRoot, data);
}

template<typename T>
void RBTree<T>::PreTravel()
{
	PreTravel(pRoot);
}

template<typename T>
void RBTree<T>::Destroy()
{
	Destroy(pRoot);
}




template<typename T>
void RBTree<T>::Print()
{
	Print(pRoot, pRoot->data, 0);
}

template<typename T>
void RBTree<T>::InsertNode(RBTreeNode<T>* &pNode, T data)
{
	//根節點 設定為黑
	if (pNode == nullptr)
	{
		pNode = new RBTreeNode<T>(BLACK, nullptr, nullptr, nullptr, data);
		return;
	}
	RBTreeNode<T> *pNodeParent = nullptr;
	RBTreeNode<T> *pTmp = pNode;

	//遍歷查詢,找到待插入位置的父節點處
	while (pTmp != nullptr)
	{
		pNodeParent = pTmp;
		if (pTmp->data < data)
		{
			pTmp = pTmp->pRight;
		}
		else
		{
			pTmp = pTmp->pLeft;
		}
	}
	RBTreeNode<T> *pNew = new RBTreeNode<T>(RED, nullptr, nullptr, nullptr, data);
	pNew->pParent = pNodeParent;
	if (pNodeParent != nullptr)
	{
		if (pNodeParent->data > pNew->data)
			pNodeParent->pLeft = pNew;
		else
			pNodeParent->pRight = pNew;
	}	

	InsertNodeAmend(pNode, pNew);
}

/*
旋轉步驟:
1、pNode右孩子的左子樹插入pNode的右子樹上,如果pNodeRightChild的左子樹不為null,則其父節點為pNode
2、pNode的父節點設為pNodeRightChild的父節點
3、判斷pNode的父節點是否為null,為null則說明pNode為根節點,應將其右節點作為pRoot
不為null,則應判斷pNode是左子樹還是右子樹,將pNodeRightChild作為相應的子樹
4、pNode作為其右孩子的左子樹,pNode的父節點指向pNodeRightChild
5、將pNodeRightChild設為當前節點pNode
*/
template<typename T>
void RBTree<T>::LeftRotate(RBTreeNode<T>*& pNode)
{
	RBTreeNode<T> *pNodeRightChild = pNode->pRight;
	pNode->pRight = pNodeRightChild->pLeft;

	if (pNodeRightChild->pLeft != nullptr)
	{
		pNodeRightChild->pLeft->pParent = pNode;
	}
	//pNode的父節點設為pNodeRightChild的父節點
	pNodeRightChild->pParent = pNode->pParent;

	//如果pNode的父節點為null,則將其右節點設為根節點
	if (pNode->pParent == nullptr)
	{
		pRoot = pNodeRightChild;
	}
	else
	{
		if (pNode->pParent->pLeft == pNode)
			pNode->pParent->pLeft = pNodeRightChild;
		else
			pNode->pParent->pRight = pNodeRightChild;
		pNodeRightChild->pParent = pNode->pParent;
	}
	//pNode作為pNodeRightChild的左孩子
	pNodeRightChild->pLeft = pNode;
	//pNode的父節點為pNodeRightChild
	pNode->pParent = pNodeRightChild;

	//pNodeRightChild作為pNode
	pNode = pNodeRightChild;
}

/*
旋轉步驟:
1、pNode左孩子的右子樹插入pNode的左子樹上,如果pNode左子樹的右子樹不為null,則該右子樹的父親為pNode
2、pNode的父節點設為pNodeLeftChild的父節點
3、判斷pNode的父節點是否為null,為null則說明pNode為根節點,應將其右節點作為pRoot
不為null,則應判斷pNode是左子樹還是右子樹,將pNodeLeftChild作為相應的子樹
4、pNode作為其左孩子的右子樹,pNode的父節點指向pNodeLeftChild
5、將pNodeLeftChild設為當前節點pNode
*/
template<typename T>
void RBTree<T>::RightRotate(RBTreeNode<T>*& pNode)
{
	//pNode左孩子的右子樹插入pNode的左子樹上
	RBTreeNode<T> *pNodeLeftChild = pNode->pLeft;
	pNode->pLeft = pNodeLeftChild->pRight;
	//如果pNode左子樹的右子樹不為null,則該右子樹的父親為pNode
	if (pNodeLeftChild->pRight != nullptr)
	{
		pNodeLeftChild->pRight->pParent = pNode;
	}

	//pNodeLeftChild的父親設為pNode的父親
	pNodeLeftChild->pParent = pNode->pParent;

	//如果pNode為根節點,則pNodeLeftChild設為根節點
	if (pNode->pParent == nullptr)
		pRoot = pNodeLeftChild;
	else//根據pNode是其父節點的左or右孩子,將pNodeLeftChild設為相應的孩子
	{
		if (pNode->pParent->pLeft == pNode)
			pNode->pParent->pLeft = pNodeLeftChild;
		else
			pNode->pParent->pRight = pNodeLeftChild;
		pNodeLeftChild->pParent = pNode->pParent;
	}
	//pNode作為其左孩子的右子樹
	pNodeLeftChild->pRight = pNode;
	//pNode的父節點為pNodeLeftChild
	pNode->pParent = pNodeLeftChild;

	//pNodeLeftChild作為pNode
	pNode = pNodeLeftChild;
}

/*
調整平衡分為以下幾種情況
1、紅黑樹為null,則新插入節點作為根節點,變色為黑色
2、插入節點的父節點為黑色,直接插入即可

3、插入節點的父節點為紅色

	3.1插入節點的父節點為插入節點的爺爺節點的左孩子
		3.1.1插入節點的叔叔節點存在,且為紅色
			 將新插入節點的父節點和其叔叔節點設定為黑色即可,爺爺節點設定為紅色,將爺爺節點設定為當前節點
		3.1.2 叔叔節點不存在(其實叔叔節點不可能為黑色,因為在新插入節點之前,父節點是紅色,破壞了黑色平衡,故叔叔節點一定是不存在的)
		(1)插入節點為其父節點的左孩子
				將父節點設定為黑色,爺爺節點設定為紅色,對其爺爺節點進行右旋
		(2)插入節點為其父節點的右孩子
			先對父節點進行左旋,將插入節點設定為黑色,爺爺節點設定為紅色,然後對其爺爺節點進行右旋

	3.2插入節點的父節點為插入節點的爺爺節點的右孩子
		3.2.1 插入節點的叔叔節點存在,且為紅色
			將新插入節點的父節點和其叔叔節點設定為黑色即可,爺爺節點設定為紅色,將爺爺節點設定為當前節點
		3.2.2 叔叔節點不存在(其實叔叔節點不可能為黑色,因為在新插入節點之前,父節點是紅色,破壞了黑色平衡,故叔叔節點一定是不存在的)
		(1)插入節點為其父節點的右孩子
			將父節點設定為黑色,爺爺節點設定為紅色,對其爺爺節點進行左旋
		(2)插入節點為其父節點的左孩子
			先對其父節點進行右旋,將插入節點設定為黑色,爺爺節點設定為紅色,然後對其爺爺節點進行左旋
*/
template<typename T>
void RBTree<T>::InsertNodeAmend(RBTreeNode<T>*& pNode, RBTreeNode<T> *pNewNode)
{
	RBTreeNode<T> *pParent, *pGParent, *pUncle;

	pParent = pNewNode->pParent;
	
	//只有當第三種情況發生時才會發生紅黑樹的調整 
	while (pParent != nullptr && pParent->color == RED)
	{
		pGParent = pParent->pParent;
		//插入節點的父節點為插入節點的爺爺節點的左孩子
		if (pParent == pGParent->pLeft)
		{
			pUncle = pGParent->pRight;
			//叔叔節點存在且為紅色
			//將新插入節點的父節點和其叔叔節點設定為黑色即可,將爺爺節點設定為當前節點
			if (pUncle && pUncle->color == RED)
			{
				SetBlack(pUncle);
				SetBlack(pParent);
				SetRed(pGParent);
				pNewNode = pGParent;
			}
			//叔叔節點不存在
			if (pUncle == nullptr)
			{
				//插入節點為其父節點的左孩子
				//將父節點設定為黑色,對其爺爺節點進行右旋
				if (pParent->pLeft == pNewNode)
				{
					SetRed(pGParent);
					SetBlack(pNewNode);
					RightRotate(pGParent);
				}
				//插入節點為其父節點的右孩子
				//先對父節點進行左旋,將插入節點設定為黑色,爺爺節點設定為紅色,然後對其爺爺節點進行右旋
				else
				{
					LeftRotate(pParent);
					SetBlack(pParent);//此時插入節點指向pParent
					SetRed(pGParent);
					RightRotate(pGParent);
				}
			}
		}
		//插入節點的父節點為插入節點的爺爺節點的右孩子
		else
		{
			pUncle = pGParent->pLeft;//叔叔節點為父節點的左孩子
			//叔叔節點存在且為紅色
			//將新插入節點的父節點和其叔叔節點設定為黑色即可,將爺爺節點設定為當前節點
			if (pUncle && pUncle->color == RED)
			{
				SetBlack(pUncle);
				SetBlack(pParent);
				SetRed(pGParent);
				pNewNode = pGParent;
			}
			//叔叔節點不存在
			if (pUncle == nullptr)
			{
				//插入節點為其父節點的右孩子
				//將父節點設定為黑色,爺爺節點設定為紅色,對其爺爺節點進行左旋
				if (pNewNode == pParent->pRight)
				{
					SetBlack(pParent);
					SetRed(pGParent);
					LeftRotate(pGParent);
				}
				//插入節點為其父節點的左孩子
				//先對其父節點進行右旋,將插入節點設定為黑色,爺爺節點設定為紅色,然後對其爺爺節點進行左旋
				else
				{
					RightRotate(pParent);
					SetBlack(pParent);//此時插入節點指向pParent
					SetRed(pGParent);
					LeftRotate(pGParent);
				}
			}

		}
	}

	//無根節點,新插入節點做根節點,設定為黑色
	SetBlack(pRoot);
}

template<typename T>
void RBTree<T>::Print(RBTreeNode<T>* pNode, T data, int direction)
{
	if (pNode != NULL)
	{
		if (direction == 0)    // tree是根節點
			std::cout << std::setw(2) << pNode->data << "(B) is root" << std::endl;
		else                // tree是分支節點
			std::cout << std::setw(2) << pNode->data << ((pNode->color == RED) ? "(R)" : "(B)") << " is " << std::setw(2) << data << "'s " << std::setw(12) << (direction == 1 ? "right child" : "left child") << std::endl;

		Print(pNode->pLeft, pNode->data, -1);
		Print(pNode->pRight, pNode->data, 1);
	}
}

template<typename T>
void RBTree<T>::PreTravel(RBTreeNode<T>* pNode)
{
	if (pNode != nullptr)
	{
		std::cout << pNode->data << std::endl;
		PreTravel(pNode->pLeft);
		PreTravel(pNode->pRight);
	}
}

//後序遍歷來刪除
template<typename T>
void RBTree<T>::Destroy(RBTreeNode<T>* pNode)
{
	while (pNode)
	{
		Destroy(pNode->pLeft);
		Destroy(pNode->pRight);
		//節點
		delete pNode;
		pNode = nullptr;
	}
}
// RBTree.cpp : 此檔案包含 "main" 函式。程式執行將在此處開始並結束。
//

#include <iostream>
#include "RBTree.h"

int main()
{
	int a[] = { 1, 4, 10, 6, 90, 7, 2, 110, 81 };
	RBTree<int> tree;
	int len = sizeof(a) / sizeof(a[0]);

	for (int i = 0; i < len; i++)
	{
		tree.Insert(a[i]);
		std::cout << "== 新增節點: " << a[i] << std::endl;
		std::cout << "== 樹的詳細資訊: " << std::endl;
		tree.Print();
		std::cout << std::endl;
	}
		

	//tree.PreTravel();


	return 0;

}