c++编程(24)——map的模拟实现

欢迎来到博主的专栏:c++编程
博主ID:代码小号

map的底层

map的底层是一个红黑树,关于红黑树的章节博主写在了数据结构专栏当中,因此不再赘述。

template<class key,class T,class keyofT,class valueofT>
class RBtree
{
public:
	RBtree()
		:_root(nullptr)
	{}
	typedef RBtreeNode<T> Node;
	typedef RBtreeIterator<T> iterator;
	typedef const RBtreeIterator<T> const_iterator;

	pair<iterator,bool> find(const key& key)
	{
		Node* cur = _root;
		while (cur != nullptr)
		{
			if (key < kot(cur->_data))
				cur = cur->_left;
			else if (key > kot(cur->_data))
				cur = cur->_right;
			else
				return make_pair(iterator(_root,cur),true);
		}
		return make_pair(iterator(_root,nullptr),false);
	}

	pair<iterator,bool> insert(const T& data)
	{
		Node* newnode = new Node(data);
		if (_root == nullptr)
		{
			_root = newnode;
			_root->_col = BLACK;
			return make_pair(iterator(_root,_root), true);
		}
		Node* cur = _root;
		Node* parent = _root;
		while (cur != nullptr)
		{
			if (kot(newnode->_data) < kot(cur->_data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (kot(newnode->_data) >kot( cur->_data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return make_pair(iterator(_root,cur), false);
			}
		}

		if (kot(newnode->_data) > kot(parent->_data))
		{
			newnode->_parent = parent;
			parent->_right = newnode;
		}
		else
		{
			newnode->_parent = parent;
			parent->_left = newnode;
		}
		cur = newnode;
		keep_balance(parent, cur);
		return make_pair(iterator(_root,cur), true);
	}
//省略
	private:

		Node* _root;
		keyofT kot;
		valueofT vot;
	};

为了与标准库中函数原型相同,博主将inseet和find函数的返回类型修改成了pair<iterator,bool>。

红黑树的节点

enum colour
{
	RED,BLACK
};
template<class T>
struct RBtreeNode
{
	RBtreeNode(const T& data)
		:_data(data)
		,_right(nullptr)
		,_left(nullptr)
		,_parent(nullptr)
		,_col(RED)
	{}
	T _data;//值
	RBtreeNode* _right;//右子树
	RBtreeNode* _left;//左子树
	RBtreeNode* _parent;//父节点
	colour _col;//颜色
};

与在数据结构中写的红黑树不同,为了让红黑树能够同时称为set和map的底层,博主不再使用pair<key,value>作为节点数据的类型,而是使用T作为泛型,这是因为set的层是key型的红黑树,而map是key-value型的红黑树,这两者之间的区别就在于节点存的数据的类型。如果用pair<key,value>作为红黑树的节点数据类型,那就不能适配set,因此采用泛型T。

map的模拟实现

template<class key,class value>
class map
{
public:
	typedef pair<key, value> value_type;

private:
	RBtree<key, value_type, mapkeyofT,valueofT> _t;
};

map需要key值映射value值,因此需要实例化出pair<key,value>类型的红黑树底层。

template<class key,class T,class keyofT,class valueofT>
class RBtree
{
public:
	RBtree()
		:_root(nullptr)
	{}
//省略
	private:

		Node* _root;
		keyofT kot;
		valueofT vot;
	};

在底层RBtree中,有两个私有成员需要注意,kot是key值提取器,vot是value值提取器,为什么要做出这种设计呢?我们以函数insert为例。

pair<iterator,bool> insert(const T& data)
{
	Node* newnode = new Node(data);
	if (_root == nullptr)
	{
		_root = newnode;
		_root->_col = BLACK;
		return make_pair(iterator(_root,_root), true);
	}
	Node* cur = _root;
	Node* parent = _root;
	while (cur != nullptr)
	{
		if (kot(newnode->_data) < kot(cur->_data))
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (kot(newnode->_data) >kot( cur->_data))
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			return make_pair(iterator(_root,cur), false);
		}
	}

	if (kot(newnode->_data) > kot(parent->_data))
	{
		newnode->_parent = parent;
		parent->_right = newnode;
	}
	else
	{
		newnode->_parent = parent;
		parent->_left = newnode;
	}
	cur = newnode;
	keep_balance(parent, cur);
	return make_pair(iterator(_root,cur), true);
}

由于RBtree不仅用于map容器,还需要用于set容器,而set容器的_data只有一个key值,因此使用key值进行比较大小非常方便,而map的_data却是pair<key,value>类型,pair类型对象之间是不支持比较大小的(虽然标准库中也允许pair之间的对象比较大小,但却不是key值之间的比较),因此我们需要将data的key值提取出来。

key值提取器定义在map当中,实现如下:

struct mapkeyofT
{
	const key& operator()(const pair<key,value>& data)
	{
		return data.first;
	}
};

这样我们使用kot(data)时,得到的是data当中的first成员,即key值,而vot则是提取data的second,即value值。

struct valueofT
{
	const value& operator()(const pair<key,value>& data)
	{
		return data.second;
	}
};

map的查找与插入

由于RBtree底层已经设计好了insert和find函数,我们只需要复用RBtree中的对应函数就行,这里很简单。

pair<iterator,bool> insert(const value_type& data)
{
	return _t.insert(data);
}
pair<iterator, bool> find(const key& key)
{
	return _t.find(key);
}

map的迭代器

红黑树的迭代器设计就比较麻烦了,RBtree的迭代器是一个双向迭代器,支持前进操作operator++,和后退操作operator--,前进与后退的行为与二叉搜索树的中序遍历完全一致。因此map的实现的难点完全在这里(因为红黑树的底层设计在之前的博客就已经完成了)。

struct RBtreeIterator
{
	typedef RBtreeNode<T> Node;
	typedef RBtreeIterator<T> Self;
	typedef T value_type;
	RBtreeIterator(Node* root=nullptr,Node* node=nullptr)
		:_root(root)
		,_node(node)
	{}

	Node* _root;
	Node* _node;
};

RBtree的迭代器需要存储两个值,一个是存储迭代器指向的树_root,另外一个则是存储指向_root的节点_node。

begin()函数返回_root的最左边的节点,这是因为二叉搜索树中序遍历的第一个节点就是此节点,因此我们将其设置成起始位置begin。
在这里插入图片描述
而c++规定,end()返回的迭代器需要不能指向有效节点,那么我们返回一个空节点(nullptr)即可(实际STL库中并非如此)。

至于operator!=等操作,比较简单,博主直接给上代码。

	template<class T>
	struct RBtreeIterator
	{
		typedef RBtreeNode<T> Node;
		typedef RBtreeIterator<T> Self;
		typedef T value_type;
		RBtreeIterator(Node* root=nullptr,Node* node=nullptr)
			:_root(root)
			,_node(node)
		{}

		const Self& operator=(const RBtreeIterator it)const
		{
			_root = it._root;
			_node = it._node;
		}

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

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

		Self& begin()
		{
			Node* y = _root;
			while(y && y->_left != nullptr)
			{
				y = y->_left;
			}
			_node = y;
			return *this;
		}

		const Self& begin() const
		{
			Node* y = _root;
			while (y && y->_left != nullptr)
			{
				y = y->_left;
			}
			_node = y;
			return *this;
		}

		Self& end()
		{
			_node = nullptr;
			return *this;
		}

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

		Node* _root;
		Node* _node;
	};

接下来就是重点的operator++和operator–操作了,前进的操作需要按照中序遍历的规则。因此我们先来顺一下二叉树的中序遍历规律吧。

情况(1),当前节点的右子树存在时。
在这里插入图片描述

根据中序遍历的规则,如果右子树存在,那么下一个遍历的节点就会在右子树的最左节点
在这里插入图片描述

情况2,如果当前节点不存在右子树,且是父节点的左节点。
在这里插入图片描述
那么下一个遍历的节点就会回溯到其父节点。
在这里插入图片描述

情况3,如果当前节点不存在右子树,而且是父节点的右节点
在这里插入图片描述
那么就一直回溯,回溯到当前节点是父节点的左子树的父节点处(有点绕,但确实是这样)。
在这里插入图片描述
代码如下:

	const Self& operator++()
	{
		assert(_node);
		Node* y = nullptr;
		if (_node->_right != nullptr)
		{
			y = _node->_right;
			while (y->_left != nullptr)
			{
				y = y->_left;
			}
			_node = y;
		}
		else
		{
			Node* parent = _node->_parent;
			if (parent->_left == _node)
				_node = parent;
			else
			{
				y = _node;
				while (parent&&y == parent->_right)
				{
					y = parent;
					parent = y->_parent;
				}
				_node = parent;
			}
		}
		return RBtreeIterator(_root, _node);
	}

而后退操作(operator--)则是反过来,我们来看看迭代器是如何实现后退操作的:
情况(1)如果当前节点是空节点(如果是end()返回的迭代器就是空节点)
那么我们就找到搜索二叉树的最右节点。
在这里插入图片描述

情况2,如果当前节点的左子树存在。
在这里插入图片描述
那么我们寻找左子树的最右节点。
在这里插入图片描述
情况(3)当前节点的左子树不存在。
在这里插入图片描述
那么我们就回溯到当前节点是父节点的右子节点的父节点处。
在这里插入图片描述
代码如下:

const Self& operator--()
{
	Node* y = nullptr;
	if (_node == nullptr)//end()
	{
		y = _root;
		while (y && y->_right)
		{
			y = y->_right;
		}
		_node = y;
	}
	else if (_node->_left!=nullptr)
	{
		y = _node->_left;
		while (y->_right != nullptr)
		{
			y = y->_right;
		}
		_node = y;
	}
	else
	{
		y = _node;
		Node* parent = y->_parent;
		while (parent->_left == y)
		{
			y = parent;
			parent = y->_parent;
		}
		_node = parent;
	}
	return *this;
}
  • 10
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

代码小豪

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

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

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

打赏作者

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

抵扣说明:

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

余额充值