红黑树简单实现

Posted Moua

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了红黑树简单实现相关的知识,希望对你有一定的参考价值。

目录

一、红黑树的概念

1、红黑树的性质

2、红黑树的节点定义

3、红黑树结构

4、红黑树 VS AVL树

二、红黑树的插入操作

三、红黑树的简单实现


一、红黑树的概念

红黑树是一种二叉搜索树,树的节点上有一个存储颜色的属性,它的颜色不是黑色就是红色,同时它还具有二叉搜索树的所有性质。

1、红黑树的性质

  • 树的节点只能是黑色或者红色。
  • 树的根节点必须是黑色。
  • 一条路径上不能存在连续的红色节点(如果一个节点是红色的,那么它的左右孩子节点必须是黑色的)。
  • 对于每个节点从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点
  • 所有叶节点都必须是黑色的。这里的叶节点和我们平时所理解的叶节点有所不同,这里的叶接地那指的是空节点(NIL节点),平时我们所说的叶节点指的是左右孩子均为空的节点。

思考:为什么满足上面性质,红黑树就能保证“其最长路径节点个数不超过最短路径节点个数的2倍”?

对于一颗红黑树来说,所有路径不能存在连续的红色节点并且从根节点到所有叶节点的路径上黑色节点的个数是相同的。因此,理论上红黑树的最长路径应该是红色和黑色节点间隔(一个黑色节点一个红色节点的形式),而最短路径应该是全黑色节点。所以,最长路径上的节点个数一定是小于等于最短路径节点个数的2倍的。

2、红黑树的节点定义

根据红黑树的性质,红黑树中至少包含左右孩子节点指针、值域、表示颜色的域,同时二叉树的插入操作需要进行旋转等,为了实现简便,这里还给出了双亲节点指针。

//节点颜色
enum Clor
{
	RED,
	BLACK
};

//节点定义
template<class K,class V>
struct RBTreeNode
{
	ValueType _val;//值
	RBTreeNode<class K, class V>* _left;//左孩子
	RBTreeNode<class K, class V>* _right;//右孩子
	RBTreeNode<class K, class V>* _parent;//双亲节点
	Color _col;//颜色

	//构造函数
	RBTreeNode(const pair<K,V>& kv)
		:_val(kv)
		, _left(nullptr), _right(nullptr)
		, _col(RED)
	{}
};

3、红黑树结构

关联式容器map和set都是直接使用红黑树进行实现的,为了实现简单,红黑树的实现中增加了一个头节点,因为根节点必须为黑色,为了与根节点进行区分,将头结点给成黑色,并让头结点的parent指向红黑树的根节点,left指向红黑树中最小的节点,right指向红黑树中最大的节点。

4、红黑树 VS AVL树

红黑树和AVL树都是二叉搜索树,由于性质的差别,导致红黑树和AVL树略有不同:

  • 平衡:AVL树是绝对平衡的二叉搜索树,而红黑树是近似平衡的。其次就是AVL树靠平衡因子保证平衡。
  • 树的高度:由于AVL树是绝对平衡的,因此有N个节点的AVL树的高度为logN;而红黑树是相对平衡的,假设红黑树中黑色节点的个数为X则总结点的个数为[X,2X],最短路径节点个数为logN,最长路径节点个数2*logN,因此树的高度近似为logN。
  • 效率比较:红黑树和AVL树的高度都可以认为是logN(假设有1亿个数据,使用红黑树存储高度最多为60而AVL树存储高度为30,看似相差一半,但是比较30次和比较60次对CPU来说差别不大,CPU的速度太快了!!!),因此可以认为搜索效率来看红黑树和AVL树是一样的。但是,插入一个节点AVL树的旋转次数要比红黑树多,因此总体来看红黑树的效率要比AVL树的效率要高。
  • 总结:红黑树和AVL数都是效率很高的搜索结构,但是相比之下红黑树更占优势,因此在实际中红黑树的使用比AVL树要多。

二、红黑树的插入操作

红黑树的插入操作和AVL树相似,也要考虑多种情况,在一些情况下也需要进行旋转。

在红黑树中插入一个节点,如果插入的是红色节点则有可能会破坏“所有路径不能出现连续的红色节点”的性质;如果插入的节点是黑色,则一定会破坏“所有路径黑色节点个数相同”的性质。但是,相比之下,插入一个红色节点比插入一个黑色节点要好调整,因此选择插入的节点为红色节点然后在对红黑树进行调整。插入一个节点后,有以下几种情况:

1、新插入节点的父节点是黑色节点

这里,可以看出并没有破坏红黑树的任何一条性质,因此不需要进行调整。

2、新插入节点的父节点是红色,叔叔节点存在且为红色节点

这种情况下,我们可以将newNode的parent节点和uncle节点变为黑色,将parent的parent节点变成红色。

因此,当新插入的节点的叔叔节点和父亲节点都是红色节点时,可以将父亲节点和叔叔节点变成黑色,parent的parent节点变成红色,在递归向上判断,直到该树变成一个红黑树为止。

3、新插入节点的父节点是红色,叔叔节点不存在或者为黑色

遇到这种情况,就需要对红黑树进行旋转,就需要进一步判断是那种旋转。

1)新插入节点为父节点的左孩子/右孩子节点,父节点为爷爷节点的左孩子/右孩子节点。

这种情况下,只需要进行单旋在将parent节点变成黑色,grandParent变成红色:

  • 当newNode是parent的左孩子且parent是grandParent的左孩子时,进行右单旋。
  • 当newNode是parent的右孩子且parent是grandParent的右孩子时,进行左单旋。

2)新插入节点为父节点的左孩子/右孩子节点,父节点为grandParent孩子的右孩子/左孩子节点。

在这种情况下,就需要进行双旋在将newNode变成黑色grandParent变成红色:

  • 当parent是grandParent的左孩子节点,newNode是parent的右孩子节点时进行左右双旋。
  • 当parent是grandParent的右孩子节点,newNode是parent的左孩子节点是进行右左双旋。

三、红黑树的简单实现

#pragma once
#include<iostream>
#include<utility>
using namespace std;

//节点颜色
enum Color
{
	RED,
	BLACK
};

//节点定义
template<class K,class V>
struct RBTreeNode
{
	std::pair<K,V> _val;//值
	RBTreeNode<K, V>* _left;//左孩子
	RBTreeNode<K, V>* _right;//右孩子
	RBTreeNode<K, V>* _parent;//双亲节点
	Color _col;//颜色

	//构造函数
	RBTreeNode(const pair<K,V>& kv)
		:_val(kv)
		, _left(nullptr), _right(nullptr), _parent(nullptr)
		, _col(RED)
	{}
};


template<class K,class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
private:
	Node* _root = nullptr;//根节点
public:
	//构造函数---使用默认即可
	//插入
	pair<Node*, bool> insert(const pair<K,V>& node)
	{
		//申请节点
		Node* newNode = new Node(node);
		//如果根节点为空,直接插入为根节点
		if (_root == nullptr)
		{
			_root = newNode;
			_root->_col = BLACK;
			return make_pair(_root,true);
		}
		//找插入位置进行插入
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_val.first > newNode->_val.first)
			{
				//插入的节点比cur小,左插
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_val.first < newNode->_val.first)
			{
				//插入的节点比cur大,右插
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				//插入的节点存在,直接返回该节点的val
				return make_pair(cur,false);
			}
		}
		//找到了插入位置,进行插入
		if (parent->_val.first > newNode->_val.first)
		{
			//插入到parent的左边
			parent->_left = newNode;
			newNode->_parent = parent;
		}
		else
		{
			//插入到parent的右边
			parent->_right = newNode;
			newNode->_parent = parent;
		}
		//插入成功,对树进行调整
		cur = newNode;
		parent = cur->_parent;
		//新插入节点的父节点是红色的才需要调整---走到这里,新插入的节点父节点肯定存在
		while (parent && parent->_col == RED)
		{
			//走到这里grandParent节点必然是黑色的
			Node* grandParent = parent->_parent;
			Node* uncle = nullptr;
			if (grandParent->_left == parent)
			{
				uncle = grandParent->_right;
			}
			else
			{
				uncle = grandParent->_left;
			}
			//情况1:新插入节点的叔叔节点存在且为红
			if (uncle && uncle->_col == RED)
			{
				//将父节点和叔叔节点变成黑色,爷爷节点变成红色
				uncle->_col = BLACK;
				parent->_col = BLACK;
				grandParent->_col = RED;
				//继续迭代
				cur = grandParent;
				parent = cur->_parent;
			}
			else
			{
				//新插入节点的叔叔节点不存在或者新插入节点的叔叔节点为黑色
				if (grandParent->_left == parent)
				{
					if (parent->_left == cur)
					{
						//右单旋
						RotateR(grandParent);
						//调整颜色
						parent->_col = BLACK;
						grandParent->_col = RED;
					}
					else
					{
						//左右双旋
						RotateL(parent);
						RotateR(grandParent);
						//调整颜色
						cur->_col = BLACK;
						grandParent->_col = RED;
					}
				}
				else
				{
					if (parent->_right == cur)
					{
						//左单旋
						RotateL(grandParent);
						parent->_col = BLACK;
						grandParent->_col = RED;
					}
					else
					{
						//右左双旋
						RotateR(parent);
						RotateL(grandParent);
						//调整颜色
						cur->_col = BLACK;
						grandParent->_col = RED;
					}
				}
				break;
			}
		}

		//在调整过程中,有可能将根节点变成了红色节点,因此需要将根节点调整成黑色的
		_root->_col = BLACK;

		return make_pair(newNode,true);
	}

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

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		Node* parentParent = parent->_parent;

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

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

			subL->_parent = parentParent;
		}
	}
	//左单旋
	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

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

		subR->_left = parent;

		Node* parentParent = parent->_parent;
		parent->_parent = subR;

		if (_root == parent)
		{
			_root = subR;
		}
		else
		{
			if (parentParent->_left == parent)
			{
				parentParent->_left = subR;
			}
			else
			{
				parentParent->_right = subR;
			}
		}

		subR->_parent = parentParent;
	}

	static void _inOrder(Node* root)
	{
		if (root == nullptr)
			return;
		_inOrder(root->_left);
		std::cout << root->_val.first << " ";
		_inOrder(root->_right);
	}
	//中序遍历
	void inOrder()
	{
		_inOrder(_root);
		std::cout << endl;
	}

	bool RedNode(Node* root)
	{
		if (root == nullptr)
		{
			return true;
		}
		if (root->_col == RED)
		{
			//判断父节点是否为红色
			if (root->_parent && root->_parent->_col == RED)
			{
				return false;
			}
		}
		//判断左右子树
		return RedNode(root->_left) && RedNode(root->_right);
	}

	bool BlackNodeNum(Node* root,int blackNum,int num)
	{
		//检查是否每条路径上的黑色节点的个数都相同
		if (root == nullptr)
		{
			return blackNum == num;
		}

		if (root->_col == BLACK)
		{
			blackNum++;
		}

		return BlackNodeNum(root->_left, blackNum, num) && BlackNodeNum(root->_right, blackNum, num);
	}

	//检查红黑树
	bool check()
	{
		if (_root && _root->_col == RED)
		{
			return false;
		}
		
		//求出一条路径上黑色节点的个数
		int num = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
			{
				num++;
			}
			cur = cur->_left;
		}
		return RedNode(_root) && BlackNodeNum(_root, 0,num);
	}

};

 

以上是关于红黑树简单实现的主要内容,如果未能解决你的问题,请参考以下文章

红黑树简单实现

红黑树插入与删除完整代码(dart语言实现)

浅谈红黑树(C语言代码实现)

10.STL简单红黑树的实现

红黑树 实现

红黑树底层实现原理分析及JAVA代码实现