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

目录

一、前言

二、底层红黑树的改良

1.如何改良?

2.只用一个模板参数接收数据类型

3.获取对所有实例化模板参数大小比较的方法

4.设置迭代器

①构建一个树的迭代器

②迭代器功能的实现

三、map与set的封装

1.红黑树封装其迭代器

2.map和set分别封装红黑树

①set

②map

四、结语


一、前言

        本文将简单模拟实现map和set,其中包括基本的增删查改以及容器迭代器最基本的使用,主要的内容是对二者的底层——红黑树的实现与改装,编译环境为VS2019。

        map和set的底层实现都是红黑树,因此只要在map和set中将两者封装即可,那么首先,我们需要一个两者都能复用的红黑树,关于红黑树的逻辑与简单实现,我之前所写的红黑树文章可供大家参考~http://t.csdnimg.cn/V1aup

        我将在前文红黑树的基础上进行修改,以适用于二者容器的实现。

二、底层红黑树的改良

1.如何改良?

        第一点:对于map和set的存储数据要适配,map是键对值pair,而set则是单个数据,如果是内置类型那还好说,如果是自定义类型呢,对此我们应该如何做到一个底层适配所有容器呢?

        第二点:对于map和set的增删查改,都需要经历查找这一步骤,而我们接收的数据类型不同,又该如何比较所有类型的大小呢?

        第三点:对于STL的所有容器,都拥有自己的迭代器,迭代器是实现容器和算法之间的桥梁,对于底层的实现当然缺不了对红黑树迭代器的实现。

        我们将从以上三点,对红黑树进行一定的改良。

2.只用一个模板参数接收数据类型

        传入的数据类型有很多种,不论是map的键对值,还是set的单种类型,都有可能是从简单的内置类型到复杂的自定义类型。对此,底层红黑树仅需使用一个模板参数来接收即可,代码如图所示:

template<class T>
class RBTree
{
	// 存储的数据为T
	typedef RBTreeNode<T> Node;
	
private:
	Node* _root = nullptr;
};

3.获取对所有实例化模板参数大小比较的方法

        问题来了,如果我们仅仅使用上面这种来改造代码的话,就会出现提及到的第二个问题。对此,我们需要再次传递一个仿函数来辅助查找这一步骤(没有大小比较还怎么往下迭代啊喂...),创建一个用来比较T类大小的仿函数,利用仿函数和模板类的特性来满足所有类型的比较。

        所以我们应该给上面的红黑树增加一个模板参数用来接收仿函数,其中的模板参数K值是用来比较大小的类模板,其应该属于T类中的一种(如果是键对值,那就是第一个参数;如果不是,那那默认和T一样),具体实现结果如下:

// KeyOfT函数模板提供仿函数来获取T中用来比较的K值
template<class K, class T, class KeyOfT>
class RBTree
{
	// 存储的数据为T
	typedef RBTreeNode<T> Node;
	
private:
    Node* _root = nullptr;
};

4.设置迭代器

        这里我们只实现迭代器的基本功能,其中包括返回树的起始和终止节点,前置后置++和--,迭代器的解引用*和引用&等。我们将一一来实现。

①构建一个树的迭代器

template<class T, class Ptr, class Ref>
class _RBTreeIterator
{
public:
	typedef _RBTreeIterator<T, T*, T&> Iterator;
	// 传入Ptr和Ref区分const与普通迭代器的返回值,接收什么返回什么
	typedef _RBTreeIterator<T, Ptr, Ref> Self;
	typedef RBTreeNode<T> Node;

	// 构造/拷贝构造常量类型的迭代器(普通迭代器则为拷贝构造,常量则为构造)
	_RBTreeIterator(const Iterator& it)
		:_node(it._node)
	{}

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

    Node* _node;
};

        这里你可能会有一些疑问,比如:啊,不就是一个迭代器嘛,怎么设置这么多模板参数?脑子瓦特了吧?我想说,我当初也这么认为的...其实这样设置是为了更好的区分好迭代器自身和他要返回的各种类型,诸君可以想一想,如果我们需要使用很多const类型的数据时候,比如:

        1.使用const迭代器时,由于是const类型,实现它的++或--该如何做?再创建个const类吗?

        2.使用const迭代器进行拷贝构造时候,应该怎么做?只是传入一个T类型的类模板就够解决问题了吗?

        显而易见,只有一个T类模板的迭代器是无法解决树的各种和const类型相关的问题的,因此我们将迭代器自身始终设置为可以修改的变量,返回值设置为传入的类型就行了,这样就需要多传入两个模板参数加以区分。对于迭代器的拷贝构造,只需要一个const类型的迭代器参数的构造函数就行了,理由在注释上有哦~

②迭代器功能的实现

        功能的实现大部分没什么好说的,直接就可以得出答案。其中需要注意的是迭代器的++和--的实现,因为所有根要比左树大,比右树小,所以++要找右树最左边,--要找左树最右边。其中实现要注意区分空和非空两种情况哦~(没用实现后置++和--,区别只是返回值不同就不写啦!)

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

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

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

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

// 前置++
Self& operator++()
{
	// 右树不为空,找到右树最左边的节点
	if (_node->_right)
	{
		Node* nodeRightMin = _node->_right;
		while (nodeRightMin->_left)
		{
			nodeRightMin = nodeRightMin->_left;
		}
		_node = nodeRightMin;
	}
	// 右树为空,找到最近一个作为祖父左孩子的父亲
	else
	{
		Node* cur = _node;
		Node* parent = cur->_parent;
		while (parent && parent->_right == cur)
		{
			cur = parent;
			parent = cur->_parent;
		}
		_node = parent;
	}
	return *this;
}

// 前置--
Self& operator--()
{
	// 左树不为空的情况
	if (_node->_left)
	{
		Node* nodeLeftMax = _node->_left;
		while (nodeLeftMax->_right)
		{
			nodeLeftMax = nodeLeftMax->_right;
		}
		_node = nodeLeftMax;
	}
	// 左树为空,找到最近一个作为右孩子的父亲
	else
	{
		Node* cur = _node;
		Node* parent = cur->_parent;
		while (parent && parent->_left == cur)
		{
			cur = parent;
			parent = cur->_parent;
		}
		_node = parent;
	}
	return *this;
}

三、map与set的封装

1.红黑树封装其迭代器

        红黑树迭代器实现完成了,剩下的就是将迭代器加进红黑树的实现当中去了,迭代器和树存储数据的节点无异,不过相当于给数据套上了一套使用方法。我们仅需要将迭代器中给数据提供的接口放入到红黑树中就行了,当然,我们也需要对原来红黑树实现的插入删除稍微做些改动(因为改变了原来树的模板参数列表),需要将仿函数加进去一起比较。

        和上文中相比的树中,只将改变的和新增的函数列举了出来:

// KeyOfT函数模板提供仿函数来获取T中用来比较的K值
template<class K, class T, class KeyOfT>
class RBTree
{
	// 存储的数据为T
	typedef RBTreeNode<T> Node;
	
public:
	typedef _RBTreeIterator<T, T*, T&> iterator;
	typedef _RBTreeIterator<T, const T*, const T&> const_iterator;
	
	// 第一个访问的不是root,而是最左子树
	iterator begin()
	{
		Node* cur = _root;
		// 排除空树
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}
		return iterator(cur);
	}

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

	// 返回常量迭代器
	const_iterator begin() const
	{
		Node* cur = _root;
		// 排除空树
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}
		return const_iterator(cur);
	}

	const_iterator end() const
	{
		return const_iterator(nullptr);
	}
	
	Node* Find(const K& Key)
	{
		Node* cur = _root;
		KeyOfT Kot;
		while (cur)
		{
			if (Key > Kot(cur->_data))
			{
				cur = cur->_right;
			}
			else if (Key < Kot(cur->_data))
			{
				cur = cur->_left;
			}
			else
				return cur;
		}
		return nullptr;
	}

	pair<iterator, bool> Insert(const T& data)
	{
		// 找到尾节点并插入新节点
		Node* parent = nullptr;
		Node* cur = _root;
		if (_root == nullptr) // 排除极端情况
		{
			_root = new Node(data);
			_root->_colour = BLACK;
			return make_pair(iterator(_root), true);
		}

		KeyOfT Kot;
		while (cur) // 找到
		{
			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 make_pair(iterator(cur), false);
		}
		if (Kot(data) < Kot(parent->_data)) // 插入
		{
			cur = new Node(data);
			cur->_parent = parent;
			parent->_left = cur;
			parent = cur->_parent;
		}
		else if(Kot(data) > Kot(parent->_data))
		{
			cur = new Node(data);
			cur->_parent = parent;
			parent->_right = cur;
			parent = cur->_parent;
		}
		else // 意料之外
			assert(false);

		//保存返回值(返回值修改后)
		Node* newnode = cur;

		// 调整红黑树
		while (parent && parent->_colour == RED)
		{
			// 获取祖父和叔叔节点(注:父节点为红,此时cur要么有祖父,要么没父亲)
			Node* grandparent = parent->_parent;
			Node* uncle;
			if (parent == grandparent->_left)
				uncle = grandparent->_right;
			else
				uncle = grandparent->_left;

			// 叔叔节点不存在或者为黑的情况
			if (!uncle || uncle->_colour == BLACK)
			{
				if (cur == parent->_left && parent == grandparent->_left)
				{
					RotateR(grandparent);
					cur = parent;
				}
				else if (cur == parent->_right && parent == grandparent->_right)
				{
					RotateL(grandparent);
					cur = parent;
				}
				else if (cur == parent->_right && parent == grandparent->_left)
				{
					RotateL(parent);
					RotateR(grandparent);
				}
				else if (cur == parent->_left && parent == grandparent->_right)
				{
					RotateR(parent);
					RotateL(grandparent);
				}
			}
			else // 为红的情况
			{
				parent->_colour = uncle->_colour = BLACK;
				grandparent->_colour = RED;
				cur = grandparent;
			}
			parent = cur->_parent; // 注意更新父节点
		}
		_root->_colour = BLACK; // 根节点始终为黑

		return make_pair(newnode, true);
	}

	// 检测
	bool IsValidRBTree();

private:
	bool _IsValidRBTree(Node* pRoot, size_t k, const size_t blackCount);

	// 左单旋
	void RotateL(Node* parent);

	// 右单旋
	void RotateR(Node* parent);

	Node* _root = nullptr;
};

2.map和set分别封装红黑树

        终于将红黑树底层给处理好了,接下来只需要封装map和set就行了,只需要自己实现一个仿函数,然后直接调用刚刚实现好的红黑树迭代器的接口就行了:

①set

namespace SET
{
	template<class K> // class compare
	class set
	{
		// 此处封装的仿函数需要为int类型的比较(可再次封装,再进行比较,此处为内部封装)
		struct SetKeyOfT
		{
			const K& operator()(const int& kv)
			{
				return kv;
			}
		};
	public:
		// 迭代器都设置为const类型
		// 防止修改K值
		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;

		// 此处返回类型实际为const_iterator
		// 不论写哪个重载都是一样的
		iterator begin() const
		{
			return _t.begin();
		}

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

		// !!!此处的iterator实际上是const类型
		// 需要重载能够构造普通迭代器,和拷贝构造const迭代器的构造函数
		pair<iterator, bool> insert(const K& kv)
		{
			pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(kv);
			return (pair<iterator, bool>)ret;
		}

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

②map

namespace MAP
{
	// map相当于一个房子,真正封装实现的是红黑树
	template<class K, class V>
	class map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& data)
			{
				return data.first;
			}
		};
	public:
		// 不能同时设置为const类型
		// 有必要需要修改V的数据
		typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
		typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;

		// 此处与set不一样
		// set存储的类型为不可修改的int类型
		// map存储的类型为pair类型,不可修改first,可以修改second
		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)
		{
			// typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator
			pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));
			return ret.first->second;
		}

		pair<iterator, bool> insert(const pair<const K, V>& kv)
		{
			// 不用像set那样,因为iterator就是iterator,const_iterator就是const_iterator
			return _t.Insert(kv);
		}

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

四、结语

        本人对于这章节的理解一言难尽,就从上面我对封装时的注释而言,我本文在学习和实践的过程中是踩了很多坑的...人都坑麻了,最大的难点在于const迭代器的细节实现,还会有“此迭代器非彼迭代器”的情况,请各位仔细甄别...

        如果各位真的想弄懂的话,强烈建议各位自己去对照着逻辑一步步实现一下,这将对您的学习大有帮助哦~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Evea_来自深渊

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

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

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

打赏作者

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

抵扣说明:

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

余额充值