红黑树介绍和结点的插入

Posted 两片空白

tags:

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

目录

一.红黑树的介绍

        1.1 红黑树的概念

        1.2 红黑树的性质

        1.3 红黑树的时间复杂度

 二.红黑树的实现

        2.1 结点定义

        2.2 红黑树的插入操作的实现

        2.2.1 按照搜索树进行插入

        2.2.2 检测新节点插入后,红黑树的性质是否被破坏

三.总代码

四.红黑树验证


一.红黑树的介绍

        1.1 红黑树的概念

        红黑树,也是一颗二叉搜索树。但是在二叉搜索树的基础上,在每个结点上增加一个存储位来表示结点的颜色,可以是Red或者Black。通过对任何一条从根节点到一种结点的路径上各自结点的着色方式的限制,来确保没有一条路径会比其它路径多出两倍,因此红黑树是接近平衡的。

         1.2 红黑树的性质

  1. 每个结点不是黑色就是红色
  2. 根节点一定是黑色
  3. 红色结点的两个孩子结点一定是黑色
  4. 每条路径的黑色结点个数相同。完整路径是包含NIL的路径。 
  5. 每一个叶子结点都是黑色的,这里的叶子结点是指空结点。

简单总结主要的性质:

  1. 根节点是黑色的
  2. 没有连续的红色结点
  3. 每条路径黑色结点个数相同。

为什么满足上面的性质就能实现没有一条路径会是其它路径长度的两倍?

红黑树中最短的的路径是结点全黑的(每个路径都有同样个数的黑色结点,最短的肯定是全黑的)

最长的路径是,一个结点为黑色,一个结点为红色,相间的。

此时最长的路径是最短路径的两倍,这两个是极端的情况。

所以不存在有一条路径会超过其它路径的两倍。

        1.3 红黑树的时间复杂度

        搜索树的增删查改都需要进行查找,搜索树的时间复杂度主要体现在查找上,二查找的效率和树的高度有关。

        如果要查找的值在最短路径中,查找效率最低为O(logN),如果要查找的值在最长的路径中,查找的效率为2*O(logN),红黑树的时间复杂度介于两者之间。所以时间复杂度为O(logN),并且效率会比平衡树效率低一点。

        但是对于CPU来说并没有什么影响,因为CPU效率太高了。

        红黑树在现实中用得多,因为同样的增删查改,平衡树旋转次数更多,并且红黑树更容易实现。

 二.红黑树的实现

        2.1 结点定义

        我实现的红黑树是一个三叉链,里边还包含颜色,并且是KV模型的。

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

//结点
template<class K,class V>
struct BRTreeNode
{
	BRTreeNode(const pair<K,V>& kv)
	:_left(nullptr)
	, _right(nullptr)
	, _parent(nullptr)
	, _kv(kv)
	, _col(RED)//新增结点的颜色为红色
	{}

	BRTreeNode *_left;
	BRTreeNode *_right;
	BRTreeNode *_parent;

	pair<K, V> _kv;
	Color _col;//结点颜色
};

为什么将插入的新节点初始化为红色?

        不管初始化为红色还是黑色都会破环红黑树的性质。

        为红色,可能破坏不能连续出现红色的性质

        为黑色,可能破坏每条路径的黑色结点个数相等的限制。

        但是初始化为红色只影响了一条路径,为黑色则破坏了所有的路径。所以选择结点初始化为红色。

        2.2 红黑树的插入操作的实现

        首先红黑树是一颗二叉搜索树,但是在搜索树的基础上增加了一些限制条件,所以当不符合限制条件时,需要进行一些调整。

步骤如下:

  1. 按照搜索树进行插入
  2. 检测新节点插入后,红黑树的性质是否照到破坏。

        2.2.1 按照搜索树进行插入

        这个我就不详细介绍,主要时按照搜索树的新增,当插入值小于当前结点,往左边找插入位置,当插入值大于当前结点,往右边找插入位置,如果相等,则插入失败。

        注意更新父节点。

//空结点,直接生成后,更新头节点。
		if (_root == nullptr){
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}
		Node *cur = _root;
		Node *parent = nullptr;
		while (cur){
			if (kv.first > cur->_kv.first){
				parent = cur;
				cur = cur->_right;
			}
			else if (kv.first < cur->_kv.first){
				parent = cur;
				cur = cur->_left;
			}
			else{
				return false;
			}
		}

		cur = new Node(kv);
		cur->_parent = parent;
        //判断插入父节点的左边还是右边
		if (parent->_kv.first>cur->_kv.first){
			parent->_left = cur;
		}
		else{
			parent->_right = cur;
		}

        2.2.2 检测新节点插入后,红黑树的性质是否被破坏

        因为插入的结点为红色,于是会有以下几种情况。

        首先约定,cur为当前结点,parent为父节点,grandfather为祖父结点,uncle为叔叔结点。并且我举得例子是cur插入在右边的情况,左边的情况类似,旋转时换格方向即可。

  • 如果根节点为空结点,直接生成根节点,将根节点的颜色置黑。
  • 如果parent结点为黑色,并没有破坏红黑树的性质。
  • 如果parent结点为红色(grandfather一定时黑色),此时破坏了红黑树的性质,不能有连续的红色结点。此时需要进行调整。调整有分以下几种情况。(注意:调整主要看uncle叔叔结点)

        情况一:uncle结点存在且为红色。(此时结点颜色:cur红色,parent红色,grandfather黑色,uncle红色)

        解决:此时需要进行变色处理。

        将parent和uncle变黑,grandfather变红。

        但是注意:grandfather所在树可能是子树,也可能是完整的树。

        如果是子树,grandfather变红,可能grandfather可能也是红的,所以需要继续往上更新。

        如果是完整的树,即grandfather是根节点,需要将grandfather颜色改为黑色。

        为什么这样解决:首先,这样没有连续的红色结点。其次grandfather为黑色结点,使得下面两颗子树的路径都增加了一个黑色节点,让parent和uncle变黑,是得两子树路径黑色结点树没变。

         情况二:uncle不存在或者uncle为黑色(此时:cur红,parent红,grandfather黑,uncle不存在/黑)

说明uncle不存在或者为黑的情况:

        uncle不存在,说明cur就是新插入的结点。如果不是新插入结点,cur的孩子结点中一定会有黑色结点,因为cur为红色。这样就不满足性质,每条路径黑色结点数相同。

        uncle为黑,说明cur一定不是新增结点。如果是新增结点,parent一定是黑色。cur肯定是黑色变色成红色的。

        此时变色已经不能解决问题,可能你会想到parent,uncle,grandfather全变黑,这个时候parent,uncle,grandfather所在路径比其它路径黑色结点数多1,不符合性质。

        需要通过旋转加变色来解决

        下面也有两种情况:

        1.parent为grandfather右节点,cur为parent的右节点。

        此时需要进行左单旋,然后将旋转后的parent变黑和grandfather变红(同样使得黑色节点数没变)。

        parent为grandfather左节点,cur为parent的左节点。

          此时需要进行右单旋,然后将旋转后的parent变黑和grandfather变红(同样使得黑色节点数没变)。

         2.parent为grandfather右节点,cur为parent的左节点。

        此时需要进行右左双旋,然后将旋转后的cur变黑和grandfather变红。

         parent为grandfather左节点,cur为parent的右节点。

        此时需要进行左右双旋,然后将旋转后的cur变黑和grandfather变红。

通过上图,我们可以将双旋实现成两个单旋,一个单旋之后,交换一下cur和parent,就和第一种情况相同了,再通过一个单旋,在变色即可。

while (parent&&parent->_col == RED){
	//此时肯定有grandfather,并且一定是黑色
	Node *grandfather = parent->_parent;

	//调整主要看uncle,下面判断uncle在哪边
	if (grandfather->_left == parent){//如果父亲在左边,
		Node *uncle = grandfather->_right;//叔叔就在右边
		//情况1:叔叔存在且为红色,变色就好了
		if (uncle&&uncle->_col == RED){
			parent->_col = BLACK;
			uncle->_col = BLACK;
			grandfather->_col = RED;

			//继续往上更新
			cur = grandfather;
			parent = cur->_parent;
		}
		//情况2:uncle不存在或者uncle为黑色,旋转+变色
		//两种情况,cur在parent的右边,左右双旋,cur在parent的右边,右单旋。
		else{
			//parent在grandfather的左边,如果cur在parent的右边,左右双旋,这里是先左单旋再右单旋
			if (cur == parent->_right){
				SigelLeft(parent);//parent成为cur的儿子了
				swap(cur, parent);//换回来,方便后面变色
			}
			//后面就都是cur在parent的右边,只有右单旋就好了
			SigelRight(grandfather);

			//变色处理,画图就理解怎么变色了
			grandfather->_col = RED;
			parent->_col = BLACK;


		}

	}
	else//uncle在左边
	{
		Node *uncle = grandfather->_left;
		//和上面一样,只是方向不一样
		if (uncle&&uncle->_col == RED){
			grandfather->_col = RED;
			parent->_col = BLACK;
			uncle->_col = BLACK;

			cur = grandfather;
			parent = cur->_parent;
		}
		else{
			if (cur == parent->_left){
				SigelRight(parent);
				swap(cur, parent);
			}
			SigelLeft(grandfather);

			//变色
			parent->_col = BLACK;
			grandfather->_col = RED;
		}
    }

三.总代码

#ifndef __BRTREE_H__
#define __BRTREE_H__

#include<iostream>
#include<algorithm>
using namespace std;

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

//结点
template<class K,class V>
struct BRTreeNode
{
	BRTreeNode(const pair<K,V>& kv)
	:_left(nullptr)
	, _right(nullptr)
	, _parent(nullptr)
	, _kv(kv)
	, _col(RED)//新增结点的颜色为红色
	{}

	BRTreeNode *_left;
	BRTreeNode *_right;
	BRTreeNode *_parent;

	pair<K, V> _kv;
	Color _col;//结点颜色
};


template<class K,class V>
class BRTree
{
	typedef BRTreeNode<K,V> Node;
public:
	bool insert(const pair<K, V>& kv){
		//空结点,直接生成后,更新头节点。
		if (_root == nullptr){
			_root = new Node(kv);
			_root->_col = BLACK;
			return true;
		}
		Node *cur = _root;
		Node *parent = nullptr;
		while (cur){
			if (kv.first > cur->_kv.first){
				parent = cur;
				cur = cur->_right;
			}
			else if (kv.first < cur->_kv.first){
				parent = cur;
				cur = cur->_left;
			}
			else{
				return false;
			}
		}

		cur = new Node(kv);
		cur->_parent = parent;
		if (parent->_kv.first>cur->_kv.first){
			parent->_left = cur;
		}
		else{
			parent->_right = cur;
		}

		while (parent&&parent->_col == RED){
			//此时肯定有grandfather,并且一定是黑色
			Node *grandfather = parent->_parent;
			//调整主要看uncle,下面判断uncle在哪边
			if (grandfather->_left == parent){//如果父亲在左边,
				Node *uncle = grandfather->_right;//叔叔就在右边
				//情况1:叔叔存在且为红色,变色就好了
				if (uncle&&uncle->_col == RED){
					parent->_col = BLACK;
					uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续往上更新
					cur = grandfather;
					parent = cur->_parent;
				}
				//情况2:uncle不存在或者uncle为黑色,旋转+变色
				//两种情况,cur在parent的右边,左右双旋,cur在parent的右边,右单旋。
				else{
					//parent在grandfather的左边,如果cur在parent的右边,左右双旋,这里是先左单旋再右单旋
					if (cur == parent->_right){
						SigelLeft(parent);//parent成为cur的儿子了
						swap(cur, parent);//换回来,方便后面变色
					}
					//后面就都是cur在parent的右边,只有右单旋就好了
					SigelRight(grandfather);

					//变色处理,画图就理解怎么变色了
					grandfather->_col = RED;
					parent->_col = BLACK;


				}

			}
			else//uncle在左边
			{
				Node *uncle = grandfather->_left;
				//和上面一样,只是方向不一样
				if (uncle&&uncle->_col == RED){
					grandfather->_col = RED;
					parent->_col = BLACK;
					uncle->_col = BLACK;

					cur = grandfather;
					parent = cur->_parent;
				}
				else{
					if (cur == parent->_left){
						SigelRight(parent);
						swap(cur, parent);
					}
					SigelLeft(grandfather);

					//变色
					parent->_col = BLACK;
					grandfather->_col = RED;
				}

			}


		}

		_root->_col = BLACK;//防止变色将根节点变成红色
		return true;

	}

private:
	void SigelLeft(Node *parent){
		Node *subr = parent->_right;
		Node *subrl = subr->_left;

		Node *pparent = parent->_parent;
		parent->_right = subrl;
		if (subrl){//subrl可能为空
			subrl->_parent = parent;
		}

		subr->_left = parent;
		parent->_parent = subr;

		if (pparent == nullptr){//根节点
			subr->_parent = nullptr;
			_root = subr;
		}
		else{//子树
			subr->_parent = pparent;
			if (pparent->_kv.first < subr->_kv.first){
				
				pparent->_right = subr;
			}
			else{
				pparent->_left = subr;
			}

		}
	}
	void SigelRight(Node *parent){
		Node *subl = parent->_left;
		Node *sublr = subl->_right;

		Node *pparent = parent->_parent;

		parent->_left = sublr;
		if (sublr){
			sublr->_parent = parent;
		}

		subl->_right = parent;
		parent->_parent = subl;

		if (pparent == nullptr){
			subl->_parent = nullptr;
			_root = subl;
		}
		else{
			subl->_parent = pparent;

			if (pparent->_kv.first < subl->_kv.first){
				pparent->_right = subl;
			}
			else{
				pparent->_left = subl;
			}
		}


	}


private:
	Node *_root = nullptr;

};


#endif

四.红黑树验证

  • 验证是否为搜索树

中序遍历皆可。

	void _InOrder(Node *root){
		if (root){
			_InOrder(root->_left);
			cout << root->_kv.first << ":" << root->_kv.second << " ";
			_InOrder(root->_right);
		}
	}
    void InOrder(){
		_InOrder(_root);
		cout << endl;
	}

验证是否满足红黑树性质。

bool _IsValidRBTree(Node* pRoot, size_t k, const size_t blackCount)
	{
		//走到null之后,判断k和black是否相等
		if (nullptr == pRoot)
		{
			if (k != blackCount)
			{
				cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;
				return false;
			}
			return true;
		}
		// 统计黑色节点的个数
		if (BLACK == pRoot->_col)
			k++;
		// 检测当前节点与其双亲是否都为红色
		Node* pParent = pRoot->_parent;
		if (pParent && RED == pParent->_col && RED == pRoot->_col)
		{
			cout << "违反性质三:没有连在一起的红色节点" << endl;
			return false;
		}
		return _IsValidRBTree(pRoot->_left, k, blackCount) &&
			_IsValidRBTree(pRoot->_right, k, blackCount);
	}
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);
	}

以上是关于红黑树介绍和结点的插入的主要内容,如果未能解决你的问题,请参考以下文章

C++之红黑树

红黑树的增加(插入)和删除

《黑马程序员》— 红黑树

红黑树介绍

数据结构之红黑树

红黑树