【C++】map和set的模拟实现

一、思路


1. 改造RBTree

现在我们有一棵 R B T r e e RBTree RBTree,那么如何用它实现 m a p map map s e t set set?我们知道 m a p map map是 KV 结构, s e t set set是 K 结构,传统思路是两份 R B T r e e RBTree RBTree的代码一个改成 m a p map map,一个改成 s e t set set。这样可以实现但不够完美,重复性较高。那么如何优雅地实现呢?查看源码,学习大佬的思路:

m a p map map:

在这里插入图片描述

s e t set set:

在这里插入图片描述

查看源码发现: m a p map map s e t set set R B T r e e RBTree RBTree传模板参数时都是<key, value> m a p map map传的是<Key, pair<const Key, Value>> s e t set set传的是<Key, Key>

再看 R B T r e e RBTree RBTree源码的实现:

在这里插入图片描述
也就是说map和set传进RBTree的Key(第一个模板参数)在RBTree的节点里是没有使用的,RBTree的节点只保存Value。

综上一起看:

在这里插入图片描述

R B T r e e RBTree RBTree这一层,第二个模板参数就能决定 R B T r e e RBTree RBTree m a p map map还是 s e t set set,那为什么要传第一个模板参数?

不要忘了 R B T r e e RBTree RBTree这一层有一个查找操作:iterator find(const Key& key)是要直接使用第一个模板参数的。

namespace RB
{
	// map --> RBTree<K, pair<const K, V>, MapKeyOfT> _t
	// set --> RBTree<K, K, SetKeyOfT> _t
	template<class K, class T, class KeyOfT>
	class RBTree
	{
	public:
		// 节点
		typedef RBTreeNode<T> Node;
	private:
		Node* _root = nullptr;
	};
}

2. 改造RBTree的节点

namespace RB
{
	// 红黑树的颜色
	enum Color
	{
		RED,
		BLACK
	};
	// 红黑树的节点
	// KV 改造为 T(T是 K 或 pair<const K, V>)
	// KV由map和set层传过来,RBTree这一层看到的都是T
	//template<class K, class V>
	template<class T>
	struct RBTreeNode
	{
		RBTreeNode<T>* _left;
		RBTreeNode<T>* _right;
		RBTreeNode<T>* _parent; //指向父节点的指针
		//pair<K, V> _kv;
		T _data;
		Color _col;
	
		//RBTreeNode(const pair<K, V>& kv)
		RBTreeNode(const T& data)
			:_left(nullptr)
			, _right(nullptr)
			, _parent(nullptr)
			, _data(data)
			, _col(RED)
		{}
	};
}

这样一来新的问题出现了:我们在RBTree这一层看到的类型是TTK pair<const K, V>,那么在查找、插入比较大小时RBTree不知道是其中的哪一个,K可以直接比较大小,库中的pair实现了关系运算符的重载也可以直接比较大小,但是库中的实现并不符合我们的预期。

template <class T1, class T2>
bool operator== (const pair<T1,T2>& lhs, const pair<T1,T2>& rhs)
{ return lhs.first==rhs.first && lhs.second==rhs.second; }

template <class T1, class T2>
bool operator!= (const pair<T1,T2>& lhs, const pair<T1,T2>& rhs)
{ return !(lhs==rhs); }

库中的实现是先比较first,再比较second,如果直接使用,在RBTree中就会出现[2, 3] < [2, 4][2, 3]插在[2, 4]的左。

如何解决?看看源码在比较大小时是如何做的:

在这里插入图片描述

源码中是使用第三个模板参数KeyOfValue传入一个仿函数用来获取Value中的Key


二、实现

1. map和set的实现框架

namespace nb
{
	template<class K, class V>
	class map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
	private:
		RB::RBTree<K, pair<const K, V>, MapKeyOfT> _t;
	};


	template<class K>
	class set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	private:
		RB::RBTree<K, K, SetKeyOfT> _t;
	};	
}

2. 改造insert

对于RBTree的insert只需要把所有比较大小的地方用KeyOfT获取key之后再比较

bool Insert(const T& data)
{
	// 是空树直接将新增节点作为根
	if (_root == nullptr)
	{
		_root = new Node(data);
		_root->_col = BLACK;
		return true;
	}
	// KeyOfT
	KeyOfT kot;// kot的作用:获取Key用于比较大小
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		// pair的 > 不能满足要求
		// 获取插入data的key和cur->_data的key,比较大小
		if (kot(data) > kot(cur->_data))
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (kot(data) < kot(cur->_data))
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}

	cur = new Node(data);
	cur->_col = RED;

	if (kot(data) > kot(parent->_data))
	{
		parent->_right = cur;
		cur->_parent = parent;
	}
	else
	{
		parent->_left = cur;
		cur->_parent = parent;
	}

	// parent为空,则cur是根节点不用继续更新
	// parent节点颜色为红,需要继续相信向上调整
	while (parent && parent->_col == RED)
	{
		Node* grandfather = parent->_parent;
		// p是g的左孩子
		if (parent == grandfather->_left)
		{
			Node* uncle = grandfather->_right;
			// 所有情况 p、cur都是红,g都是黑,
			// 情况一:u存在且为红
			if (uncle && uncle->_col == RED)
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;
				cur = grandfather;// cur变g继续更新
				parent = cur->_parent;//更新p
			}
			else// u不存在 或 u存在且为黑
			{
				// 情况二:cur是p的左,p是g的左
				if (cur == parent->_left)
				{
					//      g
					//    p
					// cur
					// 对g右单旋
					RotateR(grandfather);
					// p变黑,g变红
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else// 情况三:cur是p的右,p是g的左
				{
					//     g
					//   p
					//     cur
					RotateL(parent);
					//      g
					//   cur
					// p
					RotateR(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				break;// 旋转完成后即可退出
			}
		}
		else// p是g的右孩子
		{
			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
					//    cur
					RotateL(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					// 情况三:
					//   g
					//     p
					// cur
					RotateR(parent);
					//  g
					//    cur
					//       p
					RotateL(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				break;// 旋转完成后即可退出
			}
		}
	}
	// 避免更新出错,把根节点颜色直接给黑色
	_root->_col = BLACK;
	return true;
}

3. 实现迭代器

3. 1 RBTree的迭代器类

要实现map和set的迭代器实际上就是要实现RBTree的迭代器。

//RBTree迭代器类
template<class T, class Ref, class Ptr>
struct _RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef _RBTreeIterator<T, Ref, Ptr> Self;
	Node* _node;// 与list迭代器相同,有一个节点成员
	_RBTreeIterator(Node* node)// 构造函数:用节点构造一个迭代器
		:_node(node)
	{}
	//  * 和 ->
	Ref operator* ()
	{
		return _node->_data;
	}
	Ptr operator-> ()
	{
		return &_node->_data;
	}
	bool operator!= (const Self& s)
	{
		return _node != s._node;
	}
	// ++ 和 --
	// ……		
};

STL明确规定,begin()end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后, 可以得到一个有序的序列

因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位置,end()放在最大节点(最右侧节点)的下一个位置,关键是最大节点的下一个位置在哪里? 能否给成nullptr呢?

答案是不能,因为对end()位置的迭代器进行--操作,必须要能找最 后一个元素,此处就不行,因此最好的方式是将end()放在头结点的位置

在这里插入图片描述

本文的实现与STL实现不同:不使用header节点。begin()一样是最左节点,end()是nullptr,end()位置的迭代器不能进行--操作。

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;

	// 普通迭代器
	iterator begin()
	{
		Node* Left = _root;
		// 找最左节点
		while (Left && Left->_left)
		{
			Left = Left->_left;
		}
		return iterator(Left);
	}
	iterator end()
	{
		return iterator(nullptr);
	}
	// const迭代器
	const_iterator begin() const
	{
		Node* Left = _root;
		// 找最左节点
		while (Left && Left->_left)
		{
			Left = Left->_left;
		}
		return const_iterator(Left);
	}
	const_iterator end() const
	{
		return const_iterator(nullptr);
	}

3. 2 迭代器类的operator++

我们不需要考虑整棵树(无需递归),只需要考虑局部的子树的中序遍历(左 根 右),++走到中序的下一个,不需要考虑其他子树的节点。

在这里插入图片描述

Self& operator++()
{
	if (_node->_right)
	{
		Node* minRight = _node->_right;
		while (minRight->left)
		{
			minRight = minRight->_left;
		}
		_node = minRight;// ++到minRight
	}
	else
	{
		Node* parent = _node->_parent;
		Node* cur = _node;
		// cur 不是 parent 的左就一直往上走
		// cur 是 parent 的右说明以 parent 为根的树走完了
		// parent 为空说明走到根节点的 _parent
		while (parent && cur == parent->_right)
		{
			cur = cur->_parent;
			parent = parent->_parent;
		}
		_node = parent;
	}
	return *this;
}

3. 3 迭代器类的operator--

operator--operator++是相反的逻辑

在这里插入图片描述

Self& operator--()
{
	if (_node->_left)
	{
		// 左子树的最大节点
		Node* maxLeft = _node->_left;
		while (maxLeft->_right)
		{
			maxLeft = maxLeft->_right;
		}
		_node = maxLeft;// --到maxLeft
	}
	else
	{
		Node* cur = _node;
		Node* parent = _node->_parent;
		while (parent && cur == parent->_left)
		{
			cur = cur->_parent;
			parent = parent->_parent;
		}
		_node = parent;
	}
	return *this;
}

3. 4 map的迭代器:

template<class K, class V>
class map
{
	struct MapKeyOfT
	{
		const K& operator()(const pair<K, V>& kv)
		{
			return kv.first;
		}
	};
public:
	// 要使用关键字 typename 显式地指明iterator是一个类型
	// 否则编译器会误认为是一个静态成员变量而产生编译错误
	typedef typename RB::RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
	typedef typename RB::RBTree<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;
	// 实现map的迭代器
	iterator begin()
	{
		return _t.begin();
	}
	iterator end()
	{
		return _t.end();
	}
	const_iterator begin() const
	{
		return _t.begin();
	}
	const_iterator end() const
	{
		return _t.end();
	}
	void Insert(const pair<K, V>& _kv)
	{
		_t.Insert(_kv);
	}

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

3. 5 set的迭代器:

set的迭代器像下面这样实现是有问题的:

template<class K>
class set
{
	struct SetKeyOfT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
public:
	// 要使用关键字 typename 显式地指明const_iterator是一个类型
	// 否则编译器会误认为是一个静态成员变量而产生编译错误
	typedef typename RB::RBTree<K, K, SetKeyOfT>::const_iterator iterator;
	typedef typename RB::RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;
	// 实现set的迭代器
	iterator begin()
	{
		return _t.begin();
	}
	iterator end()
	{
		return _t.end();
	}
	const_iterator begin() const
	{
		return _t.begin();
	}
	const_iterator end() const
	{
		return _t.end();
	}
	void Insert(const K& key)
	{
		_t.Insert(key);
	}
private:
	RB::RBTree<K, K, SetKeyOfT> _t;
};

我们知道set的值是不能修改的,因为它要保证RBTree的结构、性质。所以迭代器也不能修改值。

// 普通迭代器和const迭代器都是const_iterator
typedef typename RB::RBTree<K, K, SetKeyOfT>::const_iterator iterator;
typedef typename RB::RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;

像下面的代码就会编译报错:

auto it = s.begin();
while (it != s.end())
{
	std::cout << (*it) << " ";
	*it *= 10;// 不能修改
	++it;
}

但是将修改操作的代码注释掉后:

在这里插入图片描述

下面代码中,_t.begin()是普通迭代器,而 set 的 iterator 是const迭代器,这两种类型是不兼容的,不能直接进行转换。

// 实现set的迭代器
iterator begin()
{
	return _t.begin();
}

源码里只提供普通迭代器的const修饰版本:

在这里插入图片描述

把set的普通迭代器用const修饰,删除const_iterator的:

// 实现set的迭代器
iterator begin() const
{
	return _t.begin();
}
iterator end() const
{
	return _t.end();
}

4. 实现map的operator[]

map的operator[]底层调用的是insert,所以我们要改造RBTree中的insert:

在所有返回的位置返回pair<iterator, bool>即可。

pair<iterator, bool> Insert(const T& data)
{
	// 是空树直接将新增节点作为根
	if (_root == nullptr)
	{
		_root = new Node(data);
		_root->_col = BLACK;
		// 返回新增节点位置的迭代器和是否插入成功	 
		return std::make_pair(iterator(_root), true);
	}

	KeyOfT kot;// kot的作用:获取Key用于比较大小
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		// pair的 > 不能满足要求
		// 获取插入data的key和cur->_data的key,比较大小
		if (kot(data) > kot(cur->_data))
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (kot(data) < kot(cur->_data))
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			// 返回已存在值的迭代器,充当查找
			return std::make_pair(iterator(cur), false);;
		}
	}

	cur = new Node(data);
	// cur可能会向上调整发生变化
	// 需要一个newNode用于返回新增节点的迭代器
	Node* newNode = cur;
	cur->_col = RED;

	if (kot(data) > kot(parent->_data))
	{
		parent->_right = cur;
		cur->_parent = parent;
	}
	else
	{
		parent->_left = cur;
		cur->_parent = parent;
	}

	// parent为空,则cur是根节点不用继续更新
	// parent节点颜色为红,需要继续相信向上调整
	while (parent && parent->_col == RED)
	{
		Node* grandfather = parent->_parent;
		// p是g的左孩子
		if (parent == grandfather->_left)
		{
			Node* uncle = grandfather->_right;
			// 所有情况 p、cur都是红,g都是黑,
			// 情况一:u存在且为红
			if (uncle && uncle->_col == RED)
			{
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;
				cur = grandfather;// cur变g继续更新
				parent = cur->_parent;//更新p
			}
			else// u不存在 或 u存在且为黑
			{
				// 情况二:cur是p的左,p是g的左
				if (cur == parent->_left)
				{
					//      g
					//    p
					// cur
					// 对g右单旋
					RotateR(grandfather);
					// p变黑,g变红
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else// 情况三:cur是p的右,p是g的左
				{
					//     g
					//   p
					//     cur
					RotateL(parent);
					//      g
					//   cur
					// p
					RotateR(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				break;// 旋转完成后即可退出
			}
		}
		else// p是g的右孩子
		{
			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
					//    cur
					RotateL(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				}
				else
				{
					// 情况三:
					//   g
					//     p
					// cur
					RotateR(parent);
					//  g
					//    cur
					//       p
					RotateL(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				}
				break;// 旋转完成后即可退出
			}
		}
	}
	// 避免更新出错,把根节点颜色直接给黑色
	_root->_col = BLACK;
	return std::make_pair(iterator(newNode), true);
}

map中的insert和operator[]:

template<class K, class V>
class map
{
	// ……
public:
	// 要使用关键字 typename 显式地指明iterator是一个类型
	// 否则编译器会误认为是一个静态成员变量而产生编译错误
	typedef typename RB::RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
	typedef typename RB::RBTree<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;
	// 实现map的迭代器
	// ……
	//void Insert(const pair<K, V>& kv)
	//{
	//	_t.Insert(kv);
	//}

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

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

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

但是当我们把 set 中 insert 也改成:

pair<iterator, bool> Insert(const K& key)
{
	return _t.Insert(key);
}

又出现了下面的错误:
在这里插入图片描述

出现错误的原因是:RBTree层的迭代器是普通迭代器,set层的迭代器是const迭代器,无法直接转换。

那能不能像前面的begin一样在后面加一个 const 修饰呢?
答案是不能。因为使用 const 修饰符的成员函数,它们不会修改对象的状态,但是insert是需要向树中插入值的,会修改对象的状态。

那么如何解决?

源码是这样解决的:

在这里插入图片描述

照着源码解决:

pair<iterator, bool> Insert(const K& key)
{
	//return _t.Insert(key);
	pair<typename RB::RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);
	return pair<iterator, bool>(ret.first, ret.second);
}

除了上面的方法,还可以实现下面的拷贝构造函数,迭代器一般是不需要写拷贝构造的,用一个迭代器拷贝出另一个迭代器,我们的期望就是两个迭代器指向同一个节点,系统默认生成的(浅拷贝)就符合要求。

下面的构造函数,对象是普通迭代器时,使用拷贝构造的方式来初始化新的迭代器,对象是 const 迭代器时,使用构造的方式来初始化 新的迭代器。

在这里插入图片描述

实现这个函数之后,就可以将普通迭代器直接构造转换成const迭代器,set的insert也就可以直接return了。

// 普通迭代器的时候,它是拷贝构造
// const迭代器的时候,它是构造,支持用普通迭代器构造const迭代器
template<class T, class Ref, class Ptr>
typedef _RBTreeIterator<T, T&, T*> iterator;
_RBTreeIterator(const iterator& cit)
	:_node(cit._node)
{
	//std::cout << "_RBTreeIterator(const iterator& cit)" << std::endl;
}

在这里插入图片描述

之前实现的list的如果没有下面这个函数是不能将普通迭代器直接构造转换成const迭代器

// 普通迭代器的时候,它是拷贝构造
// const迭代器的时候,它是构造,支持用普通迭代器构造const迭代器
typedef __list_iterator<T, T&, T*> iterator;
__list_iterator(const iterator& cit)
			:_pnode(cit._pnode)
{
	 //std::cout << "__list_iterator(const __list_iterator& cit)" << std::endl;
}

下图是没有上面的函数,会编译报错。

在这里插入图片描述


整体代码GitHub

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

二木 同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值