C++set和map的使用+红黑树底层对map和set的封装

1.关联式容器

  不同与list,vector等序列式容器,关联式容器存的是<key,value>结构的键值对,它在检索时比序列式容器的效率更高。

2.键值对(pair)

  用来表示具有一一对应关系的结构,该结构一般只包含key和value两个成员变量。key代表键值,value表示与key对应的信息。比如在英汉词典中,每个英文单词都对应有它的中文意思。

  在STL中定义如下:

template <class T1, class T2>
struct pair
{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair(): first(T1()), second(T2())
{}
pair(const T1& a, const T2& b): first(a), second(b)
{}
};

3.树形结构的关联式容器

  STL中实现了两种结构不同的关联式容器,一个是哈希结构,另一个是树形结构。其中树形结构的有:set,multiset,map,multimap。它们的底层使用红黑树实现的,它们的元素默认以key值进行升序排序。

3.1 set

1.set是按一定次序存储元素的容器。

2.它只有一个key值,每个key唯一不能重复,它和multiset一样没有重载[]。

3.传对象作为key值时需要传入比较方法。

注意:

1.因为只有key,所以不需要传键值对(pair)。

2.可以利用set元素不能重复的特性来进行去重。

3.set默认用小于比较,所以默认是升序。

4.它的查找效率为O(logn)。

5.它的key值不能被修改。因为它的底层是红黑树,随意修改会破坏它的结构。硬要修改可以先删除,再插入。

3.2 map

1.map的插入必须是键值对(pair),而pair是一个类,与一般的插入不一样,因为隐式类型的转换只支持单参数,而pair有两个成员变量。我们可以用以下方法来进行插入

其中直接用{n1,n2}是C++11的新特性,它是支持多参数的构造函数隐式类型转换。 make_pair是一个函数,在函数里面return 了一个实例化好的pair,一般还是建议使用make_pair比较好。

2.map中key的值不可以修改,而value可以修改。

3.map重载了[]操作符,可以用[key]的方式直接访问或者操作与key对应的value。

4.map默认也是小于比较,所以默认是升序,可以传入自定义的比较器。

3.3multiset

  不是很常用,与set最大的区别就是它可以有多个值相同的key。但是key依旧不可以修改。multiset在底层是用的<value,value>键值对。

3.4multimap

  它也是key可以有多个重复的,且key值依旧不能被修改。因为它的key相同但是value不同,导致同一个key值有不同value,所以它不支持[]访问和操作。

4.map和set的底层

  在stl中,map和set的底层是由红黑树实现的,但是它们的封装比较的复杂。

传入值:

  部分底层代码:

  首先我们可以看到 set其实是有value的,但是它的value的值也是K。map在传给红黑树的数据是一个<K,pair<const K,T(V)>>,set之所以是<K,K>是为了兼容。

  然后在rb_tree中treenode的类型模板是Value,也就是set就是K,map就是pair<const K,V>。

  最后在treenode中可以看到,我们传入的Value类型实例化的变量名是 value_field。也就是它既可以是一个值也可以是一个对象。

KeyOfValue:

  因为set和map共用同一个红黑树,所以它们需要经过封装才能达到共用一个红黑树。比如这个在rb_tree中有一个类型模板KeyOfValue,顾名思义就是将K转换成能比较的值。因为set传入的K可以直接比较,但map传入的K是一个pair,pair不能直接比较,要通过pair的first进行比较。所以底层实现的思路就是set的和map各实现一个仿函数类,然后传入到rb_tree中进行K的转换。

iterator重写的++和-- 

  关于map和set的封装还有一个难点就是重写++和--。

重写++(后置)

  首先是begin和end,begin返回的是最左侧的结点,而end由于我们的rbtree没有设置头结点,以end返回的是一个nullptr。当迭代器++时,核心因为是中序遍历,也就是左 根 右,同时要分两种情况。

代码实现:

Self& operator++()
		{
			if (_node->_right)//右子树不为空时,要找到右子树的最左结点
			{
				Node* subLeft = _node->_right;
				while (subLeft->_left)
				{
					subLeft = subLeft->_left;
				}
				_node = subLeft;
			}
			else//右子树为空时,就要开始往前回溯,直到找到左孩子是父亲的祖先
			{
				Node* cur = _node;
				Node* parent = cur->_parent;
				while (parent && parent->_right == cur)
				{
					cur = cur->_parent;
					parent = parent->_parent;
				}
				_node = parent;
			}
			return *this;
		}

重写--(后置)

  随着每次--,迭代器移动的顺序是 右  根  左。

 它也是两种情况,一是当左子树不为空时,找它的最左结点。二是当左子树为空时,开始回溯

  代码实现:

Self& operator--()
		{
			if (_node->_left)
			{
				Node* subRight = _node->_left;//左子树不为空,找到左子树的最右结点
				while (subRight->_right)
				{
					subRight = subRight->_right;
				}
				_node = subRight;
			}
			else
			{
				Node* cur = _node;
				Node* parent = _node->_parent;
				while (parent && parent->_left == cur)//往上回溯,直到parent为空或者parent的右孩子是cur
				{
					cur = cur->_parent;
					parent = parent->_parent;
				}
				_node = parent;
			}
			return *this;
		}

插入数据

  Map的插入

  我们知道,map插入的数据是一个pair,所以在map中的插入如下:

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

由于之前对迭代器的声明就给K加了const了,所以很容易的做到map中Key不可修改而Value可以修改。

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只有一个值,并且它的迭代器其实都是const。

typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;

Set中的插入是这样的

pair<iterator, bool> Insert(const K& key)
{
	pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);
	return pair<iterator, bool>(ret.first, ret.second);
}

上面的插入代码特别注意 ,函数内第一行的iterator ret是树中的普通迭代器,它用来接收树插入的返回值,返回值的pair中的迭代器是一个const迭代器。那么直接返回会导致返回类型不匹配而编译报错,那我们看看STL中的源码怎么处理。

  以上可以看出,当我们实例化的是一个const迭代器时,它直接把自己typedef成const_iterator,当我们用普通迭代器构造时,红框中的函数就是一个构造函数,它可以传一个普通迭代器来进行构造。当实例化的是一个普通迭代器时,这个函数就是一个拷贝构造毕竟拷贝构造其实也是构造函数的重载。(这里有点绕)

typename(补充)

  typename的作用是为了告诉编译这是一个里面的内嵌类型,而不是变量名或者函数名

按需编译(补充)

  当我们用泛型编程的时候,编译器对没有调用的函数不会进行编译,当这个函数有被调用才会进行编译,这就导致了这个函数如果没有被调用的话,里面就算有语法错误,编译器也可能不会检查出来。

Iterator

先看看部分代码:

template <class T,class Ptr,class Ref>
	struct _TreeIterator
	{
		typedef RBTreeNode<T> Node;
		typedef _TreeIterator<T,Ptr,Ref> Self;
		typedef _TreeIterator<T, T*, T&> Iterator;
		Node* _node;
		_TreeIterator(const Iterator& it)
			:_node(it._node)
		{}
		_TreeIterator(Node* node)
			:_node(node)
		{}
		Ref operator*()
		{
			return _node->_data;
		}
		Ptr operator->()
		{
			return &_node->_data;
		}

首先它有三个模板参数,我们可以再看看在树中它是如何传入模板参数的。

template <class K, class T,class KeyOfT>
	class RBTree
	{
		typedef RBTreeNode<T> Node;
		// 同一个类模板,传的不同的参数实例化出的不同类型
		typedef _TreeIterator<T,T*,T&> iterator;
		typedef _TreeIterator<T, const T*, const T&> const_iterator;

  可以看出它是通过对T(也就是Value)加const来声明区分普通迭代器和const迭代器。(这里树的模板参数是简化的,除了这些还有两个)

红黑树对map和set的封装模拟实现代码

namespace hzj
{
	enum Colour
	{
		RED,
		BLACK
	};

	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)
			, _col(RED)
		{}
	};

	template <class T,class Ptr,class Ref>
	struct _TreeIterator
	{
		typedef RBTreeNode<T> Node;
		typedef _TreeIterator<T,Ptr,Ref> Self;
		typedef _TreeIterator<T, T*, T&> Iterator;
		Node* _node;
		_TreeIterator(const Iterator& it)
			:_node(it._node)
		{}
		_TreeIterator(Node* node)
			:_node(node)
		{}
		Ref operator*()
		{
			return _node->_data;
		}
		Ptr operator->()
		{
			return &_node->_data;
		}
		bool operator!=(const Self& s)
		{
			return _node != s._node;
		}
		bool operator==(const Self& s)
		{
			return _node == s._node;
		}
		Self& operator--()
		{
			if (_node->_left)
			{
				Node* subRight = _node->_left;//左子树不为空,找到左子树的最右结点
				while (subRight->_right)
				{
					subRight = subRight->_right;
				}
				_node = subRight;
			}
			else
			{
				Node* cur = _node;
				Node* parent = _node->_parent;
				while (parent && parent->_left == cur)//往上回溯,直到parent为空或者parent的右孩子是cur
				{
					cur = cur->_parent;
					parent = parent->_parent;
				}
				_node = parent;
			}
			return *this;
		}
		Self& operator++()
		{
			if (_node->_right)//右子树不为空时,要找到右子树的最左结点
			{
				Node* subLeft = _node->_right;
				while (subLeft->_left)
				{
					subLeft = subLeft->_left;
				}
				_node = subLeft;
			}
			else//右子树为空时,就要开始往前回溯,直到找到左孩子是父亲的祖先
			{
				Node* cur = _node;
				Node* parent = cur->_parent;
				while (parent && parent->_right == cur)
				{
					cur = cur->_parent;
					parent = parent->_parent;
				}
				_node = parent;
			}
			return *this;
		}
	};

	template <class K, class T,class KeyOfT>
	class RBTree
	{
		typedef RBTreeNode<T> Node;
		// 同一个类模板,传的不同的参数实例化出的不同类型
		typedef _TreeIterator<T,T*,T&> iterator;
		typedef _TreeIterator<T, const T*, const T&> const_iterator;
	public:
		Node*& GetRoot()
		{
			return _root;
		}
		iterator begin()
		{
			Node* leftMin = _root;
			while (leftMin&&leftMin->_left)
			{
				leftMin = leftMin->_left;
			}
			return iterator(leftMin);
		}
		iterator end()//因为这个红黑树没有设置头结点,所以end返回空
		{
			return iterator(nullptr);
		}
		const_iterator begin() const
		{
			Node* leftMin = _root;
			while (leftMin && leftMin->_left)
			{
				leftMin = leftMin->_left;
			}
			return const_iterator(leftMin);
		}
		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->_left;
				}
				else if (key > kot(cur->_data))
				{
					cur = cur->_right;
				}
				else
				{
					return cur;
				}
			}
			return nullptr;
		}
		pair<iterator,bool> Insert(const T& data)//data有可能是一个值,也可能是一个pair
		{
			//第一次插入
			if (_root == nullptr)
			{
				_root = new Node(data);
				_root->_col = BLACK;
				return make_pair(iterator(_root),true);
			}
			Node* parent = nullptr;
			Node* cur = _root;
			KeyOfT kot;
			while (cur)//找到插入位置,记得记录parent
			{
				if (kot(data) < kot(cur->_data))
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (kot(data) > kot(cur->_data))
				{
					parent = cur;
					cur = cur->_right;
				}
				else
				{
					return make_pair(iterator(cur),false);
				}
			}
			cur = new Node(data);
			cur->_col = RED;//红黑树新插入的结点的颜色为红色
			Node* newnode = cur;
			//链接新节点
			if (kot(cur->_data) < kot(parent->_data))
			{
				parent->_left = cur;
			}
			else
			{
				parent->_right = cur;
			}
			cur->_parent = parent;
			//不能有连续的红节点,如果父亲是红结点,那么需要分情况调整
			while (parent && parent->_col == RED)
			{
				Node* grandfather = parent->_parent;
				if (parent == grandfather->_left)//parent在grandfather左边。
				{
					//那么此时u在grand的右边
					Node* uncle = grandfather->_right;
					//情况一:u存在且u为红色,那么要将u改黑,并且修改cur和parent继续调整。
					if (uncle && uncle->_col == RED)
					{
						parent->_col = uncle->_col = BLACK;
						grandfather->_col = RED;//
						cur = grandfather;
						parent = cur->_parent;
					}
					else//情况二:u不存在或者u为黑
					{
						if (cur == parent->_left)
						{
							RotateR(grandfather);
							parent->_col = BLACK;
							grandfather->_col = RED;
						}
						else
						{
							RotateL(parent);
							RotateR(grandfather);
							cur->_col = BLACK;
							grandfather->_col = RED;
						}
						break;
					}
				}
				else
				{
					Node* uncle = grandfather->_left;
					if (uncle && uncle->_col == RED)
					{
						parent->_col = uncle->_col = BLACK;
						grandfather->_col = RED;
						cur = grandfather;
						parent = cur->_parent;
					}
					else
					{
						if (cur == parent->_right)
						{
							RotateL(grandfather);
							parent->_col = BLACK;
							grandfather->_col = RED;
						}
						else
						{
							RotateR(parent);
							RotateL(grandfather);
							cur->_col = BLACK;
							grandfather->_col = RED;
						}
						break;
					}
				}
			}
			_root->_col = BLACK;
			return make_pair(iterator(newnode),true);
		}

		bool IsBalance()
		{
			return IsBalance(_root);
		}

		bool IsBalance(Node* root)
		{
			KeyOfT kot;
			if (root == nullptr) return true;
			if (root->_col == RED)
			{
				cout << kot(root->_data) << "头部颜色错误" << endl;
				return false;
			}
			int benchmark = 0;
			Node* cur = root;
			while (cur)
			{
				if (cur->_col == BLACK) benchmark++;
				cur = cur->_left;
			}
			return CheckColour(root, 0, benchmark);
		}

		int Height()
		{
			return Height(_root);
		}

		int Height(Node* root)
		{
			if (root == nullptr)
				return 0;

			int leftHeight = Height(root->_left);
			int rightHeight = Height(root->_right);

			return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
		}

		bool CheckColour(Node* root, int blacknum, int benchmark)
		{
			if (root == nullptr)
			{
				if (blacknum != benchmark)
				{
					cout << "路径长度错误" << endl;
					return false;
				}
				return true;
			}
			if (root->_col == BLACK) blacknum++;
			if (root->_col == RED && root->_parent->_col == RED)
			{
				KeyOfT kot;
				cout << kot(root->_data) << "出现了连续的红色结点" << endl;
				return false;
			}
			return CheckColour(root->_left, blacknum, benchmark) && CheckColour(root->_right, blacknum, benchmark);
		}

	private:
		void RotateL(Node* parent)
		{

			Node* cur = parent->_right;
			Node* curleft = cur->_left;
			parent->_right = curleft;
			if (curleft)
			{
				curleft->_parent = parent;
			}

			cur->_left = parent;

			Node* ppnode = parent->_parent;

			parent->_parent = cur;


			if (parent == _root)
			{
				_root = cur;
				cur->_parent = nullptr;
			}
			else
			{
				if (ppnode->_left == parent)
				{
					ppnode->_left = cur;
				}
				else
				{
					ppnode->_right = cur;

				}

				cur->_parent = ppnode;
			}
		}

		void RotateR(Node* parent)
		{
			Node* cur = parent->_left;
			Node* curright = cur->_right;

			parent->_left = curright;
			if (curright)
				curright->_parent = parent;

			Node* ppnode = parent->_parent;
			cur->_right = parent;
			parent->_parent = cur;

			if (ppnode == nullptr)
			{
				_root = cur;
				cur->_parent = nullptr;
			}
			else
			{
				if (ppnode->_left == parent)
				{
					ppnode->_left = cur;
				}
				else
				{
					ppnode->_right = cur;
				}

				cur->_parent = ppnode;
			}
		}	
		Node* _root = nullptr;
	};
}

总结

  另外红黑树中也有一个Compare比较器,默认是Less小于比较器。

  总之STL中是通过各种复杂的操作来实现Set和Map的封装的,为了解决兼容性问题,往往都是解决了一个问题可能又会诞生新的问题,生活也是如此,问题会伴随我们一生,以平常心直面问题才能走得更远。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值