【map和set的封装】

文章目录

  • 前言
  • 1 大致框架
  • 2 迭代器
  • 3 map和set的封装


前言

上篇博客已经讲解了红黑树插入的模拟实现,这篇文章的目的是利用上节课讲解的底层实现来封装map和set.参考代码借鉴的是STL SGI版本3.0


1 大致框架

首先我们来看看源码里面怎么定义的:

【map和set的封装】_第1张图片

【map和set的封装】_第2张图片从源码中我们不难发现map和set底层是用了一颗红黑树来封装的,并且模板参数与我们自己想的不太一样。set中传入的是,map中传入的是> (其他参数可以暂时不用考虑)
大家想想,为什么要这么设计❓这样设计的好处是什么❓
我们传入两个参数的目的就是为了用一颗红黑树封装map和set,也就是第二个参数我们可以理解为给的是一个T,T可以接受上层的传入来的参数。

那可能大家又有了疑问?那为啥要传入第一个参数呀?直接用第二个参数不行吗?
大家别忘了,我们使用find接口和erase接口是用的参数是啥?是不是无论是map还是set都是用的是K,所以这个参数我们必须的传。

但是这样做问题又来了,上层是如何知道我们比较结点大小的时候比较的是K,还是Pair?
所以我们还得再传入一个模板参数,不妨给一个仿函数,通过仿函数来取得数据。


2 迭代器

同样,迭代器往往就是容器中最精华的部分,所以迭代器的设计也是有着举足轻重的地位,这里迭代器的设计思路类似于链表的迭代器,不过具体实现却是比链表更加复杂,接下来我们便来看看。

* -> == !=这些运算符重载好说,实现起来不难,关键是如何实现++重载?–重载?
给了一颗红黑树,如下图,我们如何走到下个结点呢?
【map和set的封装】_第3张图片我们不妨采用这种思路:如果右子树存在,就找右子树的最左结点;右子树不存在,需要判断是否孩子是父亲的右节点,是的话还要往祖宗向上找,直到找到孩子是父亲的左节点为止。

我们不妨来举一个例子:假如当前在1这个结点,由于1的右子树存在,且6这个结点恰好是1右子树的最左节点,所以++后应该走到了6;假如现在当前结点为11,由于11的右子树为空,所以要往上找,直到找到孩子是父亲左节点为止:11往上找父亲为8,8的右孩子是11,所以没有找到孩子是父亲右孩子的情况继续往上走,孩子为8,父亲为13,13的左孩子为8,随意此时找到了孩子是父亲左的那一个,所以++后就走到了13这个位置。

同理- -运算符的重载可以与++运算符重载反着来,思路类似:如果左子树存在,就找左子树的最右结点;左子树不存在,需要判断是否孩子是父亲的右节点,是的话还要往祖宗向上找,直到找到孩子是父亲的右节点为止。
这个我就不再分析了,大家可以自行分析。

为了方便,我们将用nullptr来构造找到了末尾,不用继续找了,但是STL中并不是这样设计的,是用了一个头结点来连接:
【map和set的封装】_第4张图片走到了头结点就表示找到了末尾。
用这种方式会稍微麻烦些,不过总体也是不难的。

然后我们自己便可以实现一份迭代器了:

template<class T, class Ref, class Ptr >
struct __RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __RBTreeIterator<T, Ref, Ptr> Self;
	
	Node* _node;
	__RBTreeIterator(Node* node)
		:_node(node)
	{}


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

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

	bool operator==(const Self& self)const
	{
		return _node == self._node;
	}

	bool operator!=(const Self& self)const
	{
		return _node != self._node;
	}

	Self& operator++()
	{
		if (_node->_right)//右子树存在,就找右子树的最左结点
		{
			Node* left = _node->_right;
			while (left->_left)
			{
				left = left->_left;
			}
			_node = left;
		}
		else
		{
			//右子树不存在,需要判断是否孩子是父亲的右节点,是的话还要往祖宗向上找,直到找到
			//孩子是父亲的左节点为止
			Node* child = _node;
			Node* parent = _node->_parent;
			while (parent && parent->_right == child)
			{
				child = parent;
				parent = parent->_parent;
			}
			//走到这里说明找到了孩子是父亲的左节点或者
			//孩子已经是根节点了(表明在根节点也没有找到,说明已经遍历完成了)
			_node = parent;
		}
		return *this;
	}

	Self& operator--()
	{
		if (_node->_right)//左子树存在,就找左子树的最右结点
		{
			Node* right = _node->_left;
			while (left->_right)
			{
				left = left->_right;
			}
			_node = right;
		}
		else
		{
			//左子树不存在,需要判断是否孩子是父亲的左节点,是的话还要往祖宗向上找,直到找到
			//孩子是父亲的右节点为止
			Node* child = _node;
			Node* parent = _node->_parent;
			while (parent && parent->_left == child)
			{
				child = parent;
				parent = parent->_parent;
			}
			//走到这里说明找到了孩子是父亲的右节点或者
			//孩子已经是根节点了(表明在根节点也没有找到,说明已经遍历完成了)
			_node = parent;
		}
		return *this;
	}
};


3 map和set的封装

其他的都好说,关键是如何实现set不能够修改,而map中可以修改Val;
我们可以采取这种方式:set的普通迭代器我们用上层的const迭代器实现,set的const迭代器我们也用上层的const迭代器实现。map的话我们只需要将==第二个模板参数给pair==就可以了。

set.hpp:

namespace grm
{
	template<class K>
	class set
	{
		struct SetOfKey
		{
			const K& operator()(const K& k)
			{
				return k;
			}
		};
	private:
		RBTree<K, K, SetOfKey> _rbTree;
		//typedef typename RBTree::Iterator iterator;
		//不能够像上面这样传入参数
		typedef typename RBTree<K, K, SetOfKey>::ConstIterator iterator;
		typedef typename RBTree<K, K, SetOfKey>::ConstIterator const_terator;

	public:

		iterator begin()
		{
			return _rbTree.begin();
		}

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

		const_terator begin()const
		{
			return _rbTree.begin();
		}

		const_terator end()const
		{
			return _rbTree.end();
		}

		pair<iterator, bool> insert(const K& k)
		{
			return _rbTree.insert(k);
		}
	};

map.hpp:

namespace grm
{
	template<class K, class V>
	class map
	{
		struct MapOfKey
		{
			const K& operator()(const pair<const K, V>& kv)
			{
				return kv.first;
			}
		};
	private:
		RBTree<K, pair<const K,V>, MapOfKey> _rbTree;
	public:
		typedef typename RBTree<K, pair<const K, V>, MapOfKey>::Iterator iterator;
		typedef typename RBTree<K, pair<const K, V>, MapOfKey>::ConstIterator const_iterator;

		iterator begin()
		{
			return _rbTree.begin();
		}

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

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

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

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

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

但是这样实现set时也还是会遇到问题:那就是我们用了普通迭代器来构造const迭代器,这样势必是会报错的,有什么处理方式吗?
我们可以在迭代器中多给出一个构造:

//模板参数是普通迭代器就是拷贝构造
	//模板参数是const迭代器就是用普通迭代器构造const迭代器
	__RBTreeIterator(const __RBTreeIterator<T,T&,T*>& it)
		:_node(it._node)
	{}

这样就能够解决问题了。

如果大家还有哪里不懂的地方可以私信博主,有需要的可以参考博主的码云:
【码云地址】


你可能感兴趣的:(C++进阶,链表,数据结构,算法,map和set的封装)