红黑树与map、set的封装

简单了解红黑树

上一篇博客我们了解了AVL树的插入实现,其插入搜索时间复杂度是O(logN)级别的。而红黑树也是一颗平衡搜索二叉树,但是与AVL树不同的地方在于,红黑树的平衡策略是近似平衡,而非AVL树的完全平衡。

可能有小伙伴就会问了,不是越平衡效率越高吗?为什么更多地方采用的是红黑树而非AVL树呢?这里就要从红黑树近似平衡的特点说起了,由于平衡限制的并不那么严格,所以红黑树通过旋转调整平衡的次数相比AVL树就更少了,也就是说红黑树的插入会比AVL更快。而红黑树通过一系列规则,将根节点到叶子结点的最长路径限制在了最短路径的两倍以内,所以说其搜索时的最坏情况也不会差太多。因此红黑树使用范围非常广泛,当然并不是说AVL树就比红黑树要差了,只是红黑树在综合情况下发挥更好,而AVL树在搜索上的优势是显而易见的。

红黑树是怎么做到近似平衡的

在介绍如何达成近似平衡前,我们先来了解红黑树的五个性质。对于一颗红黑树,它必须具备以下五点性质:

1.红黑树的每个节点不是红色的就是黑色的

2.红黑树的根节点必须是黑色的

3.红色节点的两个孩子一定是黑色的

4.对于红黑树的每个节点,从该节点到其所有后代叶子结点(空节点才是叶子节点)的路径上,均包含相同数目的黑色节点

5.对于每个叶子节点(空节点)颜色都是黑的

对于下面这样一颗树,就满足了红黑树的所有性质。其所有节点都是红色与黑色的。结点0作为根节点是黑色的。

红黑树是一颗近似平衡的搜索二叉树,而其近似平衡的原理就藏在这五个性质里。

根据性质3,我们知道红黑树中没有连续的红色节点。而根据性质4,红黑树从根节点到叶子节点的黑色节点数量是固定的。那么结合这两个性质,我们可以想象,当一条路径上节点全为黑色节点时,路径最短。而要使得路径最长,就可以在黑色节点之间插入红色节点,这样红色节点不会连续,路径也增长了。如下图所示,左侧为最短路径,右侧为最长路径。为了方便,这里将NULL节点忽略。

 那么我们可以看出,在红黑树的任意一条路径上,最长的路径也不会超过最短路径的两倍。而对于红黑树而言,这样就达成了左右子树高度差在一定范围内的结果。而这样的性质,被我们称作为近似平衡。

而我们要使红黑树达到近似平衡的效果,只需要控制好这五个性质即可。

红黑树的插入

红黑树的框架

在了解红黑树的插入前,我们先将红黑树及其节点的框架(只提供成员函数,不提供函数过程)搭建好。

首先,红黑树的节点需要保存父指针、孩子指针、数据和自己的颜色:

enum Color//使用枚举体记录颜色
{
	red,
	black
};

template<class T>
class RBTreeNode
{
public:
	RBTreeNode(const T& data = T())
		:_data(data)
		,_color(red)//默认设置为红色
		,_parent(nullptr)
		,_left(nullptr)
		,_right(nullptr)
	{}
	RBTreeNode* _left;//左孩子指针
	RBTreeNode* _parent;//父节点指针
	RBTreeNode* _right;//右孩子指针
	Color _color;//颜色
	T _data;//数据
};

节点的颜色我们默认设置为红色,因为插入节点时,如果默认为黑色,那么这个新插入的节点将会影响到这条路径上所有节点的最简路径黑节点的数量,也就可能会让很多节点违反性质4,而违反性质要做出的调整的代价也就很大。而我们将新插入节点的颜色设置为红色,由于搜索二叉树的新节点是没有孩子节点的(NULL节点不算),所以插入红节点不会影响黑色节点的数量,只有在其父亲为红色时才会违反 “红色节点的两个孩子一定是黑色的”这一性质,而且影响范围小,调整的代价也小。所以新节点默认为红色。

接下来是红黑树的迭代器的基本框架:

template<class T, class Ref, class Ptr>
struct RBTreeIterator
{
public:
	typedef RBTreeNode<T> Node;
	RBTreeIterator(Node* pNode)
		: _pNode(pNode)
	{}
    //...
private:
    Node* _pNode;//迭代器所指向的节点的指针
}

而红黑树本身就比较简单了,只需要保存自己的根节点:

template<class K, class T, class KeyOfT>
class RBTree
{
public:
	typedef RBTreeNode<T> Node;
    //...
private:
    Node* _root;
}

至于其模板参数K,T,KeyOfT的作用,我们在后续封装map和set时会讲到。

插入节点

若在一棵空树中插入,由于红黑树的根节点必须是黑色的,所以我们直接进行插入即可:

pair<iterator, bool> insert(const T& data)
{
if (_root == nullptr)
{
	_root = new Node(data);
	_root->_color = black;
	return make_pair(iterator(_root),true);
}
//其他情况...
}

注意红黑树的性质:根节点必须为黑,而我们默认设置新节点的颜色为红色,所以这里我们要将新插入节点也就是根节点的颜色变为黑色。 

 而插入函数的返回值是一个键值对pair,里面存放了新插入节点或者已存在节点的迭代器,以及是否插入成功。这里我们插入成功,并且返回一个用根节点构造的迭代器。

如果树不为空,那么我们就要在红黑树中通过搜索树左子树比当前节点小,右子树比当前节点大的性质来查找出插入的位置:

		Node* child = _root;
		Node* parent = nullptr;
		while (child)
		{
			if (child->_data > data)
			{
				parent = child;
				child = child->_left;
			}
			else if (child->_data < data)
			{
				parent = child;
				child = child->_right;
			}
			else
			{
				return make_pair(iterator(child), false);
			}
		}		
        Node* newnode = new Node(data);
		Node* ret = newnode;//在这里记录新插入的节点指针,方便构造返回时的迭代器
		if (parent->_data > data)
		{
			parent->_left = newnode;
			newnode->_parent = parent;
		}
		else
		{
			parent->_right = newnode;
			newnode->_parent = parent;
		}

上面就是找到插入位置的逻辑,如果在红黑树中找到了这个值,那么久返回保存该值的节点构造的迭代器与false,表示插入失败。

但是上面还有一个问题:在map和set里,set确实是用保存的data进行比较,因为它只存储了key,而key就是这里的data。但是在map里,它的data实际上是一个键值对pair,而我们需要进行比较的实际上是pair里的第一个成员,也就是key。而上面的过程只用了data进行比较。而pair的比较规则是,先比较第一个成员的大小,如果相同,再比较第二个成员的大小,只有当两个成员都相同时,才会判定pair相同。很显然,map的比较方式是只比较K(key)的大小,而不会去比较V(value)的大小。那么,怎么做才能让map和set共用一份代码,而不是写两个插入函数呢?

我们知道红黑树的模板参数中有一个模板类型KeyOfT,它的作用是提供一个仿函数。这个仿函数的作用就是将data中的key提取出来。对于set而言,仿函数直接返回key即可,而对于map而言,仿函数需要从pair中提取key出来进行返回:

		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};

        struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};

这样,在红黑树的模板类型中填入不同的KeyOfT类型,就可以正确的取到key进行比较了。

所以上面的查找插入位置过程也需要进行修改:

		KeyOfT kot;
		Node* child = _root;
		Node* parent = nullptr;
		while (child)
		{
			if (kot(child->_data) > kot(data))
			{
				parent = child;
				child = child->_left;
			}
			else if (kot(child->_data) < kot(data))
			{
				parent = child;
				child = child->_right;
			}
			else
			{
				return make_pair(iterator(child), false);
			}
		}		
        Node* newnode = new Node(data);
		Node* ret = newnode;//在这里记录新插入的节点指针,方便构造返回时的迭代器
		if (kot(parent->_data) > kot(data))
		{
			parent->_left = newnode;
			newnode->_parent = parent;
		}
		else
		{
			parent->_right = newnode;
			newnode->_parent = parent;
		}

 红黑树的调整颜色与旋转

注:如果不了解左单旋、右单旋、双旋的过程,可以先阅读前一篇博客《AVL树的插入》AVL树的插入_凉夏y的博客-CSDN博客了解,后续对于的旋转操作细节不进行赘述。

上面,我们的往红黑树里插入新节点的过程就算是结束了,但是别忘了,插入新的节点可能会违反红黑树的性质,这时我们就需要对红黑树进行调整,将插入新节点后的树恢复成红黑树。

从前文我们直到,插入的新节点默认为红色,所以只有其父节点为红色时才会违反性质。如果父节点为黑色或者不存在,就不需要进行调整。

而如果父节点为红色,我们分为下面几种情况进行讨论:

叔叔节点(也就是父节点的兄弟节点)存在且为红色

首先,父节点必定是插入前就存在的,所以祖父节点必定是黑色,否则就有连续的红色节点存在了,而且祖父节点必定不为空,因为如果祖父节点为空,那么插入前父节点就是根节点,而其颜色为红色,就违反了根节点必须是黑色的性质。所以这一种情况可以归结为下图:

 

对于这种情况,我们要让“红色节点的两个孩子一定是黑色的”这一性质恢复其实很简单,只需要将父节点与叔叔节点变为黑色,祖父节点变为红色即可。

 这样,在这个子树中就没有连续的红色节点了,而且这样变色,也不会改变任意简单路径上的黑色节点总数。但是这样变色还有一个问题,就是祖父节点变为了红色,那么如果祖父节点的父节点还是红色,就会导致仍然有连续的红色节点存在。所以在变色后,我们还需要继续向上调整,去判断祖父节点的父节点的颜色。

 下面是这种情况的简易代码:

		while (parent&&parent->_color == red)//如果父节点存在且为红色
		{
			Node* grandfather = parent->_parent;
			Node* uncle = nullptr;
			if (parent == grandfather->_left)//取父节点的兄弟节点为uncle
				uncle = grandfather->_right;
			else
				uncle = grandfather->_left;
			if (uncle&&uncle->_color==red)//如果叔叔节点存在且为红
			{
				uncle->_color=parent->_color = black;//变色
				grandfather->_color = red;

				newnode = grandfather;//继续向上调整
				parent = newnode->_parent;
			}
			else
			{
				//其他情况...
			}
		}
		_root->_color = black;
		return make_pair(iterator(ret), true);

我们看到了调整结束后,将根节点设置为了黑色,这是因为在调整颜色、旋转的过程中,根节点的颜色可能受到影响变为红色,这违反了性质“根节点必须是黑色”,所以我们在返回前将根节点的颜色修改为黑色。并且将根节点设置为黑色并不会违反“任意节点的简单路径上的黑色节点数量相同”这一性质(因为以根节点为起点的简单路径的黑色节点数量都增加了一个,依然保持相同),所以可以直接这样修改根节点的颜色保证根节点为黑色。

叔叔节点不存在

这种情况可以归结为下图:

 这里父节点的右子树一定是空树,因为父节点是红色,其孩子节点必须是黑色,否则会产生连续的红色节点。而对于祖父节点来说,其右子树的简单路径上黑色节点为一,左子树也必须为一,而如果右子树存在,左子树上黑色节点数量就至少为二,也就违反了这个规则。所以右子树为空,也就是下面这种情况:

很明显,这里通过调整节点的颜色以及无法恢复性质了,所以需要进行右单旋进行调整。

调整之后,将父节点变为黑色,祖父节点变为红色:

这样这颗子树的简单路径上的黑色节点数量没有变化,并且不需要继续向上调整了。因为此时与上方连接的父节点变为了黑色,不会产生新的连续的红色节点了。

叔叔节点存在且为黑色

对于这种情况,可以看下图:

 

 此时,祖父节点的右子树简单路径上至少有两个黑色节点,而左子树的每个路径上也至少要有两个黑色节点,那么父节点的右子树必定存在,并且父节点的右孩子一定为黑。那么对于这个newnode,如果它是新插入节点,在插入之前,父节点的左孩子作为路径终点的黑色节点数为1,就违反了红黑树的性质,下图蓝色数字表示统计该条路径上的黑色节点数量。

通过上面的分析,很明显newnode不可能是新插入的节点,而是由于调整而转变为红色的。 

 所以树在newnode变为红色后的情况应该是下面这样的:

  对于这种情况,很明显也无法通过单纯的颜色调整让这个子树符合红黑树的所有特性。所以这时候要对祖父节点进行右单旋,得到下面的树:

 再将父节点变为黑色,祖父节点变为红色:

这样,就恢复了红黑树的性质,并且对路径的黑色节点数量没有影响,不需要继续向上调整。

到这里,我们可以发现,对于“叔叔节点存在且叔叔为黑色”与“叔叔节点不存在” 这两种情况所需要做的调整与颜色变化相同,所以我们可以将其归为同一类进行处理。

当然我们也注意到,上面的情况都假设在newnode是其父节点的左孩子的前提下。

那么,如果newnode是父节点的右孩子呢?

当叔叔不存在时,是这样的情况:

很显然,调整颜色也不能解决问题,所以这里需要对祖父节点进行左右双旋,得到下面这样的子树:

 

 然后,将祖父节点变为红色,newnode变为黑色:

这样,这颗子树的性质得到恢复,并且也不需要继续向上调整了。

而对于叔叔存在,newnode是父节点的右孩子的情况:

 进行左右双旋,得到:

 然后,将祖父节点变为红色,newnode变为黑色:

这样,就完成了旋转与调整,并且不需要向上调整。

 我们发现,当newnode是父节点的右孩子时,无论叔叔节点不存在或者叔叔节点存在且为黑,都是对祖父进行左右双旋再将祖父节点变为红色,newnode变为黑色以完成调整的。

所以我们得到四种情况:

1.父节点为祖父节点的左孩子,newnode为父节点的左孩子,叔叔节点不存在

2.父节点为祖父节点的左孩子,newnode为父节点的左孩子,叔叔节点为黑色

第一种和第二种的处理方式为对祖父进行右单旋,然后将父节点变为黑色,祖父节点变为红色

3.父节点为祖父节点的左孩子,newnode为父节点的右孩子,叔叔节点不存在

4.父节点为祖父节点的左孩子,newnode为父节点的右孩子,叔叔节点为黑色

第三种和第四种的处理方式为对祖父进行左右单旋,然后将祖父节点变为红色,newnode变为黑色

我们可以看到,上面的情况都是假定父节点是祖父节点的左孩子的情况下所展开讨论的。而对于父节点时祖父节点的右孩子,实际上与上述处理均为镜像对称关系,我们通过代码进行展示:

		while (parent&&parent->_color == red)
		{
			Node* grandfather = parent->_parent;
			Node* uncle = nullptr;
			if (parent == grandfather->_left)
				uncle = grandfather->_right;
			else
				uncle = grandfather->_left;
			if (uncle&&uncle->_color==red)//如果叔叔节点存在且为红色
			{
				uncle->_color=parent->_color = black;
				grandfather->_color = red;
				newnode = grandfather;
				parent = newnode->_parent;
			}
			else//叔叔节点不存在或者为黑色
			{

                //当父节点是祖父节点的左孩子且newnode是父节点的左孩子
				if (parent == grandfather->_left && newnode == parent->_left)
				{
					RotateR(grandfather);
					parent->_color = black;
					grandfather->_color = red;
				}

                //当父节点是祖父节点的右孩子且newnode是父节点的右孩子
				else if (parent == grandfather->_right && newnode == parent->_right)
				{
					RotateL(grandfather);
					parent->_color = black;
					grandfather->_color = red;
				}

                //当父节点是祖父节点的左孩子且newnode是父节点的右孩子
				else if (parent == grandfather->_left && newnode == parent->_right)
				{
					RotateL(parent);
					RotateR(grandfather);
					newnode->_color = black;
					grandfather->_color = red;
				}
                    
                //当父节点是祖父节点的左孩子且newnode是父节点的左孩子
				else if (parent == grandfather->_right && newnode == parent->_left)
				{
					RotateR(parent);
					RotateL(grandfather);
					newnode->_color = black;
					grandfather->_color = red;
				}
				break;//由于这四种情况均不需要继续向上调整,所以跳出循环
			}
		}

下面展示插入函数的所有代码:

	pair<iterator, bool> insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_color = black;
			return make_pair(iterator(_root),true);
		}
		KeyOfT kot;
		Node* child = _root;
		Node* parent = nullptr;
		while (child)
		{
			if (kot(child->_data) > kot(data))
			{
				parent = child;
				child = child->_left;
			}
			else if (kot(child->_data) < kot(data))
			{
				parent = child;
				child = child->_right;
			}
			else
			{
				return make_pair(iterator(child), false);
			}
		}
		Node* newnode = new Node(data);
		Node* ret = newnode;
		if (kot(parent->_data) > kot(data))
		{
			parent->_left = newnode;
			newnode->_parent = parent;
		}
		else
		{
			parent->_right = newnode;
			newnode->_parent = parent;
		}
		while (parent&&parent->_color == red)
		{
			Node* grandfather = parent->_parent;
			Node* uncle = nullptr;
			if (parent == grandfather->_left)
				uncle = grandfather->_right;
			else
				uncle = grandfather->_left;
			if (uncle&&uncle->_color==red)
			{
				uncle->_color=parent->_color = black;
				grandfather->_color = red;
				newnode = grandfather;
				parent = newnode->_parent;
			}
			else
			{
				if (parent == grandfather->_left && newnode == parent->_left)
				{
					RotateR(grandfather);
					parent->_color = black;
					grandfather->_color = red;
				}
				else if (parent == grandfather->_right && newnode == parent->_right)
				{
					RotateL(grandfather);
					parent->_color = black;
					grandfather->_color = red;
				}
				else if (parent == grandfather->_left && newnode == parent->_right)
				{
					RotateL(parent);
					RotateR(grandfather);
					newnode->_color = black;
					grandfather->_color = red;
				}
				else if (parent == grandfather->_right && newnode == parent->_left)
				{
					RotateR(parent);
					RotateL(grandfather);
					newnode->_color = black;
					grandfather->_color = red;
				}
				break;
			}
		}
		_root->_color = black;
		return make_pair(iterator(ret), true);
	}

 红黑树的迭代器

红黑树的迭代器代码实现如下:

template<class T>
class RBTreeNode
{
public:
	RBTreeNode(const T& data = T())
		:_data(data)
		,_color(red)
		,_parent(nullptr)
		,_left(nullptr)
		,_right(nullptr)
	{}
	RBTreeNode* _left;
	RBTreeNode* _parent;
	RBTreeNode* _right;
	Color _color;
	T _data;
};
template<class T, class Ref, class Ptr>
struct RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef RBTreeIterator<T, Ref, Ptr> Self;

	RBTreeIterator(Node* pNode)
		: _pNode(pNode)
	{}

	Ref operator*()
	{
		return _pNode->_data;
	}
	Ptr operator->()
	{
		return &(_pNode->_data);
	}

	Self& operator++()
	{
		if (_pNode->_right)
		{
			_pNode = _pNode->_right;
			while (_pNode->_left)
				_pNode = _pNode->_left;
			return *this;
		}
		Node* parent = _pNode->_parent;
		while (parent)
		{
			if (_pNode == parent->_left)
			{
				_pNode = parent;
				return *this;
			}
			_pNode = parent;
			parent = _pNode->_parent;
		}
		_pNode = nullptr;
		return *this;
	}
	Self operator++(int)
	{
		Self tmp(_pNode);
		++(*this);
		return tmp;
	}

	Self& operator--()
	{
		if (_pNode->_left)
		{
			_pNode = _pNode->_left;
			while (_pNode->_right)
				_pNode = _pNode->_right;
			return *this;
		}
		Node* parent = _pNode->_parent;
		while (parent)
		{
			if (_pNode == parent->_right)
			{
				_pNode = parent;
				return *this;
			}
			_pNode = parent;
			parent = _pNode->_parent;
		}
		_pNode = nullptr;
		return  *this;
	}
	Self operator--(int)
	{
		Self tmp(_pNode);
		--(*this);
		return tmp;
	}

	bool operator!=(const Self& s)const
	{
		return s._pNode != _pNode;
	}
	bool operator==(const Self& s)const
	{
		return s._pNode == _pNode;
	}

private:
	Node* _pNode;
};

其中,我们着重讲解++与--运算符的重载。

对于搜索树而言,其迭代器的遍历过程实际和中序遍历相同,我们用下面这棵树作为例子讲解:

 

 

对于上面的搜索树,其中序遍历很显然就是顺序:1 2 3 4 5 6,那么我们如何实现迭代器的++呢?

其实可以发现,对于一个节点,它的右子树的所有值都比它的值要大,而右子树的最左节点则是迭代器遍历的下一个节点。 例如4的下一个节点就是以6为根节点的子树的最左节点,也就是5。

而如果一个节点没有右子树,例如3,其下一个节点就需要找他的祖先节点,那么到底是哪个祖先呢?我们从3出发向上找,由于2比3小,所以不可能是下一个节点,而4比3大,我们就将这个节点作为迭代器的下一个节点。

而对于--,其实就是将这个过程镜像翻转以下即可。

红黑树的实现代码

这里使用代码+注释的方式进行介绍,由于上面介绍过了insert函数,这里不再赘述

template<class K, class T, class KeyOfT>
class RBTree
{
public:
	typedef RBTreeNode<T> Node;
	typedef RBTreeIterator<T, T&, T*> iterator;
	typedef RBTreeIterator<T, const T&, const T*> const_iterator;

	RBTree()
	{
		_root = nullptr;
	}

	RBTree(const RBTree<K,T,KeyOfT>& t)
	{
		_root = copy(t._root);//拷贝构造,调用私有的copy函数进行拷贝
	}

	RBTree<K, T, KeyOfT>& operator=(RBTree<K, T, KeyOfT> t)
	{
		swap(_root, t._root);//将作为临时变量的树的根节点指针与自己的交换,直接交换整个树
                             //而交换过去的树会随着临时变量在出作用域后被销毁    
		return *this;
	}

	iterator begin()//找树的最左节点
	{
		Node* ret = _root;
		while (ret&&ret->_left)
			ret = ret->_left;
		return iterator(ret);
	}

	iterator end()//这里使用nullptr作为end迭代器
	{
		return iterator(nullptr);
	}

	bool empty()const
	{
		return _root != nullptr;
	}

	void clear()
	{
		_clear(_root);//调用私有的子函数完成清空
		_root = nullptr;//最后将指针置空防止产生野指针
	}

	pair<iterator, bool> insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_color = black;
			return make_pair(iterator(_root),true);
		}
		KeyOfT kot;
		Node* child = _root;
		Node* parent = nullptr;
		while (child)
		{
			if (kot(child->_data) > kot(data))
			{
				parent = child;
				child = child->_left;
			}
			else if (kot(child->_data) < kot(data))
			{
				parent = child;
				child = child->_right;
			}
			else
			{
				return make_pair(iterator(child), false);
			}
		}
		Node* newnode = new Node(data);
		Node* ret = newnode;
		if (kot(parent->_data) > kot(data))
		{
			parent->_left = newnode;
			newnode->_parent = parent;
		}
		else
		{
			parent->_right = newnode;
			newnode->_parent = parent;
		}
		while (parent&&parent->_color == red)
		{
			Node* grandfather = parent->_parent;
			Node* uncle = nullptr;
			if (parent == grandfather->_left)
				uncle = grandfather->_right;
			else
				uncle = grandfather->_left;
			if (uncle&&uncle->_color==red)
			{
				uncle->_color=parent->_color = black;
				grandfather->_color = red;
				newnode = grandfather;
				parent = newnode->_parent;
			}
			else
			{
				if (parent == grandfather->_left && newnode == parent->_left)
				{
					RotateR(grandfather);
					parent->_color = black;
					grandfather->_color = red;
				}
				else if (parent == grandfather->_right && newnode == parent->_right)
				{
					RotateL(grandfather);
					parent->_color = black;
					grandfather->_color = red;
				}
				else if (parent == grandfather->_left && newnode == parent->_right)
				{
					RotateL(parent);
					RotateR(grandfather);
					newnode->_color = black;
					grandfather->_color = red;
				}
				else if (parent == grandfather->_right && newnode == parent->_left)
				{
					RotateR(parent);
					RotateL(grandfather);
					newnode->_color = black;
					grandfather->_color = red;
				}
				break;
			}
		}
		_root->_color = black;
		return make_pair(iterator(ret), true);
	}
		
	iterator find(const K& key)//利用搜索树的性质进行查找即可
	{
		KeyOfT kot;
		Node* cur = _root;
		while (cur)
		{
			if (kot(cur->_data) > key)
				cur = cur->_left;
			else if (kot(cur->_data) < key)
				cur = cur->_right;
			else
				return iterator(cur);
		}
		return end();
	}

	bool IsValidRBTRee()//可以使用这个函数进行测试红黑树是否满足其性质
	{
		if (_root && _root->_color == red)//如果根节点为红,则不满足性质
			return false;
		int banch = 0;//记录最左路径的黑色节点数量,作为基准值对比
		Node* node = _root;
		while (node)
		{
			if (node->_color == black)
				banch++;
			node = node->_left;
		}
		return _IsValidRBTRee(_root, banch, 0);//借助子函数递归进行判断
	}

	~RBTree()
	{
		clear();
	}
private:

	bool _IsValidRBTRee(Node* cur,int banch,int num)
	{
		if (cur == nullptr)//当cur为空,检测该路径的黑节点数量与基准值是否相同
		{
			if (banch != num)
			cout << "different blacknum" << endl;
			return banch == num;
		}
		if (cur->_color == red && cur->_parent->_color == red)//检测是否有相连的红色节点
		{
			cout << "connect red" << endl;
			return false;
		}
		if (cur->_color == black)//若cur为黑,计数器自增
			num++;
		return _IsValidRBTRee(cur->_left, banch, num) && _IsValidRBTRee(cur->_right, banch, num);
	}

	void _clear(Node* cur)//使用后序遍历的方法对树进行销毁
	{
		if (cur == nullptr)
			return;
		_clear(cur->_left);
		_clear(cur->_right);
		delete cur;
	}

	Node* copy(Node* cur)//深拷贝一个红黑树,注意保持所有的链接关系
	{
		if (cur == nullptr)
			return nullptr;
		Node* newnode = new Node(cur->_data);
		newnode->_color = cur->_color;
		newnode->_left = copy(cur->_left);
		newnode->_right = copy(cur->_right);
		if (newnode->_left)
			newnode->_left->_parent = newnode;
		if (newnode->_right)
			newnode->_right->_parent = newnode;
		return newnode;
	}

	// 左单旋
	void RotateL(Node* pParent)
	{
		Node* cur = pParent->_right;
		Node* curLeft = cur->_left;
		Node* parentParent = pParent->_parent;
		cur->_left = pParent;
		pParent->_parent = cur;
		pParent->_right = curLeft;
		if (curLeft)
			curLeft->_parent = pParent;
		if (parentParent)
		{
			cur->_parent = parentParent;
			if (parentParent->_left == pParent)
				parentParent->_left = cur;
			else
				parentParent->_right = cur;
		}
		else
		{
			cur->_parent = nullptr;
			_root = cur;
		}
	}

	// 右单旋
	void RotateR(Node* pParent)
	{
		Node* cur = pParent->_left;
		Node* curRight = cur->_right;
		Node* parentParent = pParent->_parent;
		cur->_right = pParent;
		pParent->_parent = cur; 
		pParent->_left = curRight;
		if (curRight)
			curRight->_parent = pParent;
		if (parentParent)
		{
			cur->_parent = parentParent;
			if (parentParent->_left == pParent)
				parentParent->_left = cur;
			else
				parentParent->_right = cur;
		}
		else
		{
			cur->_parent = nullptr;
			_root = cur;
		}
	}
private:
	Node* _root;
};

map和set的封装

map的封装

#pragma once
#include "RBTree.h"

namespace hsh//为了避免命名冲突,使用命名空间
{
	template<class K, class V>
	class map
	{
	public:
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
		typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator;
		pair<iterator, bool> insert(const pair<K, V>& kv)
		{
			return _t.insert(kv);
		}
		iterator find(const K& key)
		{
			return _t.find(key);
		}
		iterator begin()
		{
			return _t.begin();
		}
		iterator end()
		{
			return _t.end();
		}
		V& operator[](const K& key)
		{
			auto it = _t.insert(make_pair(key, V()));
			return it.fisrt->second;
		}
	private:
		RBTree<K, pair<K, V>, MapKeyOfT> _t;
	};
}

这里主要解释一下运算符 [ ] 的重载:我们知道 [ ] 在map里十分方便,它可以用来插入或者修改元素,那么它是怎么做到的呢?首先我们将括号内的key值进行插入,而插入的pair的V使用默认构造进行创建,这样就得到了一个pair<key,V()>的键值对。如果key已经存在,也不会产生额外的影响。而我们将插入函数的返回值接收,我们知道,插入函数的返回值返回pair<iterator,bool>,而iterator就是插入节点的迭代器。我们返回迭代器的second的引用,也就是val的引用,这样对 [ ] 的返回值进行赋值就可以修改val了。

set的封装

#pragma once
#include "RBTree.h"
namespace hsh
{
	template<class K>
	class set
	{
	public:
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
		typedef typename RBTree<K, K, SetKeyOfT>::iterator iterator;
		pair<iterator, bool> insert(const K& key)
		{
			return _t.insert(key);
		}
		iterator begin()
		{
			return _t.begin();
		}
		iterator end()
		{
			return _t.end();
		}
		iterator find(const K& key)
		{
			return _t.find(key);
		}
		void print()
		{
			_t.printfTree();
		}
	private:
		RBTree<K, K, SetKeyOfT> _t;
	};
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值