详解c++---map和set的封装

目录标题

  • 前言
  • 红黑树的基本代码
  • map和set的封装
  • 红黑树迭代器
  • 红黑树迭代器- -
  • begin和end函数
  • 代码测试
  • const迭代器
  • 方括号的实现

前言

通过之前的学习我们知道set容器中存储的数据是k,map容器中存储的数据是k和v,但是这两个容器底层都是通过红黑树来进行实现的,那根据我们正常的思维就是,红黑树分别给这两个容器来进行一个适配,也就是说给map实现一个kv版本的迭代器,给set实现一个k版本的迭代器,但是如果是这样实现的话肯定就十分的麻烦,会干很多相同的事情,而c++是有模板这个东西的那我们能不能通过模板使得两个容器虽然装的数据个数是不一样的,但是却可以通过一个模板的红黑树类来封装成map或者set呢?答案是可以,我们使用的标准库就是用的一个红黑树封装了map和set,那接下来我们就要一步一步的实现map和set的封装。

红黑树的基本代码

enum colour
{
	RED,
	BLACK,
};
template<class K, class V>
struct RBTreeNode
{
	RBTreeNode(const pair<K, V>& _kv)
		:parent(nullptr)
		, right(nullptr)
		, left(nullptr)
		, kv(_kv)
		, _col(RED)
	{}
	RBTreeNode<K, V>* parent;
	RBTreeNode<K, V>* right;
	RBTreeNode<K, V>* left;
	pair<K, V> kv;
	colour _col;
};
template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K, V> Node;
public:
	RBTree()
		:root(nullptr)
	{}
	bool insert(const pair<K, V>& _kv)
	{
		if (root == nullptr)
		{
			root = new Node(_kv);
			root->_col = BLACK;
			return true;
		}
		Node* _cur = root;
		Node* _parent = nullptr;
		while (_cur != nullptr)
		{
			if (_cur->kv.first > _kv.first)
			{
				_parent = _cur;
				_cur = _cur->left;
			}
			else if (_cur->kv.first < _kv.first)
			{
				_parent = _cur;
				_cur = _cur->right;
			}
			else
			{
				return false;
			}
		}
		_cur = new Node(_kv);
		if (_kv.first > _parent->kv.first)
			//如果插入的数据比parent的数据大
		{
			_parent->right = _cur;
			_cur->parent = _parent;
		}
		else
			//如果插入的数据比parent的数据笑
		{
			_parent->left = _cur;
			_cur->parent = _parent;
		}
		while (_parent != nullptr && _parent->_col == RED)
		{
			Node* _grandparent = _parent->parent;
			if (_grandparent->left == _parent)
				//祖先节点的左边要调整
			{
				Node* uncle = _grandparent->right;//parent在左边所以uncle在右边
				//情况一
				if (uncle != nullptr && uncle->_col == RED)
				{
					uncle->_col = _parent->_col = BLACK;
					if (_grandparent->parent != nullptr)
					{
						_grandparent->_col = RED;
					}
					_cur = _grandparent;
					_parent = _grandparent->parent;
				}
				else
				{
					//情况二
					if (_cur == _parent->left)
					{
						RototalR(_grandparent);
						_parent->_col = BLACK;
						_grandparent->_col = RED;
					}
					else//情况三
					{
						RototalL(_parent);
						RototalR(_grandparent);
						_cur->_col = BLACK;
						_grandparent->_col = RED;
					}
					break;
				}

			}
			else
				//祖先节点的右边要调整
			{
				Node* uncle = _grandparent->left;//parent在右边所以uncle在左边
				//情况一
				if (uncle != nullptr && uncle->_col == RED)
				{
					uncle->_col = _parent->_col = BLACK;
					if (_grandparent->parent != nullptr)
					{
						_grandparent->_col = RED;
					}
					_cur = _grandparent;
					_parent = _grandparent->parent;
				}
				else
				{
					//情况二
					if (_cur == _parent->right)
					{
						RototalL(_grandparent);
						_parent->_col = BLACK;
						_grandparent->_col = RED;
					}
					else//情况三
					{
						RototalR(_parent);
						RototalL(_grandparent);
						_cur->_col = BLACK;
						_grandparent->_col = RED;
					}
					break;
				}
			}
		}
		return true;

	}
	void inorder()
	{
		_inorder(root);
	}
	bool find(const K& val)
	{
		Node* cur = root;
		while (cur)
		{
			if (cur->kv.first == val)
			{
				return true;
			}
			else if (cur->kv.first > val)
			{
				cur = cur->left;
			}
			else
			{
				cur = cur->right;
			}
		}
		return false;
	}
	bool check(Node* root, int BlackNums, int ref)
	{
		if (root == nullptr)
		{
			if (ref != BlackNums)
			{
				cout << "违反规则" << endl;
				return false;
			}
			return true;
		}
		if (root->_col == RED && root->parent->_col == RED)
		{
			cout << "出现了连续红色节点" << endl;
		}
		if (root->_col == BLACK)
		{
			BlackNums++;
		}
		return check(root->left, BlackNums, ref) && check(root->right, BlackNums, ref);
	}
	bool IsBalance()
	{
		if (root == nullptr)
		{
			return true;
		}
		if (root->_col != BLACK)
		{
			return false;
		}
		int ref = 0;
		Node* _left = root;
		while (_left)
		{
			if (_left->_col == BLACK)
			{
				ref++;
			}
			_left = _left->left;
		}
		return check(root, 0, ref);

	}
private:
	Node* root;
	void _inorder(const Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_inorder(root->left);
		cout << root->kv.first << " " << root->kv.second << endl;
		_inorder(root->right);
	}
	void RototalL(Node* _parent)
	{
		Node* subR = _parent->right;//右孩子的节点
		Node* subRL = subR->left;//右孩子的左节点
		Node* ppNode = _parent->parent;//祖父节点
		//把subRL放到_parent的右
		_parent->right = subRL;
		if (subRL)
		{
			//如果subRL不为空则修改父节点的指向
			subRL->parent = _parent;
		}
		//把_parent放到subR的左
		subR->left = _parent;
		//修改_parent的parent的指向
		_parent->parent = subR;
		if (ppNode)//如果祖父不为空,则要改变祖父的指向
		{
			if (ppNode->right == _parent)
			{
				ppNode->right = subR;
				subR->parent = ppNode;
			}
			else//如果_parent是祖父的左
			{
				ppNode->left = subR;
				subR->parent = ppNode;
			}
		}
		else//祖父为空节点说明当前调整的是根节点
		{
			root = subR;
			subR->parent = nullptr;
		}
	}
	//右旋转
	void RototalR(Node* _parent)
	{
		Node* subL = _parent->left;
		Node* subLR = subL->right;
		Node* ppNode = _parent->parent;
		_parent->left = subLR;
		if (subLR)
		{
			subLR->parent = _parent;
		}
		subL->right = _parent;
		_parent->parent = subL;
		if (ppNode != nullptr)
		{
			if (ppNode->right == _parent)
			{
				ppNode->right = subL;
				subL->parent = ppNode;
			}
			else
			{
				ppNode->left = subL;
				subL->parent = ppNode;
			}
		}
		else
		{
			root = subL;
			subL->parent = nullptr;
		}
	}
};

如果大家不知道红黑树的原理,或者红黑树不会实现的话可以看看我的上篇文章。

map和set的封装

我们知道set容器里面装的是一种元素k,map容器里面装的是两种元素k和v,根据前面的学习我们知道map是使用pair来装载的两种元素,那逻辑上讲map中存储的就是一种元素pair,那红黑树的模板可以是一种元素吗?那这里我们先一步一步的分析,如果红黑树的模板是一个元素的话map和set的封装应该是这个样子:

template<class K>
class set
{
public:

private:
	RBTree<K> tree;
};

template<class K,class V>
class map
{
public:

private:
	RBTree<pair<const K,V>> tree;

};

如果我传过来的是一个K的话,那么红黑树实例化的就是set,如果传过来的是一个pair的话,那么红黑树实例化的就是map,看上去好像很有道理但是标准库中好像不是这么干的,在标准库里面map用两个参数k和pair来进行实例化,set也是用两个参数来进行实例化但是这两个参数都是k,map和set的底层都是根据红黑树来进行封装,如果第二个参数是k那么实例化的就是set,如果第二个参数是pair则实例化的就是map,比如说下面的代码:

template<class K>
class set
{
public:

private:
	RBTree<K,K> tree;
};

#include"RBTree.h"
template<class K,class V>
class map
{
public:

private:
	RBTree<K,pair<const K,V>> tree;

};

那么这里就有个问题既然第二个模板参数就能够确定谁是map谁是set,那为什么还要传第一个模板参数呢?那为了搞清楚这个问题我们先来看看只有一个参数会出现什么问题,首先就是insert函数,这个函数的参数是这样的:

itertaor insert(const V& date)

如果当前的容器是set的话这里V的类型就为K,如果当前容器为map的话这里V的类型就为pair,而且平时我们使用insert函数传递参数时也是相对应的,往set中插入数据传递的就是K,往map中插入数据的话传递的就是pair,如果红黑树的模板只有一个参数的话这里也能应对的过来,那如果是find函数呢?不管是map容器还是find容器我们使用find函数时传递的都是K类型的数据,不存在说如果当前的容器是map的话我们就传递pair来进行查找对吧!所以如果红黑树只有一个参数的话那map容器中的find函数如何来声明呢?所以为了能够正常的封装出map和set这里的红黑树得有两个模板参数来表示数据的类型,当前红黑树既要封装map又要封装set所以我们还要对描述节点的结构体做出修改,之前结构体的形式是这样:

template<class K, class V>
struct RBTreeNode
{
	RBTreeNode(const pair<K, V>& _kv)
		:parent(nullptr)
		, right(nullptr)
		, left(nullptr)
		, kv(_kv)
		, _col(RED)
	{}
	RBTreeNode<K, V>* parent;
	RBTreeNode<K, V>* right;
	RBTreeNode<K, V>* left;
	pair<K, V> kv;
	colour _col;
};

我们上面说红黑树的第二个参数就能确定当前的树封装的是map还是set,那么这里也是同样的道理该结构体也只需要一个参数就能够确定当前实例化的是map的节点还是set的节点,所以我们将模板的参数个数改成一个用来表述存储数据的类型,然后将pair变量修改成为模板类型创建出来的变量即可,比如说下面的代码:

template< class T>
struct RBTreeNode
{
	RBTreeNode(const T& _data)
		:parent(nullptr)
		, right(nullptr)
		, left(nullptr)
		, data(_data)
		, _col(RED)
	{}
	RBTreeNode<T>* parent;
	RBTreeNode<T>* right;
	RBTreeNode<T>* left;
	T data;
	colour _col;
};

但是这里就出现了一个新的问题:在insert函数和find函数的内部都会拿传来的参数进行比较,对于set来说这个比较是正常的可以直接拿k值进行比较那map呢?他可以直接拿内部的pair来进行比较吗?那么这里就有两个问题首先pair能不能使用操作符">" "<"来进行比较,其次这个比较的规则又是否符合我们的预期呢?对于第一个问题答案是可以使用操作符来进行比较,pair提供了对该操作符的重载比如说下面的图片:
详解c++---map和set的封装_第1张图片
既然可以进行比较那我们就来看看第二个问题这里重载的逻辑是否符合我们的预期呢?对于map我们希望他内部数据的比较规则是pair的第一个元素越大那么这个pair的值就越大跟第二个元素没有关系,那库中的重载是这么实现的吗?我们来看看下面的图片:
详解c++---map和set的封装_第2张图片
大家仔细看一下就能发现这里的实现跟我们想象的不一样,他把第二个元素也添加到pair的大小比较之中了,所以如果map中直接使用pair来进行比较的话他肯定是会出问题的,我们只希望使用pair的第一个元素来进行比较也就是使用K来进行比较,那这里如何来解决这个问题呢!答案是在红黑树的模板中添加一个仿函数,这个仿函数的功能就是专门获取容器内部数据的K,首先这个仿函数需要一个参数,对于set来说这个参数的类型就是K,对于map来说这个参数的类型就是pair,比如说下面的代码:

//set.h
	struct SetofKey
	{
		const K& operator()(const K& key)
		{
		}
	};
//map.h
	struct MapofKey
	{
		const K& operator()(const pair<const K, V>& kv)
		{
			
		}
	};

对于set的仿函数传过来的数据就是key所以我们直接返回即可,对于map的仿函数传过来的数据是pair我们将pair中的第一个元素进行返回即可,那么这里的代码就如下:

//set.h
	struct SetofKey
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
//map.h
	struct MapofKey
	{
		const K& operator()(const pair<const K, V>& kv)
		{
			return kv.first;
		}
	};

既然添加了仿函数那么红黑树在显示实例化的时候是不是也得做出修改啊,那么这里的代码就如下:

//set.h
template<class K>
class set
{
public:

private:
	struct SetofKey
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};

	RBTree<K,K, SetofKey> tree;

};
//map.h
template<class K,class V>
class map
{
public:

private:
	struct MapofKey
	{
		const K& operator()(const pair<const K, V>& kv)
		{
			return kv.first;
		}
	};

	RBTree<K,pair<const K,V>,MapofKey> tree;
};

添加了仿函数那么红黑树的比较逻辑也得做出修改,之前的比较逻辑是这样的:

template<class K, class V>
class RBTree
{
	typedef RBTreeNode<K,V> Node;
	bool insert(const pair<K, V>& _kv)
	{
		//.....
		Node* _cur = root;
		Node* _parent = nullptr;
		while (_cur != nullptr)
		{
			if (_cur->kv.first > _kv.first)
			{
				_parent = _cur;
				_cur = _cur->left;
			}
			else if (_cur->kv.first < _kv.first)
			{
				_parent = _cur;
				_cur = _cur->right;
			}
			else
			{
				return false;
			}
		}
		//.....
	}
}

有了仿函数之后就不能靠我们自己来获取数据中的K,而是使用仿函数创建出来的仿函数对象来获取节点中data的Key值,这里大家不要忘记修改insert函数的的参数类型和节点实例化的参数个数以及红黑树模板的参数个数,那么这里修改之后的代码就是这样:

template<class K, class T,class KeyofT>
class RBTree
{
public:
typedef RBTreeNode<T> Node;
	bool insert(const T& _data)
	{
		//....
		KeyofT kot;
		Node* _cur = root;
		Node* _parent = nullptr;
		while (_cur != nullptr)
		{
			if (kot(_cur->data) > kot(_data))
			{
				_parent = _cur;
				_cur = _cur->left;
			}
			else if (kot(_cur->data) < kot(_data))
			{
				_parent = _cur;
				_cur = _cur->right;
			}
			else
			{
				return false;
			}
		}
		//....
	}
}

当然这里要修改的地方远不止上面这些地方,只要是用到使用pair的first进行比较的地方都得做出修改,因为篇幅问题这里不能给大家一一展示。那么修改之后map和set的insert函数和find函数就可以调用红黑树的find和insert函数来实现比如说下面的代码:

//map.h
template<class K, class V>
class map
{
public:
	bool insert(const pair<K,V>& data)
	{
		return tree.insert(data);
	}
	bool find(const K& key)
	{
		return tree.find(key);
	}
private:
	struct MapofKey
	{
		const K& operator()(const pair<const K, V>& kv)
		{
			return kv.first;
		}
	};
	RBTree<K, pair<const K, V>, MapofKey> tree;
};
//set.h
template<class K>
class set
{
public:
	bool insert(const K& key)
	{
		return tree.insert(key);
	}
	bool find(const K& key)
	{
		return tree.find(key);
	}
private:
	struct SetofKey
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
	RBTree<K, K, SetofKey> tree;

红黑树迭代器

我们首先来看看库中的迭代器是如何实现的:
详解c++---map和set的封装_第3张图片

库中给了一个头结点用于指向树的根节点,header的left指针指向最左节点,所以迭代器的begin函数就是指向header的left,header的right指针指向的是最右节点,所以迭代器的end函数指向的就是header,那么这就是库中迭代器实现的方式,那么我们为了方便就不添加头节点,begin函数依然是返回最左节点,而end函数则是直接返回一个空指针,那么这里我们就可以实现一下这两个函数,根据前面list的经验我们知道这里的迭代器不可能是在红黑树类的内部来实现的,而是得单独创建一个类出来,因为我们当前要实现的是一个普通迭代器,所以这个类模板就只有一个参数用于表示当前迭代器只想的数据类型,因为我们要在这个类里面执行一些操作比如说找到最左节点或者下一步的节点等等,所以我们得在该类里面创建一个该节点类型的变量用于做一些操作,迭代器里面肯定存在++和–的操作符重载,这些函数的返回类型就是迭代器本身,所以为了方便后面的书写我们就对常用的类型进行重命名,因为该类中存在着成员变量所以我们还得实现一下该类的构造函数,那么这里的代码就如下:

template<class V>
struct _RBTreeIterator
{
	typedef RBTreeNode<V> Node;
	typedef _RBTreeIterator<V> self;
	Node* _node;
	_RBTreeIterator( Node* node)
		:_node(node)
	{}
};

然后我们就可以实现几个简单的操作符重载函数,第一个就是解引用重载该函数直接返回当前节点里面的data数据的引用即可,第二个就是->操作符重载该函数的作用就是返回当前节点内部数据的地址,第三个就是!=操作符重载,这三个函数都十分的简单我们就不讲解大家直接看代码即可:

template<class V>
struct _RBTreeIterator
{
	typedef RBTreeNode<V> Node;
	typedef _RBTreeIterator<V> self;
	Node* _node;
	_RBTreeIterator( Node* node)
		:_node(node)
	{}
	V& operator*()
	{
		return _node->data;
	}
	V* operator->()
	{
		return &_node->data;
	}
	bool operator !=(const self& s)
	{
		return _node != s._node;
	}
};

我们说搜索二叉树采用中序遍历得到的结果就是有序的,而中序遍历的顺序就是先访问左子树再访问根节点最后访问右子树,使用++就是让当前的节点往后走一步,当前停留的节点一定是某个子树的根节点,这个时候再往后走的话一定是来到右子树的最左节点,但是这里有个情况就是如果右节点不存在怎么办呢?对吧如果右节点的不存在就说明我们当前的子树已经访问完了得访问上层的子树,可是这里的子树是不是也得分情况讨论啊,如果当前的子树是左子树的话我们是不是还得访问上层的根啊,如果当前的子树是右子树的话是不是还得继续往上调整一直遇到一个当前节点是根节点的左边为止或者父节点为空为止,首先判断一下我的右子树是否为空,如果不为空的话就循环到右子树的最左子树,那么这里的代码就如下:

self& operator++()
{
	if (_node->right)
	{
		Node* mine = _node->right;
		while (mine->left)
		{
			mine = mine->left;
		}
		_node = mine;
	}
	else
	{
	}
}

如果右子树为空就说明当前子树已经遍历完了,这时需要往上走了,如果当前节点是父节点的左边的话我们就停止,如果当前节点是父节点的右边的话我们还得继续往上调整,所以这里我们得创建两个Node*指针一个名为parent用于指向父节点,一个名为cur指向当前的节点,每次循环我们就判断一下cur和parent的位置关系以及parent是否为空,循环体里面就是将parent的值赋值给cur,然后让parent指向parent的parent,最后将parent的值赋值给_node即可,那么这里的代码就如下:

	self& operator++()
	{
		if (_node->right)
		{
			Node* mine = _node->right;
			while (mine->left)
			{
				mine = mine->left;
			}
			_node = mine;
		}
		else
		{
			Node* parent = _node->parent;
			Node* cur = _node;
			while (parent != nullptr && parent->right == cur)
			{
				cur = parent;
				parent = parent->parent;
			}
			_node = parent;
		}
		return *this;
	}

红黑树迭代器- -

中序遍历的顺序是先访问左子树再访问根节点最后访问右子树,- - 是倒着走所以这里访问的节点顺序就是先访问右子树再访问根节点最后访问左子树,迭代器所在的位置一定是某个子树的根节点,当迭代器位于这个位置时说明该节点的右子树已经访问完了,接下来就要访问该节点的左子树又因为先要访问 右子树,所以对该迭代器- -就会来到左子树的最右节点,如果左子树为空的话这里就得分情况讨论,如果当前的节点是父节点的左的话就得继续往上调整,如果当前的节点是父节点的右的话这里就往上调整一次即可:

self& operator--()
{
	if (_node->left)//左边不为空
	{
		Node* mine = _node->left;
		while (mine->right)
		{
			mine = mine->right;
		}
		_node = mine;
	}	
	else//左边为空
	{
		Node* _parent = _node->parent;
		Node* cur = _node;
		while (_parent->left == cur && _parent != nullptr)
		{
			cur = _parent;
			_parent = _parent->parent;
		}
		_node = _parent;
	}
	return *this;
}

begin和end函数

红黑树的begin函数就是返回当前树的最左节点,begin函数在红黑树的类中实现,所以我们还得在红黑树的类中添加一个迭代器的类型,然后begin函数的返回类型是迭代器类型,那么这里的代码就如下:

iterator begin()
{
	Node* cur = root;
	while (cur->left)
	{
		cur = cur->left;
	}
	return iterator(cur);	
}

end函数就是直接用空指针构造一个迭代器返回即可:

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

红黑树的迭代器完成之后就可以用来封装map和set的迭代器,那么这里的代码就如下:

//set.h
template<class K>
class set
{
	struct SetofKey
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
public:
	typedef typename RBTree<K, K, SetofKey>::iterator iterator;
	iterator begin()
	{
		return tree.begin();
	}
	iterator end()
	{
		return tree.end();
	}
//....
private:
	RBTree<K, K, SetofKey> tree;
};
//map.h
template<class K, class V>
class map
{
	struct MapofKey
	{
		const K& operator()(const pair<const K, V>& kv)
		{
			return kv.first;
		}
	};
public:
	typedef typename RBTree<K, pair<const K, V>, MapofKey>::iterator iterator;
	iterator begin()
	{
		return tree.begin();
	}
	iterator end()
	{
		return tree.end();
	}
//....
private:
	RBTree<K, pair<const K, V>, MapofKey> tree;
};

我们在map和set中使用了RBTree中的类型iterator,但是使用之前我们并没有实例化出来一个红黑树对象,所以这就会导致使用RBTree, MapofKey>::iterator这个迭代器类型时编译器不知道这个iterator到底是类型还是一个静态变量,所以这里我们得添加一个typename来告诉编译器虽然这里没有实例化出来对象但是这个iterator是一个类型名。

代码测试

将上面的代码完成之后我们就可以写一段代码来测试一下上面的代码的正确性,首先测试一下set的插入函数和查找函数的实现是否是正确的,那么这里的测试代码就如下:

#include"set.h"
int main()
{
	srand(time(0));
	set<int> s1;
	const size_t N = 100000;
	for (size_t i = 0; i < N; ++i)
	{
		size_t x = rand();
		s1.insert(x);
	}
	s1.insert(100);
	cout << s1.check() << endl;
	cout << s1.find(100) << endl;
	return 0;
}

代码的运行结果就如下:
详解c++---map和set的封装_第4张图片
符合我们的预期,那么我们再测试一下set的迭代器实现的是否是真确的,那么这里的测试代码如下:

int main()
{
	srand(time(0));
	set<int> s1;
	for (int i = 0; i < 100; i++)
	{
		int x = rand();
		s1.insert(x);
	}
	set<int>::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	return 0;
}

代码的运行结果如下:
详解c++---map和set的封装_第5张图片
符合我们的预期那么这就说明我们的代码实现的结果是正确的,接下来我们就看看map的insert函数和find函数实现的是否是正确的:

#include"set.h"
#include"map.h"
int main()
{
	map<int, int> m;
	srand(time(0));
	for (int i = 0; i <= 10000; i++)
	{
		int x = rand();
		m.insert(make_pair(x, x));
	}
	m.insert(make_pair(100, 100));
	cout << m.find(100) << endl;
	return 0;
}

代码的运行结果如下:
详解c++---map和set的封装_第6张图片
我们再来看看迭代器的测试代码:

	map<int, int> m;
	srand(time(0));
	for (int i = 0; i <= 100; i++)
	{
		int x = rand();
		m.insert(make_pair(x, x));
	}
	map<int, int>::iterator it = m.begin();
	while (it != m.end())
	{
		cout << (*it).first << " ";
		++it;
	}

代码的运行结果如下:

详解c++---map和set的封装_第7张图片
符合我们的预期那么这就说明当前map迭代器实现的内容是正确的,但是这里的实现存在一个问题,当迭代器来到end位置时内部的_node指针就指向了空,那这个时候我们要是想通过- - 让他往回走的话是会出错的,所以这是上面迭代器中得一个bug,如果想要修改这个bug的话就得像库一样往树中插入一个头节点,那这里我们就不修改了,接着来看const迭代器的实现。

const迭代器

有了前面list迭代器的经验我们知道要想用一个迭代器模板封装出来const迭代器和普通迭代器的话迭代器模板需要三个参数,比如说下面的代码:

template<class V,class Ref,class Ptr>

第一个参数表示当前迭代器所指向的数据类型,第二个参数表示的就是引用的数据类型,第三个参数表示的是指针的数据类型,迭代器的模板参数发生修改之后,我们就得对红黑树里面的迭代器类型重命名进行修改,那么这里修改之后的代码就如下:

class RBTree
{
public:
//....
	typedef RBTreeNode<V> Node;
	typedef _RBTreeIterator<V, V&, V*> iterator;
	typedef _RBTreeIterator<V,const V&,const V*> const_iterator;
//....
}

那么在迭代器类里面还得对函数的返回值进行修改,* 操作符重载返回的是节点的引用所以就将这个函数的返回值修改成为Ref,->操作符重载函数返回的是内部数据的地址所以将其返回值修改成为Ptr,那么修改后的代码就如下:

template<class V,class Ref,class Ptr>
struct _RBTreeIterator
{
	typedef RBTreeNode<V> Node;
	Node* _node;
	_RBTreeIterator( Node* node)
		:_node(node)
	{}
	Ref operator++(){//...}
	Ref operator--(){//...}
	Ref operator*(){//...}
	Ptr operator->(){//...}
	bool operator !=(const self& s){//...}
};

既然内部的迭代器发生了改变,那么外部的set和map是不是也得做出一点修改,首先set类得添加const版本的begin函数和end函数,比如说下面的代码:

template<class K>
class set
{
//...
public:
	typedef typename RBTree<K, K, SetofKey>::iterator iterator;
	typedef typename RBTree<K, K, SetofKey>::const_iterator const_iterator;
	iterator begin(){return tree.begin();}
	iterator end(){return tree.end();}
	const_iterator begin() const{return tree.begin();}
	const_iterator end() const{return tree.end();}
private:
	RBTree<K, K, SetofKey> tree;
};

但是这么实现存在一个问题,我们知道set内部的数据存在很紧密的关系,如果随意修改内部数据的值的话就会导致关系出现错误使其不再成为一个搜索二叉树,比如说下面的代码:

int main()
{
	set<int> s;
	s.insert(1);
	s.insert(2);
	s.insert(3);
	s.insert(4);
	set<int>::iterator it = s.begin();
	cout << *it << endl;
	*it = 10;
	cout << *it << endl;
	return 0;
}

这段代码的运行结果如下:
详解c++---map和set的封装_第8张图片
对吧不管是const迭代器还是普通迭代器对于set来说内部的数据都不能修改,所以本质上来说就不应该存在普通版本的迭代器,但是库中有啊我们平时的使用习惯也存在普通迭代器,那这里该如何进行修改呢?但是用const迭代器封装set的普通迭代器,表面上看这是一个普通迭代器但是实际上这是一个const迭代器,但是这样修改又会出现另外一个问题,之前普通版本的begin和end函数都返回的都是红黑树的普通迭代器,但是经过上面的修改现在的返回值的类型都变成了const迭代器,所以这里就会出现问题普通迭代器没有办法转换成为const迭代器,那如何解决呢?但是将普通版本的begin和end函数去掉即可,让类中只存在const版本的begin和end函数,这样在调用的时候返回的都是const版本的红黑树迭代器,那么这里的代码就如下:

template<class K>
class set
{
//...
public:
	typedef typename RBTree<K, K, SetofKey>::const_iterator iterator;
	typedef typename RBTree<K, K, SetofKey>::const_iterator const_iterator;
	iterator begin() const{return tree.begin();}
	iterator end() const{return tree.end();}
	bool insert(const K& key){return tree.insert(key);}
	bool find(const K& key){return tree.find(key);}
	bool check(){return tree.IsBalance();}
private:
	RBTree<K, K, SetofKey> tree;
};

经过这样的修改上面的测试代码就无法正常运行:
详解c++---map和set的封装_第9张图片
map容器就不能像上面一样将两个类型的迭代器都封装成为const迭代器,因为map允许用户修改每个数据中的V值,所以map中的const迭代器就是红黑树中的const迭代器,普通迭代器就是红黑树中的普通迭代器,并且得提供两个不同版本的begin和end函数,那么这里的代码就如下:

template<class K, class V>
class map
{
//....
public:
	typedef typename RBTree<K, pair<const K, V>, MapofKey>::iterator iterator;
	typedef typename RBTree<K, pair<const K, V>, MapofKey>::const_iterator const_iterator;
	iterator begin(){return tree.begin();}
	iterator end(){return tree.end();}
	const_iterator begin() const{return tree.begin();}
	const_iterator end() const{return tree.end();}
	bool insert(const pair<K,V>& data) {return tree.insert(data);}
	bool find(const K& key)	{return tree.find(key);}
private:
	RBTree<K, pair<const K, V>, MapofKey> tree;
};

我们就可以用下面的代码来进行测试:

int main()
{
	map<string, int> m;
	m.insert(make_pair("a", 1));
	m.insert(make_pair("b", 2));
	m.insert(make_pair("c", 3));
	m.insert(make_pair("d", 4));
	map<string, int>::iterator it = m.begin();
	cout << it->second << endl;
	it->second = 10;
	cout << it->second << endl;
	return 0;
}

这段代码的运行结果如下:
详解c++---map和set的封装_第10张图片

方括号的实现

要实现方括号的话我们就得修改insert函数的返回值让其返回pair,那么外部map和set容器中的insert函数如下:

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

然后再对红黑树里面的insert函数进行修改,因为pair里面的迭代器指向的是插入数据的地址或者是已经存在的元素的地址,又因为insert函数里面的cur变量会随着红黑树的调整而发生更改,所以我们找到插入的位置之后就可以创建一个变量来记录当前的位置,这样在返回值的时候就好返回,如果当前的元素不存在第二个元素的值就为true,如果当前插入的值存在的话第二个元素的值就为false,那么这里修改之后的代码就如下:

	pair<iterator,bool> insert(const V& _data)
	{
		if (root == nullptr)
		{
			root = new Node(_data);
			root->_col = BLACK;
			return make_pair(iterator(root), true);
		}
		KeyofT kot;
		Node* _cur = root;
		Node* _parent = nullptr;
		while (_cur != nullptr)
		{
			if (kot(_cur->data) > kot(_data))
			{
				_parent = _cur;
				_cur = _cur->left;
			}
			else if (kot(_cur->data) < kot(_data))
			{
				_parent = _cur;
				_cur = _cur->right;
			}
			else
			{
				return make_pair(iterator(_cur),false);
			}
		}
		_cur = new Node(_data);
		Node* newnode = _cur;
		//节点的调整
		//.....
		return make_pair(iterator(newnode)true);
	}

经过这个修改之后我们就可以实现方括号重载函数,首先创建一个pair来接收insert函数的返回值,然后再将pair中的第一个元素的第二个元素进行返回即可,那么这里的代码如下:

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

但是这么改会出问题因为set的insert函数返回的pair里面是const迭代器,而红黑树的insert函数返回的pair里面是普通迭代器,普通迭代器无法转换成为const迭代器所以这里就会出现问题,那么库中解决的方法就是通过先接收红黑树的插入函数的返回值比如说下面的代码:

pair<iterator,bool> insert(const K& key)
{
	pair<typename RBTree<K, K, SetofKey>::iterator, bool> ret = tree.insert(key);
}

然后我们就要实现一个函数这个函数的功能就是能用一个普通迭代器构造出const迭代器即可,那么这里的创建方式就是下面这样:

template<class V,class Ref,class Ptr>
struct _RBTreeIterator
{
	typedef RBTreeNode<V> Node;
	typedef _RBTreeIterator<V, Ref, Ptr> self;
	typedef _RBTreeIterator<V, V&, V*> iterator;
	Node* _node;
	_RBTreeIterator( Node* node)
		:_node(node)
	{}
	_RBTreeIterator(const iterator& s)
		:_node(s._node)
	{}
//....
};

我们创建了一个新的类型iterator,这个类型是该迭代器的重命名,但是这个迭代器的参数是V也就是说iterator永远都是一个普通迭代器,我们还添加了一个构造函数但是这个构造函数的参数是普通迭代器,如果当前的迭代器是普通迭代器的话这个函数就是拷贝构造函数,如果当前的迭代器是const迭代器的话,这个函数的意思就是用普通迭代器来构造const迭代器,那么我们可以用下面的代码来进行验证:

int main()
{
	map<string,  int> m;
	m.insert(make_pair("a", 1));
	m.insert(make_pair("b", 2));
	m.insert(make_pair("c", 3));
	m.insert(make_pair("d", 4));
	map<string, int>::const_iterator it = m.begin();//编译器优化
	cout << it->second << endl;
	it->second = 10;
	cout << it->second << endl;
	return 0;
}

这里将普通迭代器赋值给了一个const迭代器,然后const迭代器想要对值进行修改,这段代码的运行结果如下:
详解c++---map和set的封装_第11张图片
很明显这段代码是错的,但是报错的原因是const迭代器不能进行修改而不是普通迭代器构造const迭代器出现错误,那么这就说明我们的代码实现的是正确的,最后用下面的代码来进行测试:

int main()
{
	string arr[] = { "苹果","苹果", "苹果", "苹果", "苹果",
				  "香蕉","香蕉", "香蕉", "香蕉", "香蕉",
					"西瓜","西瓜", "西瓜", "梨子", "梨子" };
	map<string, int> m;
	for (auto ch : arr)
	{
		m[ch]++;
	}
	for (auto ch : m)
	{
		cout << ch.first << ":" << ch.second << endl;
	}
	return 0;
}

代码运行的结果如下:
详解c++---map和set的封装_第12张图片
符合我们的预期,那么这就说明我们的代码实现的没有问题,完整的代码如下:
RBTree.h文件的代码如下:

#pragma once
#include
using namespace std;
enum colour
{
	RED,
	BLACK,
};
template<class T>
struct RBTreeNode
{
	RBTreeNode(const T& _data)
		:parent(nullptr)
		, right(nullptr)
		, left(nullptr)
		, data(_data)
		, _col(RED)
	{}
	RBTreeNode<T>* parent;
	RBTreeNode<T>* right;
	RBTreeNode<T>* left;
	T data;
	colour _col;
};
template<class V,class Ref,class Ptr>
struct _RBTreeIterator
{
	typedef RBTreeNode<V> Node;
	typedef _RBTreeIterator<V, Ref, Ptr> self;
	typedef _RBTreeIterator<V, V&, V*> iterator;
	Node* _node;

	_RBTreeIterator( Node* node)
		:_node(node)
	{}
	_RBTreeIterator(const iterator& s)
		:_node(s._node)
	{}
	self& operator++()
	{
		if (_node->right)
		{
			Node* mine = _node->right;
			while (mine->left)
			{
				mine = mine->left;
			}
			_node = mine;
		}
		else
		{
			Node* parent = _node->parent;
			Node* cur = _node;
			while (parent != nullptr && parent->right == cur)
			{
				cur = parent;
				parent = parent->parent;
			}
			_node = parent;
		}
		return *this;
	}
	Ref operator--()
	{
		if (_node->left)//左边不为空
		{
			Node* mine = _node->left;
			while (mine->right)
			{
				mine = mine->right;
			}
			_node = mine;
		}
		else//左边为空
		{
			Node* _parent = _node->parent;
			Node* cur = _node;
			while (_parent != nullptr&&_parent->left == cur )
			{
				cur = _parent;
				_parent = _parent->parent;
			}
			_node = _parent;
		}
		return *this;
	}
	Ref operator*()
	{
		return _node->data;
	}
	Ptr operator->()
	{
		return &_node->data;
	}
	bool operator !=(const self& s)
	{
		return _node != s._node;
	}
};
template<class K, class V, class KeyofT>
class RBTree
{

public:
	typedef RBTreeNode<V> Node;

	typedef _RBTreeIterator<V, V&, V*> iterator;
	typedef _RBTreeIterator<V,const V&,const V*> const_iterator;
	RBTree()
		:root(nullptr)
	{}
	iterator begin()
	{
		Node* cur = root;
		while (cur->left)
		{
			cur = cur->left;
		}
		return iterator(cur);
	}
	iterator end()
	{
		return iterator(nullptr);
	}
	pair<iterator,bool> insert(const V& _data)
	{
		if (root == nullptr)
		{
			root = new Node(_data);
			root->_col = BLACK;
			return make_pair(iterator(root), true);
		}
		KeyofT kot;
		Node* _cur = root;
		Node* _parent = nullptr;
		while (_cur != nullptr)
		{
			if (kot(_cur->data) > kot(_data))
			{
				_parent = _cur;
				_cur = _cur->left;
			}
			else if (kot(_cur->data) < kot(_data))
			{
				_parent = _cur;
				_cur = _cur->right;
			}
			else
			{
				return make_pair(iterator(_cur),false);
			}
		}
		_cur = new Node(_data);
		Node* newnode = _cur;
		if (kot(_data) > kot(_parent->data))
			//如果插入的数据比parent的数据大
		{
			_parent->right = _cur;
			_cur->parent = _parent;
		}
		else
			//如果插入的数据比parent的数据笑
		{
			_parent->left = _cur;
			_cur->parent = _parent;
		}
		while (_parent != nullptr && _parent->_col == RED)
		{
			Node* _grandparent = _parent->parent;
			if (_grandparent->left == _parent)
				//祖先节点的左边要调整
			{
				Node* uncle = _grandparent->right;//parent在左边所以uncle在右边
				//情况一
				if (uncle != nullptr && uncle->_col == RED)
				{
					uncle->_col = _parent->_col = BLACK;
					if (_grandparent->parent != nullptr)
					{
						_grandparent->_col = RED;
					}
					_cur = _grandparent;
					_parent = _grandparent->parent;
				}
				else
				{
					//情况二
					if (_cur == _parent->left)
					{
						RototalR(_grandparent);
						_parent->_col = BLACK;
						_grandparent->_col = RED;
					}
					else//情况三
					{
						RototalL(_parent);
						RototalR(_grandparent);
						_cur->_col = BLACK;
						_grandparent->_col = RED;
					}
					break;
				}
			}
			else
				//祖先节点的右边要调整
			{
				Node* uncle = _grandparent->left;//parent在右边所以uncle在左边
				//情况一
				if (uncle != nullptr && uncle->_col == RED)
				{
					uncle->_col = _parent->_col = BLACK;
					if (_grandparent->parent != nullptr)
					{
						_grandparent->_col = RED;
					}
					_cur = _grandparent;
					_parent = _grandparent->parent;
				}
				else
				{
					//情况二
					if (_cur == _parent->right)
					{
						RototalL(_grandparent);
						_parent->_col = BLACK;
						_grandparent->_col = RED;
					}
					else//情况三
					{
						RototalR(_parent);
						RototalL(_grandparent);
						_cur->_col = BLACK;
						_grandparent->_col = RED;
					}
					break;
				}
			}
		}
		return make_pair(iterator(newnode),true);
	}
	void inorder()
	{
		_inorder(root);
	}
	bool find(const K& val)
	{
		Node* cur = root;
		KeyofT kot;
		while (cur)
		{
			if ( kot(cur->data) == val)
			{
				return true;
			}
			else if (kot(cur->data) > val)
			{
				cur = cur->left;
			}
			else
			{
				cur = cur->right;
			}
		}
		return false;
	}
	bool check(Node* root, int BlackNums, int ref)
	{
		if (root == nullptr)
		{
			if (ref != BlackNums)
			{
				cout << "违反规则" << endl;
				return false;
			}
			return true;
		}
		if (root->_col == RED && root->parent->_col == RED)
		{
			cout << "出现了连续红色节点" << endl;
		}
		if (root->_col == BLACK)
		{
			BlackNums++;
		}
		return check(root->left, BlackNums, ref) && check(root->right, BlackNums, ref);
	}
	bool IsBalance()
	{
		if (root == nullptr)
		{
			return true;
		}
		if (root->_col != BLACK)
		{
			return false;
		}
		int ref = 0;
		Node* _left = root;
		while (_left)
		{
			if (_left->_col == BLACK)
			{
				ref++;
			}
			_left = _left->left;
		}
		return check(root, 0, ref);

	}
private:
	void _inorder(const Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_inorder(root->left);
		cout << root->key << " ";
		_inorder(root->right);
	}
	void RototalL(Node* _parent)
	{
		Node* subR = _parent->right;//右孩子的节点
		Node* subRL = subR->left;//右孩子的左节点
		Node* ppNode = _parent->parent;//祖父节点
		//把subRL放到_parent的右
			_parent->right = subRL;
		if (subRL)
		{
			//如果subRL不为空则修改父节点的指向
				subRL->parent = _parent;
		}
		//把_parent放到subR的左
			subR->left = _parent;
		//修改_parent的parent的指向
			_parent->parent = subR;
		if (ppNode)//如果祖父不为空,则要改变祖父的指向
		{
			if (ppNode->right == _parent)
			{
				ppNode->right = subR;
				subR->parent = ppNode;
			}
			else//如果_parent是祖父的左
			{
				ppNode->left = subR;
				subR->parent = ppNode;
			}
		}
		else//祖父为空节点说明当前调整的是根节点
		{
			root = subR;
			subR->parent = nullptr;
		}
	}
	//右旋转
	void RototalR(Node* _parent)
	{
		Node* subL = _parent->left;
		Node* subLR = subL->right;
		Node* ppNode = _parent->parent;
		_parent->left = subLR;
		if (subLR)
		{
			subLR->parent = _parent;
		}
		subL->right = _parent;
		_parent->parent = subL;
		if (ppNode != nullptr)
		{
			if (ppNode->right == _parent)
			{
				ppNode->right = subL;
				subL->parent = ppNode;
			}
			else
			{
				ppNode->left = subL;
				subL->parent = ppNode;
			}
		}
		else
		{
			root = subL;
			subL->parent = nullptr;
		}
	}

	Node* root;
};

set.h文件的代码如下:

#pragma once
#include"RBTree.h"
template<class K>
class set
{
	struct SetofKey
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
public:

	typedef typename RBTree<K, K, SetofKey>::const_iterator iterator;
	typedef typename RBTree<K, K, SetofKey>::const_iterator const_iterator;
	iterator begin() const
	{
		return tree.begin();
	}
	iterator end() const
	{
		return tree.end();
	}
	pair<iterator,bool> insert(const K& key)
	{
		pair<typename RBTree<K, K, SetofKey>::iterator, bool> ret = tree.insert(key);
	}
	bool find(const K& key)
	{
		return tree.find(key);
	}
	bool check()
	{
		return tree.IsBalance();
	}
private:
	RBTree<K, K, SetofKey> tree;
};

map.h文件的大代码如下:

#pragma once
#include"RBTree.h"
template<class K, class V>
class map
{
	struct MapofKey
	{
		const K& operator()(const pair<const K, V>& kv)
		{
			return kv.first;
		}
	};
public:
	typedef typename RBTree<K, pair<const K, V>, MapofKey>::iterator iterator;
	typedef typename RBTree<K, pair<const K, V>, MapofKey>::const_iterator const_iterator;
	iterator begin()
	{
		return tree.begin();
	}
	iterator end()
	{
		return tree.end();
	}
	const_iterator begin() const
	{
		return tree.begin();
	}
	const_iterator end() const
	{
		return tree.end();
	}
	pair<iterator,bool> insert(const pair<K,V>& data)
	{
		return tree.insert(data);
	}
	bool find(const K& key)
	{
		return tree.find(key);
	}
	V& operator[](const K& key)
	{
		pair<iterator, bool> ret = insert(make_pair(key, V()));
		return ret.first->second;
	}
private:
	RBTree<K, pair<const K, V>, MapofKey> tree;
};

你可能感兴趣的:(c++详解,c++,算法,开发语言)