數據結構:紅黑樹的旋轉原理和模擬實現

紅黑樹的旋轉原理和模擬實現

我們瞭解到AVL樹雖然效率很高,但是它是通過多次的旋轉纔到達一個絕對的平衡,旋轉的消耗其實也很大。因此開始引入近似平衡的一棵樹----紅黑樹(RBTree)。紅黑樹每一個節點不是紅色的就是黑色的,它保證了最長路徑不超過最短路徑的二倍。

其實一般來說使用紅黑樹會比AVL樹更多,因爲雖然AVL樹是更加平衡的,但是它的平衡是通過更多次的旋轉得到的,旋轉的時候消耗還是很大的。而紅黑樹是近似平衡的,它的旋轉比AVL樹要少。但是有人可能會有疑問,那紅黑樹的搜索效率沒有AVL樹高啊?其實他倆是差不多的,因爲AVL樹是O(logN),而紅黑樹是O(2logN),假如是十億個數據的話,AVL樹用30次查找,那麼紅黑樹也就是60次查找,對於現在的計算機CPU計算速度來說是沒有多大影響的,所以用紅黑樹用的會多一些。

紅黑樹的特點:
  1. 根節點一定是黑色的
  2. 一個紅色節點的孩子只能是黑色的
  3. 每一條路徑上的黑色節點的個數相同
  4. 每個節點不是紅色的就是黑色的
  5. 每個葉子結點都是黑色的(此處的葉子結點指的是空結點)

根據這五個特點我們就可以推斷出最長路徑不超過最短路徑的二倍。因爲如果一顆樹是紅黑樹,那麼它的最短路徑就是全黑節點的那一條路徑,最長路徑就是一個黑色一個紅色這樣紅黑相間的路徑。

紅黑樹節點的定義

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;//顏色
};

紅黑樹的插入

紅黑樹的插入需要這麼幾步:

  1. 如果是空樹,那麼直接讓root等於這個新開節點,然後把這個節點的顏色變爲黑色
  2. 如果不是空樹,那麼就先去找要插入的位置,如果要插入的值比該節點小就去左邊,比該節點大就去右邊
  3. 找到位置之後將該節點連接到樹中,然後將這個新插入的節點設置爲紅色(爲什麼是設置爲紅色呢? 因爲如果插入一個黑色節點的話,那麼就會破壞紅黑樹的每條路徑上的黑色節點數目相同的這條性質,因爲在你插入的這條路徑上就會多出來一個黑色節點,那麼就會影響到其他的路徑,影響範圍很大。但是如果插入的是紅色節點,那麼如果它的父親是黑色就非常完美,不用對樹進行修改,只有當它的父親是紅色才需要進行修改,因爲兩個紅色的節點不能連接再一起。所以說如果插入黑色節點,必然一定會破壞所以路徑,而插入紅色的節點是有可能會破壞這條路徑。)
  4. 如果插入這個紅色節點之後父親也是紅色,則對這顆樹進行調整

如果要進行調整的的話分爲以下幾種情況,我們約定一下起名規則如下:
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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章