红黑树封装map和set

文章详细介绍了如何利用模板区分map和set的红黑树实现,包括红黑树节点的定义、迭代器的begin()和end()方法、自增自减操作以及set和map的迭代器特性和插入操作。还探讨了红黑树的平衡性和高度计算。
摘要由CSDN通过智能技术生成

一、利用模板区分map和set

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

// 红黑树节点的定义
// T是值的类型: 如果是map,则为pair<K, V>; 如果是set,则为k
template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;	// 节点的左孩子
	RBTreeNode<T>* _right;	// 节点的右孩子
	RBTreeNode<T>* _parent;	// 节点的双亲(红黑树需要旋转,为了实现简单给出该字段)

	T _data;	// 节点的值
	Colour _col;	// 节点的颜色

	RBTreeNode(const T& data)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		, _data(data)//data可能是K也可能是<K.V>
		,_col(RED)
	{}
};

由于map和set的valuetype不同,我们将红黑树的模板参数改为T,T代表的就是valuetype,来区别set和value的数据类型。

二、set和map的迭代器

2.1红黑树的begin()和end()

STL库中源码是采用下图结构,用了应该header节点,迭代器begin()可以指向header的左,迭代器end()指向header。
在这里插入图片描述
本文采用的是无头节点进行封装,用nullptr作为end()。

	iterator begin()//找到最左节点的迭代器就是begin()
	{
		Node* leftMin = _root;
		while (leftMin && leftMin->_left)
		{
			leftMin = leftMin->_left;
		}

		return iterator(leftMin);
	}

	iterator end()
	{
		return iterator(nullptr);
	}

	//重载begin和end函数,便于const迭代器使用
	const_iterator begin() const//找到最左节点的迭代器就是begin()
	{
		Node* leftMin = _root;
		while (leftMin && leftMin->_left)
		{
			leftMin = leftMin->_left;
		}

		return const_iterator(leftMin);
	}

	const_iterator end() const
	{
		return const_iterator(nullptr);
	}

这里begin()迭代器返回的就是最左节点,end()迭代器我们使用nullptr,重载const_iterator是为了set和map的const对象使用

2.2 operator++()

这里重载++分俩种情况,一种是右树存在,一种是右树不存在。

  1. 右树存在的话,++就要找到比_node大的最小的数,也就是_node的右子树的最左边的数。
  2. 右树不存在的话,说明要往parent找,如果_node在parent左边,那下一个比_node大的最小数就是parent。
    如果_node在parent右边,说明_node比parent大,就需要继续往上找,找到孩子是父亲左的那个父亲节点,就是下一个要访问的节点。
    因为_node在parent左边,parent肯定比_node大,这个parent就是比_node大的最小的数。
    总结就是遍历顺序:左子树 根 右子树

	Self& operator++()//左子树 根 右子树
	{
		if (_node->_right)//右树存在
		{
			//右树的最左节点(最小节点)
			Node* subleft = _node->_right;
			while (subleft->_left)
			{
				subleft = subleft->_left;
			}

			_node = subleft;
		}
		else//右树不存在
		{
			Node* cur = _node;
			Node* parent = cur->_parent;
			//找孩子是父亲左的那个祖先节点,就是下一个要访问的节点
			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = parent->_parent;
			}
			_node = parent;
		}

		return *this;
	}

2.3 operator–()

同理,–也是分俩种情况,_node左树存在和左树不存在

  1. _node左树存在,我们就找到左树的最右边的节点
  2. _node左树不存在,就找到孩子是父亲右的那一个节点,返回父亲,走到nullptr就是遍历完成。
	Self& operator--()
	{
		if (_node->_left)//左数存在
		{
			//左数的最右节点
			Node* subright = _node->_left;
			while (subright->_right)
			{
				subright = subright->_right;
			}
			_node = subright;
		}
		else
		{
			// 孩子是父亲的右的那个节点
			Node* cur = _node;
			Node* parent = cur->_parent;

			while (parent && cur == parent->_left)
			{
				cur = parent;
				parent = parent->_parent;

			}
			_node = parent;
		}
		return *this;
	}

三、set迭代器

set的value是不可以修改的,所以它的底层将普通迭代器和const迭代器全部封装成const_iterator。

typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;

//set的普通迭代器和const迭代器其中都是同一个类型,所以我们只需要const迭代器的begin和end
//对应的,我们要在红黑树当中补齐const_iterator迭代器。
const_iterator begin() const
{
	return _t.begin();
}

const_iterator end() const
{
	return _t.end();
}

四、map迭代器

map的key不能修改,但是value是可以修改的,这里STL用了一种很巧妙的方法,就是在利用红黑树封装map的时候我们对传入的pair的K用const进行修饰,这样,我们就可以保证K不被修改,而value是可以被修改的。

RBTree<K, pair<const K, V>, MapKeyOfT> _t;
//这里俩个迭代器中都将K进行const再传入,这样就不能修改key,但可以修改value
typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;
iterator begin()
{
	return _t.begin();
}

iterator end()
{
	return _t.end();
}

const_iterator begin() const
{
	return _t.begin();
}

const_iterator end() const
{
	return _t.end();
}

insert函数用pair<iterator,bool>作为返回值类型,RBtree中insert函数,如果要插入的数据存在,就会返回已经存在迭代器的位置和false,如果不存在就会返回新插入节点的位置和true。
重载[]:借助了insert函数,我们将需要的key传入,就可以返回key位置的迭代器,再利用迭代器就可以找到key的value

V& operator[](const K& key)
{
	pair<iterator, bool> ret = insert(make_pair(key, V()));
	return ret.first->second;
}
//这里的迭代器就是普通迭代器,不需要像set再用一个ret来接收
pair<iterator,bool> insert(const pair<K, V>& kv)
{
	return _t.Insert(kv);
}

五、用普通迭代器构造const迭代器

set中的inset函数:
由于set的iterator是const迭代器,所以我们不能直接用rbtree的Insert进行返回,我们要先利用一个普通迭代器来接收它,再利用普通迭代器构造const迭代器的构造函数,返回set中的insert函数。

//这里的iterator是RBTree::const_iterator
//用普通迭代器先接收,再用普通迭代器构造const迭代器const_iterator
pair<iterator,bool> insert(const K& key)
{
	// pair<RBTree::iterator, bool>//这里的iterator是RBTree中的普通迭代器
	pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);
	return pair<iterator, bool>(ret.first, ret.second);
}
__TreeIterator(const Iterator& it)
	:_node(it._node)
{}

在struct __TreeIterator中增加了这个拷贝构造函数
当Self中的Ptr和Ref被实例化成T和T&的时候,我们这个函数就是普通的拷贝构造
当Self中的Ptr和Ref被实例化成const T
和const T&的时候,我们这个函数就是用普通迭代器构造const迭代器的构造,因为typedef __TreeIterator<T, T*, T&> Iterator;这里的Iterator永远是一个普通迭代器,作为构造函数的参数类型。

struct __TreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __TreeIterator<T, Ptr, Ref> Self;
	//这里的迭代器直接用T*和T&传入,也就是说不管实例化出来的是const迭代器还是普通迭代器,这里都是普通迭代器,用来下面的构造函数传参
	typedef __TreeIterator<T, T*, T&> Iterator;
	//这个类被实例化成const迭代器时,这个函数是一个构造,支持普通迭代器构造const迭代器
	//这个类被实例化成普通迭代器,这个函数就是一个拷贝构造
	__TreeIterator(const Iterator& it)//这里的Iterator是普通迭代器
		:_node(it._node)
	{}
	
	Node* _node;
	__TreeIterator(Node* node)
		:_node(node)
	{}

六、利用仿函数类进行比较大小

set仿函数类:

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

map仿函数类:

struct MapKeyOfT
{
	//这个函数是重载括号运算符,返回key,便于插入的时候进行比较key再插入
	const K& operator()(const pair<K, V>& kv)
	{
		return kv.first;
	}
};

这里仿函数类重载了(),set的data就是key,主要是给map使用,map只要把kv的first返回即可,就是key。

七、源码

7.1 RBTree

// set->RBTree<K, K, SetKeyOfT> _t;
// map->RBTree<K, pair<K, V>, MapKeyOfT> _t;
template<class K, class T,class KeyOfT>
//K还有什么用呢?用来作在find函数中的参数类型
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	//同一个类模板,传的不同的参数实例化出的不同类型
	typedef __TreeIterator<T, T*, T&> iterator;
	typedef __TreeIterator<T, const T*,const T&> const_iterator;

	//查看节点是否存在,利用KeyOfT 伪函数类
	Node* Find(const K& key)
	{
		Node* cur = _root;
		KeyOfT kot;
		while (cur)
		{
			if (kot(cur->_data) < key)
			{
				cur = cur->_right;
			}
			else if(kot(cur->_data) > key)
			{
				cur = cur->_left;
			}
			else
			{
				return key;
			}
		}
		return nullptr;
	}

	pair<iterator,bool> Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return make_pair(iterator(_root), true);
		}

		Node* parent = nullptr;
		Node* cur = _root;
		
		//找到要插入节点的parent,如果节点已经存在,返回已经存在节点的迭代器,和false
		KeyOfT kot;//利用KeyOfT类中的()重载找到key
		while (cur)
		{
			if (kot(cur->_data) < kot(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (kot(cur->_data) > kot(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return make_pair(iterator(cur), false);//要插入的数据已经存在,返回已经存在的数据迭代器和false
			}
		}

		cur = new Node(data);
		cur->_col = RED;
		//根据新节点key的大小插入新节点
		Node* newnode = cur;
		if (kot(parent->_data) < kot(data))
		{
			parent->_right = newnode;
		}
		else
		{
			parent->_left = newnode;
		}
		newnode->_parent = parent;

		//以上完成插入操作
		//下面完成红黑树规则
		while (parent && parent->_col == RED)
		{
			// 注意:grandfather一定存在
			// 因为parent存在,且不是黑色节点,则parent一定不是根,则其一定有双亲
			Node* grandfather = parent->_parent;
			// 先讨论左侧情况
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;
				// 情况一:叔叔节点存在,且为红
				if (uncle && uncle->_col == RED)
				{
					//变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;
					//继续向上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else// 情况二:叔叔节点不存在,或者叔叔节点存在且为黑
				{
					//     g
					//   p
					// c
					if (cur == parent->_left)//左左
					{
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else//左右
					{
						//     g
						//   p
						//		c
						RotateL(parent);
						RotateR(grandfather);

						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;//
				}
			}
			// 再讨论右侧情况
			else//parent == grandfather->_right
			{
				Node* uncle = grandfather->_left;
				//情况一:叔叔节点存在,且为红
				if (uncle && uncle->_col == RED)
				{
					//变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续向上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				// 情况二:叔叔节点不存在,或者叔叔节点存在且为黑
				else
				{
					if (cur == parent->_right)//右右
					{
						// g
						//	  p
						//       c
						RotateL(grandfather);
						grandfather->_col = RED;
						parent->_col = BLACK;
						}
					else//右左
					{
						// g
						//	  p
						// c
						RotateR(parent);
						RotateL(grandfather);

						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
		}

		_root->_col = BLACK;
		return make_pair(iterator(newnode), true);;
	}

	void RotateL(Node* parent)
	{
		++_rotateCount;

		Node* cur = parent->_right;
		Node* curleft = cur->_left;

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

		cur->_left = parent;
		Node* ppnode = parent->_parent;
		parent->_parent = cur;

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

			cur->_parent = ppnode;
		}
	}

	void RotateR(Node* parent)
	{
		++_rotateCount;

		Node* cur = parent->_left;
		Node* curright = cur->_right;

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

		Node* ppnode = parent->_parent;
		cur->_right = parent;
		parent->_parent = cur;

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

			cur->_parent = ppnode;
		}
	}

	//blacknum用来记录每条路径黑色节点的个数,这里不要传引用,这里才能记录每条路径的数量
	bool CheckColour(Node* root, int blacknum, int benchmark)
	{
		//走到null之后,判断blacknum和benchmark是否相等
		if (root == nullptr)
		{
			if (blacknum != benchmark)//root等于空说明已经到路径的尾部,如果和基准值不一致,说明不是红黑树(每条线路的黑色节点相等)
				return false;

			return true;
		}

		if (root->_col == BLACK)
		{
			++blacknum;//记录黑色节点的个数
		}

		//检测当前节点与其双亲是否都为红色
		//当前节点为颜色为红色且父亲存在且父亲颜色为红色,说明出现连续红色节点,返回false
		if (root->_col == RED && root->_parent && root->_parent->_col == RED)
		{
			cout << root->_kv.first << "出现连续的红色节点,违反性质三" << endl;
			return false;
		}

		return CheckColour(root->_left, blacknum, benchmark)
			&& CheckColour(root->_right, blacknum, benchmark);
	}

	bool IsBalance()
	{
		return IsBalance(_root);
	}

	bool IsBalance(Node* root)
	{
		// 空树也是红黑树
		if (root == nullptr)
			return true;

		// 检测根节点是否满足情况,根节点不是黑色节点,直接返回false
		if (root->_col != BLACK)
			return false;

		//设置一个基准值,获取任意一条路径中黑色节点的个数
		//直接算最左边的路径方便,不管基准值是不是正确的后面只要有一条路径和基准值不相等,说明不满足红黑树条件
		int benchmark = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_col == BLACK)
				++benchmark;

			cur = cur->_left;
		}
		// 检测是否满足红黑树的性质
		return CheckColour(root, 0, benchmark);//检查每条路径的黑色节点数量是否相等
	}

	int Height()
	{
		return Height(_root);
	}

	int Height(Node* root)
	{
		if (root == nullptr)
			return 0;
		
		int leftHeight = Height(root->_left);
		int rightHeight = Height(root->_right);

		return leftHeight > rightHeight ? leftHeight+1 : rightHeight+1;
	}

private:
	Node* _root = nullptr;
public:
	int _rotateCount = 0;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值