【C++篇】STL的关联容器:map和set(下篇):用一颗红黑树同时封装出map和set


一、红黑树源码

本文用于封装的红黑树代码(无erase接口):

enum Color
{
	BLACK,
	RED
};

template<class K, class V>
struct BRTreeNode
{
	BRTreeNode<K, V>* _left;
	BRTreeNode<K, V>* _right;
	BRTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	Color _col;

	BRTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_kv(kv)
		,_col(RED)
	{ }
};

template<class K, class V>
class BRTree
{
	typedef BRTreeNode<K, V> Node;
public:
	// 在红黑树中插入值为kv的节点
	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 (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}

		cur = new Node(kv);
		//新增在左
		if (parent->_kv.first > kv.first)
		{
			parent->_left = cur;

		}
		else//新增在右
		{
			parent->_right = cur;
		}
		cur->_parent = parent;

		//更新颜色
		//检查是否需要更新颜色,若parent为黑则无需更新
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			Node* uncle = nullptr;
			if (grandfather->_left == parent)
			{
				uncle = grandfather->_right;
			}
			else
			{
				uncle = grandfather->_left;
			}

			if (uncle && uncle->_col == RED)//uncle存在且为红——变色
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				if (grandfather->_parent)
				{
					if (grandfather->_col == BLACK)
					{
						break;
					}
					else
					{
						cur = grandfather;
						parent = cur->_parent;
					}
				}
				else
				{
					grandfather->_col = BLACK;
					break;
				}
			}
			else//uncle不存在或uncle存在且为黑——旋转+变色
			{
				//判断何种情况,该用何种旋转
				if (grandfather->_right == parent)
				{
					if (cur == parent->_right)
					{
						//  g
						//     p
						//		  c
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//  g
						//     p
						//  c
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
				}
				else
				{
					if (cur == parent->_left)
					{
						//        g
						//     p
						//  c
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//    g
						//  p
						//    c
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
				}
				break;
			}
		}
		_root->_col = BLACK;
		return true;
	}

二、控制红黑树的模板参数

我们知道,map是KV模型,键值是pair<Key, Value>;而set是K模型,键值是Key
那如何使得一颗KV模型的红黑树可以同时适配这两种模型呢?

首先我们控制map和set传入底层红黑树的模板参数,为了与原红黑树的模板参数进行区分,我们将红黑树第二个模板参数的名字改为T,意为通用参数。

template<class K, class T>
class BRTree

对于set,我们传入Key:

template<class K>
class set
{
public:
	//...
private:
	BRTree<K, K> _t;
};

对于map,我们传入键值对:

template<class K, class V>
class map
{
public:
	//...
private:
	BRTree<K, pair<K, V>> _t;
};

细心的朋友可以发现,在红黑树中,第一个模板参数K似乎多余了,因为模板参数T中也含有Key。
那么,可以省略第一个模板参数K吗?

看似无伤淡雅,实则不然。
对于set来说,确实没啥影响,因为set传入红黑树的两个模板参数是一样的。
但对于map来说,部分接口的函数参数是要求直接给出Key值的,例如find和erase。

既然我们改了红黑树的参数了,那节点类也得改改了。

对于模板参数,我们只需T即可,并没有任何用到K的地方。

成员我们依次更改一下类型即可:

template<class T>
struct BRTreeNode
{
	BRTreeNode<T>* _left;
	BRTreeNode<T>* _right;
	BRTreeNode<T>* _parent;
	T _data;
	Color _col;

	BRTreeNode(const T& data)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_data(data)
		,_col(RED)
	{ }
};

三、提取Key,仿函数的添加

在红黑树的find、insert等的接口中,需要对T类型的数据进行比较操作。
对于set来说,没啥问题;
但对于map,其类型为pair<K, V>,我们且来看看pair的比较运算符的重载是否符合我们的要求(只比较Key(first)的值)。
在这里插入图片描述
可以看到,pair的比较方式是:先比较first,若不为真,就再比较second

显然,这是不符合map的要求的。
既然如此,我们就需要利用仿函数来手动解决了。

目标仿函数功能:提取出pair<Key,Val>中的Key

设置红黑树仿函数模板参数为KeyOfT:

template<class K, class T, class KeyOfT>
class BRTree

在map中:

template<class K, class V>
class map
{
	//作为内部类
	//提取key
	struct MapKeyOfT
	{
		const K& operator()(const pair<K, V>& kv)
		{
			return kv.first;
		}
	};
public:
	//...
private:
	BRTree<K, pair<K, V>, MapKeyOfT> _t;

虽然对于set无需这般操作,但红黑树必须添加这个模板参数,set也只能无奈“陪跑”了。

template<class K>
	//仿函数提取key
	struct SetKeyOfT
	{
		const K& operator()(const K& Key)
		{
			return Key;
		}
	};
class set
{
public:
	//...
private:
	BRTree<K, K, SetKeyOfT> _t;
};

四、普通迭代器的实现

迭代器的本质就是对指针进行封装,然后重载一些操作符,以至于我们可以像普通指针一样地对数据结构进行操作。

在红黑树中,迭代器必然封装的是节点指针,因此节点指针就是我们迭代器的成员。

template<class T, class T*, class T&>
struct TreeIterator
{
	typedef BRTreeNode<T> Node;//节点类型
	typedef TreeIterator<T, Ptr, Ref> Self;//迭代器类型
	
	Node* _node;//迭代器所封装的指针
};

接下来我们来一一实现他重要的运算符的重载:

当对迭代器进行解引用操作时,直接返回其对应结点数据的引用即可

T& operator*()
{
	return _node->_data; //返回结点数据的引用
}

当对迭代器进行->操作时,直接返回其对应结点数据的指针即可

T* operator->()
{
	return &_node->_data; //返回结点数据的指针
}

当对两迭代器进行比较时,我们还需要==!=运算符

//判断两个正向迭代器是否不同
bool operator!=(const Self& s) const
{
	return _node != s._node; //判断两个正向迭代器所封装的结点是否是同一个
}
//判断两个正向迭代器是否相同
bool operator==(const Self& s) const
{
	return _node == s._node; //判断两个正向迭代器所封装的结点是否是同一个
}

上面几个都是“开胃小菜”,真正的大餐来了😏

operator++实现

迭代器进行++操作后,应该根据红黑树中序遍历的序列找到当前结点的下一个结点。

在这里插入图片描述

核心逻辑

  1. 如果当前节点的右子树不为空时,则下一个访问节点应该在右子树的最左节点(最小节点)
  2. 如果当前节点的右子树为空时,则下一个访问节点应该在该节点的的祖先节点中,找到上一个孩子是父亲的左孩子的那个祖先。

代码:

// 迭代器的++操作,让迭代器可以移动
Self& operator++()
{
	//右子树存在,下一个访问节点是右子树的最左节点
	if (_node->_right)
	{
		Node* LeftMax = _node->_right;
		while (LeftMax->_left)
		{
			LeftMax = LeftMax->_left;
		}
		_node = LeftMax;
	}
	else//右子树不存在,沿着祖先路径往上寻找存在左孩子的那个祖先
	{
		Node* cur = _node;
		Node* parent = cur->_parent;;
		
		while (parent)
		{
			if (cur == parent->_left)
				break;
			else
			{
				cur = parent;
				parent = parent->_parent;
			}
		}
		_node = parent;
	}
	return *this;
}

还有operator--,是同理的,左右反一下就好了,笔者这里留下发挥空间。

迭代器的实现到这基本告一段落了,我们现在来给红黑树、map、set配置迭代器:

红黑树:

template<class K, class T, class KeyOfT>
class BRTree
{
	typedef BRTreeNode<T> Node;
public:
	//注意这里要typedef在public内,否则就是私有成员了,map和set就无法访问了
	typedef TreeIterator<T, T*, T&> Iterator;
	
	//返回最左节点
	Iterator _begin()
	{
		Node* LeftMax = _root;
		while (LeftMax->_left)
		{
			LeftMax = LeftMax->_left;
		}
		return Iterator(LeftMax);
	}

	Iterator _end()
	{
		return Iterator(nullptr);
	}
	//…………
};

map:

template<class K, class V>
class map
{
	//提取key
	struct MapKeyOfT
	{
		const K& operator()(const pair<K, V>& kv)
		{
			return kv.first;
		}
	};
public:
	typedef typename BRTree<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;

	iterator begin()
	{
		return _t._begin();
	}

	iterator end()
	{
		return _t._end();
	}
private:
	BRTree<K, pair<const K, V>, MapKeyOfT> _t;
};

set:

template<class K>
class set
{
	//仿函数提取key
	struct SetKeyOfT
	{
		const K& operator()(const K& Key)
		{
			return Key;
		}
	};
public:
	typedef typename BRTree<K, K, SetKeyOfT>::Iterator iterator;
	
	iterator begin() 
	{
		return _t._begin();
	}

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

private:
	BRTree<K, K, SetKeyOfT> _t;
};

五、const迭代器的实现

const迭代器的实现并不难,难的是后续会引发一系列复杂的问题,后文基本上都是在解决这些问题。

将红黑树传入迭代器的T*T&参数用const修饰

typedef TreeIterator<T, const T*, const T&> const_Iterator;

此时,迭代器的模板参数就不能用具体的参数了,将T*换成PtrT&换成Ref,以便const T*const T&的传入。

template<class T, class Ptr, class Ref>
struct TreeIterator

然后在map和set中也需要和普通迭代器一样去定义

最后在红黑树、map、set中实现const_iterator的begin()、end()接口


六、设置Key值不可修改

我们知道,map和set中的Key是不可修改的,如何做到呢?

对于set,我们使用“障眼法”(记住这个障眼法,后文要考):

typedef typename BRTree<K, K, SetKeyOfT>::const_Iterator iterator;
typedef typename BRTree<K, K, SetKeyOfT>::const_Iterator const_iterator;

对于map,我们巧妙地将传入底层红黑树的pair<K, V>改为pair<const K, V>,方可实现Key值无法修改。


七、修改insert的返回值 + operator[]的实现

我们知道,operator[]的本质是插入,它是调用insert的接口实现的,且insert的返回值被设置为pair<iterator, bool>。(上篇有详细介绍

1. 修改insert返回值

非常简单,我们将原本的返回值和当前插入节点的迭代器make_pair即可。

然后我们修改map和set的接口:

对于map,直接修改insert返回值即可

pair<iterator, bool> insert(const pair<K, V>& kv)
{
	return _t._Insert(kv);
}

然而对于set,出大事了!
如果和map一样处理的话,是会报错的,为什么呢?

//错误写法
pair<iterator, bool> insert(const K& key)
{
	return _t._Insert(key);
}

还记得我们之前使用的妙计“障眼法”吗?
没错,我们现在要来付出代价了🤣
在set中,iterator是“假“”的,它本质上是const_iterator,而我们insert返回值中的iterator是“货真价实”的iterator
错误点:用pair<const_iterator, bool>类型作为返回值类型去返回pair<iterator, bool>类型的值。

别看它们长得像,它们可是两个不同的类型!

解决方案
用一个pair<iterator, bool>类型的变量ret去接收insert的返回值,然后再用iterator去构造成const_iterator,再返回。

//正确写法
pair<iterator, bool> insert(const K& key)
{
	pair<typename BRTree<K, K, SetKeyOfT>::Iterator, bool> Ret = _t._Insert(key);
	//用iterator去构造成const_iterator,再返回
	return pair<iterator, bool>(Ret.first, Ret.second);
}

还没完,我们的迭代器还不支持用普通迭代器去构造const迭代器,我们需要去写一个构造函数:

typedef TreeIterator<T, T*, T&> iterator;

TreeIterator(const iterator& it)
	:_node(it._node)
{}

别看这个函数普普通通,其实别有洞天:

  • 当这个迭代器类被实例化为const迭代器,这个函数的作用是一个构造函数。可以用普通迭代器去构造一个const迭代器。
  • 当这个迭代器类被实例化为普通迭代器,这个函数就是一个拷贝构造函数。

2. operator[]

直接返回insert返回的迭代器的value即可

V& operator[](const K& key)
{
	pair<iterator, bool> Ret = insert(make_pair(key, V()));
	return Ret.first->second;
}

八、封装后的源代码

虽然封装过程已经阐述完毕了,但在代码更改过程中还是有许多细节的,下面给出完整封装后的代码。

1. 红黑树

enum Color
{
	BLACK,
	RED
};

template<class T>
struct BRTreeNode
{
	BRTreeNode<T>* _left;
	BRTreeNode<T>* _right;
	BRTreeNode<T>* _parent;

	Color _col;
	T _data;

	BRTreeNode(const T& data)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_data(data)
		,_col(RED)
	{ }
};

template<class K, class T, class KeyOfT>
class BRTree
{
	typedef BRTreeNode<T> Node;
public:
	typedef TreeIterator<T, T*, T&> Iterator;
	typedef TreeIterator<T, const T*, const T&> const_Iterator;

	Iterator _begin()
	{
		Node* LeftMax = _root;
		while (LeftMax->_left)
		{
			LeftMax = LeftMax->_left;
		}
		return Iterator(LeftMax);
	}

	Iterator _end()
	{
		return Iterator(nullptr);
	}

	const_Iterator _begin() const
	{
		Node* LeftMax = _root;
		while (LeftMax->_left)
		{
			LeftMax = LeftMax->_left;
		}
		return const_Iterator(LeftMax);
	}

	const_Iterator _end() const
	{
		return const_Iterator(nullptr);
	}

	//查找
	Node* _find(const K& key)
	{
		if (_root == nullptr)
		{
			return nullptr;
		}

		Node* cur = _root;
		while (cur)
		{
			if (kot(cur->_data) < key)
			{
				cur = cur->_right;
			}
			else if (kot(cur->_data) > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}

		return nullptr;
	}

	// 在红黑树中插入值为val的节点
	pair<Iterator, bool> _Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return make_pair(_root, true);
		}

		KeyOfT kot;
		Node* cur = _root;
		Node* parent = nullptr;
		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(cur, false);
			}
		}

		cur = new Node(data);
		Node* newnode = cur;
		//新增在左
		if (kot(parent->_data) > kot(data))
		{
			parent->_left = cur;
		}
		else//新增在右
		{
			parent->_right = cur;
		}
		cur->_parent = parent;

		//更新颜色
		//检查是否需要更新颜色,若parent为黑则无需更新
		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			Node* uncle = nullptr;
			if (grandfather->_left == parent)
			{
				uncle = grandfather->_right;
			}
			else
			{
				uncle = grandfather->_left;
			}

			if (uncle && uncle->_col == RED)//uncle存在且为红——变色
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				if (grandfather->_parent)
				{
					if (grandfather->_col == BLACK)
					{
						break;
					}
					else
					{
						cur = grandfather;
						parent = cur->_parent;
					}
				}
				else
				{
					grandfather->_col = BLACK;
					break;
				}
			}
			else//uncle不存在或uncle存在且为黑——旋转+变色
			{
				//判断何种情况,该用何种旋转
				if (grandfather->_right == parent)
				{
					if (cur == parent->_right)
					{
						//  g
						//     p
						//		  c
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//  g
						//     p
						//  c
						RotateR(parent);
						RotateL(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
				}
				else
				{
					if (cur == parent->_left)
					{
						//        g
						//     p
						//  c
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//    g
						//  p
						//    c
						RotateL(parent);
						RotateR(grandfather);
						cur->_col = BLACK;
						grandfather->_col = RED;
					}
				}
				break;
			}
		}
		_root->_col = BLACK;
		return make_pair(newnode, true);
	}

2. 迭代器

template<class T, class Ptr, class Ref>
struct TreeIterator
{
	typedef BRTreeNode<T> Node;
	typedef TreeIterator<T, Ptr, Ref> Self;
	typedef TreeIterator<T, T*, T&> iterator;
	Node* _node;

	TreeIterator(const iterator& it)
		:_node(it._node)
	{ }

	TreeIterator(Node* node)
		:_node(node)
	{ }

	Ref operator*()
	{
		return _node->_data;
	}

	Ptr operator->()
	{
		return &_node->_data;
	}

	// 迭代器的++操作,让迭代器可以移动
	Self& operator++()
	{
		//右子树存在,下一个访问节点是右子树的最左节点
		if (_node->_right)
		{
			Node* LeftMax = _node->_right;
			while (LeftMax->_left)
			{
				LeftMax = LeftMax->_left;
			}
			_node = LeftMax;
		}
		else//右子树不存在,沿着祖先路径往上寻找存在左孩子的那个祖先
		{
			Node* cur = _node;
			Node* parent = cur->_parent;;
			
			while (parent)
			{
				if (cur == parent->_left)
					break;
				else
				{
					cur = parent;
					parent = parent->_parent;
				}
			}
			_node = parent;
		}
		return *this;
	}

	Self operator++(int)
	{
		Self tmp(*this);
		//右子树存在,下一个访问节点是右子树的最左节点
		if (_node->_right)
		{
			Node* LeftMax = _node->_right;
			while (LeftMax->_left)
			{
				LeftMax = LeftMax->_left;
			}
			_node = LeftMax;
		}
		else//右子树不存在,沿着祖先路径往上寻找存在左孩子的那个祖先
		{
			Node* cur = _node;
			Node* parent = cur->_parent;;

			while (parent)
			{
				if (cur == parent->_left)
					break;
				else
				{
					cur = parent;
					parent = parent->_parent;
				}
			}
			_node = parent;
		}
		return tmp;
	}

	Self& operator--()
	{
		//左子树存在,下一个访问节点是左子树的最右节点
		if (_node->_left)
		{
			Node* RightMax = _node->_left;
			while (RightMax->_right)
			{
				RightMax = RightMax->_right;
			}
			_node = RightMax;
		}
		else//左子树不存在,沿着祖先路径往上寻找存在右孩子的那个祖先
		{
			Node* cur = _node;
			Node* parent = cur->_parent;;

			while (parent)
			{
				if (cur == parent->_right)
					break;
				else
				{
					cur = parent;
					parent = parent->_parent;
				}
			}
			_node = parent;
		}
		return *this;
	}

	Self operator--(int)
	{
		Self tmp(*this);
		//左子树存在,下一个访问节点是左子树的最右节点
		if (_node->_left)
		{
			Node* RightMax = _node->_left;
			while (RightMax->_right)
			{
				RightMax = RightMax->_right;
			}
			_node = RightMax;
		}
		else//左子树不存在,沿着祖先路径往上寻找存在右孩子的那个祖先
		{
			Node* cur = _node;
			Node* parent = cur->_parent;;

			while (parent)
			{
				if (cur == parent->_right)
					break;
				else
				{
					cur = parent;
					parent = parent->_parent;
				}
			}
			_node = parent;
		}
		return tmp;
	}

	// 让迭代器能够支持比较
	bool operator!=(const Self& s)const
	{
		return _node != s._node;
	}
	bool operator==(const Self& s)const
	{
		return _node == s._node;
	}
};

3. set

#pragma once

#include"BRTree.h"

namespace Surplus
{
	template<class K>
	class set
	{
		//仿函数提取key
		struct SetKeyOfT
		{
			const K& operator()(const K& Key)
			{
				return Key;
			}
		};
	public:
		typedef typename BRTree<K, K, SetKeyOfT>::const_Iterator iterator;
		typedef typename BRTree<K, K, SetKeyOfT>::const_Iterator const_iterator;

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

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

		pair<iterator, bool> insert(const K& key)
		{
			pair<typename BRTree<K, K, SetKeyOfT>::Iterator, bool> Ret = _t._Insert(key);
			return pair<iterator, bool>(Ret.first, Ret.second);
		}
	private:
		BRTree<K, K, SetKeyOfT> _t;
	};
}

4. map

#pragma once

#include"BRTree.h"

namespace Surplus
{
	template<class K, class V>
	class map
	{
		//提取key
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		typedef typename BRTree<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;
		typedef typename BRTree<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();
		}

		V& operator[](const K& key)
		{
			pair<iterator, bool> Ret = insert(make_pair(key, V()));
			return Ret.first->second;
		}

		pair<iterator, bool> insert(const pair<K, V>& kv)
		{
			return _t._Insert(kv);
		}

	private:
		BRTree<K, pair<const K, V>, MapKeyOfT> _t;
	};

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值