C++--16.模拟实现set与map

这篇博客深入探讨了STL中set与map的底层实现,它们基于红黑树这一数据结构。文章解释了如何通过模板参数和仿函数来实现set(模型为<k,k>)和map(模型为<k,value>)的共享底层,并详细阐述了红黑树的迭代器实现,包括begin()、end()、operator++()和operator--()的功能。此外,还提供了模拟实现set和map的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在我们了解了AVLTree与红黑树之后,我们便可以去理解set与map的底层了,stl中的set与map就是用红黑树去实现的,那么他们到底是如何实现的呢?我们一起来了解下

stl中的map与set源码框架

map框架

 set框架

 我们其实按照一般的想法去理解,set与map应该是分别使用一棵二叉树来进行底层实现,但其实不是的,因为那样就会引起代码的冗余,我们的map与set仅仅是模型的参数不同,set是<k,k>模型,map是<k,value>模型,也仅仅只有参数不同而已,那么它到底是如何实现一棵树用在两个模型上的呢?

我们来看看stl中是如何来解决这个问题的

首先我们在成员变量中引入了我们需要使用的红黑树模板,我们可以看到,在红黑树模板中,建立了一个value模型,通过成员变量rb_tree_node来控制我们的第二个参数value,当我们类型匹配到set时,set中的第二个参数就是key,当匹配到map时,rb_tree_node就决定map中的第二个参数变为了pair,所以其实决定set,map中模型类型的,是rb_tree_node去传模板参数来决定的

值得注意的是我们如此改变之后,红黑树模板也需要相应做出调整,就不再确定参数类型了,需要对红黑树进行改造

 我们可以看到,我们在红黑树中加了一个模板参数,用这个模板参数来决定调用,在内部设置了一个仿函数,分别对map与set进行处理,set返回的k,map返回的kv.first,随后才对各自的操作进行处理

红黑树的迭代器

我们通过浏览源码可知,set与map的迭代器,实际上也都是红黑树完成的,所以我们这里对红黑树的迭代器进行研究

迭代器的好处是可以方便遍历,是数据结构的底层实现与用户透明。如果想要给红黑树增加迭代器,需要考虑以前问题:
begin()end()
STL明确规定,begin()end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后,可以得到一个有序的序列,因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位置end()放在最大节点(最右侧节点)的下一个位置,关键是最大节点的下一个位置在哪块?能否给成nullptr呢?答案是行不通的,因为end()位置的迭代器进行--操作,必须要能找最后一个元素,此处就不行,因此最好的方式是end()放在头结点的位置

 operator++()与operator--()

对于我们一般对于红黑树的认知,我们的++与--逻辑上是如何来走的呢,是不是如上图一样,5,6,7,8....一直到15,有序的排列呢?我们可以发现,8的下一个是它右子树的最左节点,8的上一个是左子树的最右结点,所以++我们可以将结点分为有右子树与没有右子树的,当一个结点有右子树时,其下一个节点就是右子树的最左节点,当一个结点没有右子树时,又会分为两种情况,在左树,则他的下一个节点他的parent,在右树,则他的下一个节点为parent的parent,7与8就是例子,由此我们可以得出代码

Self& operator++()//重载++
	{
		// 1、如果右不为空,中序的下一个就是右子树的最左节点
		// 2、如果右为空,表示_node所在的子树已经访问完成,在一个节点在他的祖先中找
		//  沿着路径往上找孩子是它的左的那个祖先
		if (_node->_right)//右节点存在
		{	
			Node* subLeft = _node->_right;//初始化subLeft
			while (subLeft->_left)//当subLeft有左子节点时
			{
				subLeft = subLeft->_left;//向左走到最后一个叶子节点
			}

			_node = subLeft;//返回此节点
		}
		else//当右为空
		{
			Node* cur = _node;//初始化cur
			Node* parent = cur->_parent;//初始化parent
			while (parent && cur == parent->_right)//当parent存在,且cur为parent的右子树
			{
				cur = cur->_parent;向上走
				parent = parent->_parent;
			}
			//此时代表左子树遍历完毕,该根结点了
			_node = parent;//parent就是下一个节点,这种情况包含了cur为左子树的情况
		}

		return *this;//返回此节点
	}

而我们的--,则是分为有无左子树,有左子树,则下一个就为左子树的最右结点,就像8下来是7一样,无左子树,又分为两种情况,为右树,则下一个直接就是parent,就像15与13一样,为左树,下一个就是parent的parent,就像12与11一样

Self& operator--()
	{
		if (_node->left)
		{
			Node* subRight = _node->left;
			while (subRighr&&subRight->right)
			{
				subRight = subRight->_right;
			}
			_node = subRight;
		}
		else
		{
			Node* cur = _node;
			Node* parent = _cur->_parent;
			while (parent&&cur == parent->_left)
			{
				cur = cur->_parent;
				parent = parent_parent;
			}
			_node = parent;
		}
		return *this;
	}

当我们完成了最关键的++与--,我们就可以将其封装在迭代器中,我们也就可以去模拟实现set与map了

模拟实现set

namespace wxy
{
	template<class K>
	class set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& k)//仿函数,返回判断参数
			{
				return k;//这里返回k,代表k模型
			}
		};
	public:
		typedef typename RBTree<K, K, SetKeyOfT>::iterator iterator;//调用kk模型

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

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

		bool Insert(const K& k)
		{
			return _t.Insert(k);
		}

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

	void test_set()
	{
		set<int> s;
		s.Insert(3);
		s.Insert(4);
		s.Insert(1);
		s.Insert(2);
		s.Insert(5);

		set<int>::iterator it = s.begin();
		while (it != s.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}
}

模拟实现map

namespace wxy
{
	template<class K, class V>
	class map
	{
		struct MapKeyOfT//map的仿函数,用于传递pair参数
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;//反回的是kv的pair中的first
			}
		};
	public:
		typedef typename RBTree<K, pair<K,V>, MapKeyOfT>::iterator iterator;//调用kv模型

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

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

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

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

	void test_map()
	{
		map<int, int> m;
		m.Insert(make_pair(1, 1));
		m.Insert(make_pair(3, 3));
		m.Insert(make_pair(10, 10));
		m.Insert(make_pair(5, 5));
		m.Insert(make_pair(6, 6));

		map<int, int>::iterator it = m.begin();
		while (it != m.end())
		{
			cout << it->first << ":" << it->second << endl;
			++it;
		}
		cout << endl;

		for (auto kv : m)
		{
			cout << kv.first << ":" << kv.second << endl;
		}

		cout << endl;
	}
}

改造过后可以适配set与map的RBTree

enum Colour
{
	BLACK,
	RED,
};

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)//判断set还是map的参数
		, _col(RED)
	{}
};

template<class T>
struct __TreeIterator//红黑树迭代器
{
	typedef RBTreeNode<T> Node;
	typedef __TreeIterator<T> Self;
	Node* _node;

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

	T& operator*()//重载解引用
	{
		return _node->_data;//返回判断参数
	}

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

	Self& operator++()//重载++
	{
		// 1、如果右不为空,中序的下一个就是右子树的最左节点
		// 2、如果右为空,表示_node所在的子树已经访问完成,在一个节点在他的祖先中找
		//  沿着路径往上找孩子是它的左的那个祖先
		if (_node->_right)//右节点存在
		{	
			Node* subLeft = _node->_right;//初始化subLeft
			while (subLeft->_left)//当subLeft有左子节点时
			{
				subLeft = subLeft->_left;//向左走到最后一个叶子节点
			}

			_node = subLeft;//返回此节点
		}
		else//当右为空
		{
			Node* cur = _node;//初始化cur
			Node* parent = cur->_parent;//初始化parent
			while (parent && cur == parent->_right)//当parent存在,且cur为parent的右子树
			{
				cur = cur->_parent;向上走
				parent = parent->_parent;
			}
			//此时代表左子树遍历完毕,该根结点了
			_node = parent;//parent就是下一个节点,这种情况包含了cur为左子树的情况
		}

		return *this;//返回此节点
	}

	Self& operator--()
	{
		if (_node->left)
		{
			Node* subRight = _node->left;
			while (subRighr&&subRight->right)
			{
				subRight = subRight->_right;
			}
			_node = subRight;
		}
		else
		{
			Node* cur = _node;
			Node* parent = _cur->_parent;
			while (parent&&cur == parent->_left)
			{
				cur = cur->_parent;
				parent = parent_parent;
			}
			_node = parent;
		}
		return *this;
	}

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

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

template<class K, class T, class KOfT>//改造后的红黑树
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	typedef __TreeIterator<T> iterator;
	iterator begin()//迭代器的起始点,树的最左下角
	{
		Node* cur = _root;//初始化cur
		while (cur && cur->_left)//当cur存在且cur的左子节点存在
		{
			cur = cur->_left;//向左移动,直至最后一个叶子节点
		}

		return iterator(cur);//返回cur
	}

	iterator end()//迭代器的末尾
	{
		//return iterator(nullptr);//返回NULL或者最右结点
		Node* cur = _root;
		while (cur && cur->_right)
		{
			cur = cur->_right;
		}
		return iterator(cur);
	}

	bool Insert(const T& data)
	{
		// 1、按搜索树的规则进行插入
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			return true;
		}

		KOfT koft;//此参数随着set与map的不同,set会变为K去调data,map则会变为pair中first去调data
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (koft(cur->_data) < koft(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (koft(cur->_data) > koft(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else
				//........
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值