set和map + multiset和multimap(使用+封装(RBTree))

前言

本文介绍的是树型关联式容器。
关联式容器:用来存储数据,存储的是<key, value>结构的键值对,在检索时效率更高。主要有这四种:map,set,multimap,multiset。

键值对:用来标识具有一一对应关系的结构,该结构一般包含两个成员变量key和value,key表示键值,value表示与key对应的信息。

SGI—STL中关于键值对的定义:

//pair底层
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)
	{}
};

一、使用

1. set

(1)、模板参数列表

模板参数列表

(2)、常见构造

void test_constructor()
{
	set<int> s1;                            //无参构造

	int arr[] = { 10,20,30,40,50 };
	set<int> s2(arr, arr + 5);              //数组范围构造
	set<int> s3(s2.begin(), s2.end());      //迭代器区间构造

	set<int> s4(s3);                        //拷贝构造
}

(3)、find和count

void test_find()
{
	set<int> s;
	s.insert(5);
	s.insert(10);
	s.insert(8);
	s.insert(2);

	//find return value: iterator(val is found)
	//otherwise set::end 
	if (s.find(5) != s.end())
	{
		cout << "find:找到了" << endl;      
	}

	//count return value:1 (val is found),or 0 otherwise
	if (s.count(5))
	{
		cout << "count:找到了" << endl;     
	}
}

(4)、insert和erase

void test_modify()
{
	//insert
	//去重+排序
	set<int> s;
	s.insert(10);
	s.insert(5);
	s.insert(6);
	s.insert(5);
	s.insert(5);
	s.insert(7);
	s.insert(2);

	for (auto e : s)
	{
		cout << e << " ";     //2 5 6 7 10
	}
	cout << endl;

	//迭代器
	set<int>::iterator sit;
	pair<set<int>::iterator, bool> ret;   //接收插入返回值

	//pair<iterator, bool> insert(const value_type & val);        insert参数列表
	ret = s.insert(1);
	if (ret.second == true)
		sit = ret.first;
	cout << *sit << endl;    //1
	
	ret = s.insert(1);
	if (ret.second == false) 
		sit = ret.first;
	cout << *sit << endl;    //1


	//iterator insert(iterator position, const value_type & val);  insert参数列表
	sit = s.insert(sit, 20);
	cout << *sit << endl;   //20

	//	template <class InputIterator>
	//void insert(InputIterator first, InputIterator last);        insert参数列表
	int arr[] = { 0,10,15 };            // 10 already in set, not inserted
	s.insert(arr, arr + 3);
	for (auto e : s)
	{
		cout << e << " ";     //0 1 2 5 6 7 10 15 20
	}
	cout << endl;

	///
	//erase

	//void erase(iterator position);               erase参数列表
	s.erase(sit);    //*sit = 20

	//size_type erase(const value_type & val);     erase参数列表
	int e_ret = s.erase(0);
	cout << e_ret << endl;
	for (auto e : s)
	{
		cout << e << " ";     //1 2 5 6 7 10 15
	}
	cout << endl;

	//void erase(iterator first, iterator last);   erase参数列表
	s.erase(s.begin(), s.end());
	for (auto e : s)
	{
		cout << e << " ";     //empty
	}
	cout << endl;
}

(5)、iterator

void test_iteator()
{
	int arr[] = { 10,20,30,40,50 };
	set<int> s(arr, arr + 5);

	set<int>::iterator it = s.begin();
	set<int>::const_iterator cit = s.cbegin();
	set<int>::reverse_iterator rit = s.rbegin();
	set<int>::const_reverse_iterator crit = s.crbegin();

	while (it != s.end())
	{
		cout << *it << " ";   //10 20 30 40 50
		it++;
	}
	cout << endl;

	while (cit != s.cend())
	{
		cout << *cit << " ";   //10 20 30 40 50
		cit++;
	}
	cout << endl;

	while (rit != s.rend())
	{
		cout << *rit << " ";   //50 40 30 20 10
		rit++;
	}
	cout << endl;

	while (crit != s.crend())
	{
		cout << *crit << " ";  //50 40 30 20 10
		crit++;
	}
	cout << endl;
}

(6)、lower_bound和upper_bound

//iterator lower_bound(const value_type & val) const;                  lower_bound的声明
//iterator upper_bound(const value_type & val) const;                  upper_bound的声明
//pair<iterator, iterator> equal_range(const value_type& val) const;   equal_range的声明
void test_bound()
{
	set<int> s;
	set<int>::iterator itlow, itup;

	for (size_t i = 1; i < 10; i++)
	{
		s.insert(i * 10);               //10 20 30 40 50 60 70 80 90
	}


	//左闭右开[30,70)
	itlow = s.lower_bound(30);
	itup = s.upper_bound(60);

	cout << *itlow << endl;        //30
	cout << *itup << endl;         //70


	set<int>::iterator it = s.lower_bound(35);
	//   s > 35
	cout << *it << endl;           //40
	//*it = 50;     //不能修改,保护键值

	s.erase(itlow, itup);

	for (auto e : s)
	{
		cout << e << " ";             //10 20 70 80 90
	}
	cout << endl;

//
	//equal_range - most_use multiset

	//pair<set<int>::const_iterator, set<int>::const_iterator> 
	auto ret1 = s.equal_range(15);
	itlow = ret1.first;
	itup = ret1.second;
	//因为不存在15,所以itlow和itup是一段不存在的区间
	cout << *itlow << endl;      //20   
	cout << *itup << endl;       //20   左开右闭


	auto ret2 = s.equal_range(80);
	itlow = ret2.first;
	itup = ret2.second;
	//[80,90)
	cout << *itlow << endl;     //80   
	cout << *itup << endl;      //90

	auto ret = s.equal_range(90);
	itlow = ret.first;
	itup = ret.second;
	//程序直接崩溃,到最后了
	cout << *itlow << endl;   //90     
	cout << *itup << endl;    //end()
}

2. multiset

这里的演示就不和set一样分开表示了,主要是multiset不去重

void test_multiset()
{
	//排序
	int arr[] = { 7, 7, 7, 3, 6, 5, 2, 3, 3, 3 };
	multiset<int> s(arr, arr + sizeof(arr) / sizeof(arr[0]));

	//不去重
	for (auto& e : s)
	{
		cout << e << " ";
	}
	cout << endl;

	multiset<int>::iterator pos = s.find(3);  //返回中序遍历的第一个3
	while (pos != s.end())           //find失败返回end()
	{
		//*pos = 10;    //err  不能修改,保护键值
		cout << *pos << " ";
		++pos;
	}
	cout << endl;

	cout << s.count(3) << endl;   //4个3



	pair<multiset<int>::iterator, multiset<int>::iterator> ret = s.equal_range(7);
	multiset<int>::iterator itlow = ret.first;
	multiset<int>::iterator itup = ret.second;

	// [itlow, itup)
	// [7,end())  s.equal_range(7);
	//cout << *itlow << endl;
	//cout << *itup << endl;      //error   *itup没有值

	// [itlow, itup)
	// [5,5)  s.equal_range(4); 
	ret = s.equal_range(4);
	itlow = ret.first;
	itup = ret.second;
	cout << *itlow << endl;
	cout << *itup << endl;       //ok

	s.erase(itlow, itup);   //没有进行删除   [5, 5)

	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;
}

3. map

(1)、模板参数列表

模板参数列表

(2)、构造

void test_constructor()
{
	map<string, string> dict;
	//"insert"和"插入"都分别有隐式类型转换,const char* 转成const string&
	//不能直接进行隐式类型转换,原因:多参数
	pair<string, string> kv1("insert", "插入");
	dict.insert(kv1);

	dict.insert(pair<string, string>("sort", "排序"));  //匿名对象

	//常用
	// C++98
	dict.insert(make_pair("string", "字符串"));
	// C++11 多参数的构造函数隐式类型转换
	dict.insert({ "string", "字符串" });   //{}会自动调用pair的构造

	// 隐式类型的转换  构造+拷贝构造(优化)
	string str1 = "hello";
	pair<string, string> kv2 = { "string", "字符串" };

	//const pair<string, string>& kv2 = { "string", "字符串" };  引用一个临时变量
}

(3)、modifiers和operations

void test_modifiers()
{
	map<string, string> dict;
	dict.insert(make_pair("string", "字符串"));
	dict.insert(make_pair("insert", "插入"));
	dict.insert(make_pair("success", "成功"));

	//key已经有了就不会插入    //pair<iterator,bool> insert (const value_type& val);  插入的参数列表
	pair<map<string, string>::iterator, bool> ret = dict.insert(make_pair("insert", "xxx"));
	if (ret.second == false) {
		cout << "element already existed:";
		cout << ret.first->first << ":" << ret.first->second << endl;
	}

	//iterator insert (iterator position, const value_type& val);                      插入的参数列表
	map<string, string>::iterator it = dict.begin();
	it = dict.insert(it, make_pair("begin", "开始"));  // max efficiency inserting
	cout << it->first << ":" << it->second << endl;


	/*template <class InputIterator>
	void insert(InputIterator first, InputIterator last);*/                             //插入的参数列表
	map<string, string> copymap;
	//iterator find (const key_type& k); //查找的参数列表,返回值是查找这个位置的迭代器,如果没查找到,返回end()
	copymap.insert(dict.begin(), dict.find("success"));        


	it = dict.begin();
	while (it != dict.end())
	{
		//it->first = "xxx";   //error
		//it->second = "sss";  //ok

		//cout << (*it).first << ":" << (*it).second << endl;
		cout << it->first << ":" << it->second << endl;
		++it;
	}
	cout << endl;


	int number = dict.erase("sucess");
	cout << number << endl;

	for (const auto& e : dict)
	{
		cout << e.first << ":" << e.second << endl;
	}
}

(4)、operator[]

注意key不存在,operator[]是插入,at是抛异常

void test_operator()
{
	string arr[] = { "西瓜", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
	map<string, int> countMap;
	for (const auto& e : arr)
	{
		auto it = countMap.find(e);
		if (it == countMap.end())
		{
			countMap.insert(make_pair(e, 1));   //首次插入
		}
		else
		{
			it->second++;                       //统计次数
		}

	}

	//pair<map<char, int>::iterator, bool>::iterator it = this->insert(make_pair(k,mapped_type()))
	//(*(it.first)).second
	for (const auto& e : arr)
	{
		//查找e是否存在,如果不存在进行插入,如果存在,返回value
		countMap[e]++;
	}

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

4. multimap

multimap和map的唯一不同,前者的key可以重复

二、封装

set和map的封装的底层结构使用的都是红黑树(这篇博客介绍了红黑树的旋转),在STL中set底层实际存储的数据是键值对< value, value >,这样就可以调用同个红黑树。

RBTree

迭代器原理

红黑树

双向迭代器 -> 根据红黑树的特征:

  1. 迭代器++,只需要判断当前位置的右侧节点的情况
  2. 迭代器- -,只需要判断当前位置的左侧节点的情况
  1. 迭代器++
  1. 右孩子不为空,访问右子树的最左节点(最小节点)。
  2. 右孩子为空,下一个访问的是孩子是父亲左的祖先节点。

代码实现:

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 && cur == parent->_right)
		{
			cur = parent;
			parent = parent->_parent;
		}
		
		//parent为空的情况和找到下一个节点的情况
		_node = parent;
	}
	return *this;
}
  1. 迭代器–
  1. 左孩子不为空,访问左子树的最右节点(最大节点)。
  2. 左孩子为空,下一个访问的是孩子是父亲右的祖先节点。
Self& operator--()
{
	//左孩子不为空
	if (_node->_left)
	{
		Node* subRight = _node->_left;
		while (subRight->_right)
		{
			subRight = subRight->_right;
		}

		_node = subRight;
	}
	else  //左孩子为空
	{ 
		//孩子是父亲右的那个节点
		Node* cur = _node;
		Node* parent = cur->_parent;
		
		while (parent && cur == parent->_left)
		{
			cur = parent;
			parent = parent->_parent;
		}
		
		//parent为空的情况和找到下一个节点的情况
		_node = parent;
	}
	return *this;
}

RBTree实现代码

//节点的颜色
enum Color
{
	RED,
	BLACK
};

//这里一个模板参数T就可以,这个T既是set的key,也是map的value
template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	T _data;
	Color _color;

	RBTreeNode(const T& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
		, _color(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;
	
	//这个类被实列化成const迭代器时,这个函数是一个构造,支持普通迭代器构造const迭代器
	//这个类被实列化成普通迭代器时,这个函数是一个拷贝构造
	__TreeIterator(const Iterator& it)
		:_node(it._node)
	{}

	Node* _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;
	}

	Self& operator--()
	{
		if (_node->_left)
		{
			Node* subRight = _node->_left;
			while (subRight->_right)
			{
				subRight = subRight->_right;
			}

			_node = subRight;
		}
		else  
		{ 
			//孩子是父亲右的那个节点
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_left)
			{
				cur = parent;
				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 && cur == parent->_right)
			{
				cur = parent;
				parent = parent->_parent;
			}
			//parent为空的情况和找到下一个节点的情况
			_node = parent;
		}
		return *this;
	}
};


//set->RBTree<K, K, SetKeyOfT> _t;
//map->RBTree<K, pair<K, V>, MapKeyOfT> _t;

//KeyOfT是上层传下来的仿函数
template<class K, class T, class KeyOfT>
class RBTree
{
	typedef RBTreeNode<T> Node;
public:
	typedef __TreeIterator<T, T*, T&> iterator;
	typedef __TreeIterator<T, const T*, const T&> const_iterator;

	iterator begin()
	{
		Node* leftMin = _root;
		while (leftMin && leftMin->_left)
		{
			leftMin = leftMin->_left;
		}
		return iterator(leftMin);
	}
	iterator end()
	{
		//区分,这里和STL源码中的结束方式不同,
		return iterator(nullptr);
	}

	const_iterator begin() const
	{
		Node* leftMin = _root;
		while (leftMin && leftMin->_left)
		{
			leftMin = leftMin->_left;
		}
		return iterator(leftMin);
	}
	const_iterator end() const
	{
		return iterator(nullptr);
	}


	//传K的作用
	Node* Find(const K& key)
	{
		Node* cur = _root;

		KeyOfT kot;
		while (cur)
		{
			if (kot(cur->_data) < key)
			{
				cur = cur->_right;
			}
			else if (kot(cur->_data) > key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}

		//没找到,返回nullptr
		return nullptr;
	}

	//注意insert的返回值是一个键值对
	pair<iterator, bool> Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_color = BLACK;
			return make_pair(iterator(_root), true);
		}

		//寻找要链接新节点的位置
		Node* parent = nullptr;
		Node* cur = _root;

		KeyOfT kot;
		while (cur)
		{
			if (kot(cur->_data) < kot(data))
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (kot(cur->_data) > kot(data))
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return make_pair(iterator(cur), false);
			}
		}

		//插入节点 + 链接
		cur = new Node(data);
		cur->_color = RED;

		//保存节点,用于返回
		Node* newnode = cur;

		if (kot(parent->_data) > kot(data))
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}

		cur->_parent = parent;


		//调整   这里parent是否为空,是为了下一次循环判断
		//       如果parent->_color == BLACK也不用玩了
		while (parent && parent->_color == RED)
		{
			Node* grandfather = parent->_parent;
			if (grandfather->_left == parent)
			{
				Node* uncle = grandfather->_right;
				//u为红
				if (uncle && uncle->_color == RED)
				{
					parent->_color = uncle->_color = BLACK;
					grandfather->_color = RED;

					//继续向上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else //u不存在 或 存在且为黑
				{
					if (cur == parent->_left)
					{
						//      g
						//   p
						//c
						RotateR(grandfather);
						parent->_color = BLACK;
						grandfather->_color = RED;
					}
					else
					{
						//      g
						//   p
						//      c	
						RotateL(parent);
						RotateR(grandfather);
						cur->_color = BLACK;
						grandfather->_color = RED;
					}

					//调整完之后,就不需要继续改变了
					break;
				}
			}
			else   //grandfather->_right == parent
			{
				Node* uncle = grandfather->_left;
				//u为红
				if (uncle && uncle->_color == RED)
				{
					parent->_color = uncle->_color = BLACK;
					grandfather->_color = RED;

					//继续向上调整
					cur = grandfather;
					parent = cur->_parent;
				}
				else //u不存在 或 存在且为黑
				{
					if (cur == parent->_right)
					{
						//g
						//   p
						//      c
						RotateL(grandfather);
						parent->_color = BLACK;
						grandfather->_color = RED;
					}
					else
					{
						//g
						//   p
						//c	
						RotateR(parent);
						RotateL(grandfather);
						cur->_color = BLACK;
						grandfather->_color = RED;
					}

					//调整完之后,就不需要继续改变了
					break;
				}
			}
		}

		//根节点的颜色改成黑色
		_root->_color = BLACK;

		return make_pair(iterator(newnode), true);
	}

	//判断该树是不是红黑树
	bool IsBalance()
	{
		return _IsBalance(_root);
	}

	//计算红黑树的高度
	int Height()
	{
		return Height(_root);
	}


private:
	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 CheckColor(Node* root, int blacknum, int benchmark)
	{
		if (root == nullptr)
		{
			if (blacknum != benchmark)
			{
				return false;
			}
			return true;
		}

		//计算每条路径的黑色节点
		if (root->_color == BLACK)
		{
			++blacknum;
		}

		if (root->_color == RED && root->_parent && root->_parent->_color == RED)
		{
			cout << root->_kv.first << "出现连续红色节点" << endl;
			return false;
		}


		return CheckColor(root->_left, blacknum, benchmark)
			&& CheckColor(root->_right, blacknum, benchmark);
	}


	bool _IsBalance(Node* root)
	{
		if (root == nullptr)
		{
			return true;
		}

		if (root->_color != BLACK)
		{
			return false;
		}

		//基准值 -->  用于比较别的路径黑色节点个数
		int benchmark = 0;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_color == BLACK)
				benchmark++;
			cur = cur->_left;
		}

		return CheckColor(root, 0, benchmark);

	}


	//旋转
	//都是二叉树的旋转,所以和AVLTree的旋转一样,只不过这里没有平衡因子
	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;
		}
	}

	void RotateL(Node* parent)
	{
		Node* cur = parent->_right;
		Node* curleft = cur->_left;

		//重新链接
		parent->_right = curleft;
		if (curleft)
			curleft->_parent = parent;

		cur->_left = parent;

		//提前保存parent->_parent,可能是根节点,也可能是子树的根节点
		Node* ppnode = parent->_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;
		}

	}


private:
	Node* _root = nullptr;
};

map

namespace kpl
{
	template<class K, class V>
	class map
	{
		//RBTree仿函数的主要作用在这里,set的封装只是跟跑
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};

	public:
		typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
		typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;


		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)
		{
			pair<iterator, bool> ret = insert(make_pair(key, V()));
			return ret.first->second;
		}

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

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

set

namespace kpl
{
	template<class K>
	class set
	{
		//仿函数
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};

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

		//set只保留一个const即可
		const_iterator begin() const
		{
			return _t.begin();
		}

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


		pair<iterator, bool> insert(const K& key)
		{
			//这里返回值的first的迭代器是普通迭代器,用普通迭代器接收
			pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);

			//使用普通迭代器构造一个const的迭代器,这里就体现出迭代器实现中的那个拷贝构造
			return pair<iterator, bool>(ret.first, ret.second);
		}

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

三、总结

set

  1. 插入的元素只需要value,不用键值对
  2. set中的元素不能重复(set可以去重)
  3. 单个元素的访问速度比unordered_set慢
  4. 中序遍历有序,使用其迭代器访问也是有序
  5. 不允许修改,破坏结构

map

  1. map中的元素是键值对
  2. map中的key是唯一的,不能修改,但是value可以修改
  3. 中序遍历有序,使用其迭代器访问也是有序
  4. 支持operator[]
  5. 单个元素的访问速度比unordered_set慢

multiset和multimap(区分set和map)
multiset的value可以重复,multimap的key也可以重复

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

kpl_20

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

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

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

打赏作者

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

抵扣说明:

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

余额充值