C++ STL详解(六) ---------- set、map、multiset、multimap的介绍

目录

1.关联式容器

2.树形结构与哈希结构

3.键值对

4.set 

5.multiset

6.map

7.multimap

8.具体的使用


1.关联式容器

①C++STL包含了序列式容器和关联式容器:

  • 序列式容器里面存储的是元素本身,其底层为线性序列的数据结构。比如:vector,list,deque,forward_list(C++11)等。
  • 关联式容器里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高。比如:set、map、unordered_set、unordered_map等。

②注意: C++STL当中的stack、queue和priority_queue属于容器适配器,它们默认使用的基础容器分别是deque、deque和vector。
 

                        

2.树形结构与哈希结构

  • 根据应用场景的不同,C++STL总共实现了两种不同结构的关联式容器:树型结构和哈希结构
  • 树型结构容器中的元素是一个有序的序列,而哈希结构容器中的元素是一个无序的序列。
关联式容器容器结构底层实现
set、map、multiset、multimap树型结构平衡搜索树(红黑树)
unordered_set、unordered_map、unordered_multiset、unordered_multimap哈希结构哈希表

                        

3.键值对

  • 键值对是用来表示具有一 一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息。
  • 比如我们若是要建立一个英译汉的字典,那么该字典中的英文单词与其对应的中文含义就是一一对应的关系,即通过单词可以找到与其对应的中文含义。
--在SGI-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)
	{}
};

                 

4.set 

  • ① set是按照一定次序存储元素的容器,使用set的迭代器遍历set中的元素,可以得到有序序列。
  • ② set当中存储元素的value都是唯一的,不可以重复,因此可以使用set进行去重。
  • ③ 与map/multimap不同,map/multimap中存储的是真正的键值对<key, value>,set中只放value,但在底层实际存放的是由<value, value>构成的键值对,因此在set容器中插入元素时,只需要插入value即可,不需要构造键值对。
  • ④ set中的元素不能被修改,因为set在底层是用二叉搜索树来实现的,若是对二叉搜索树当中某个结点的值进行了修改,那么这棵树将不再是二叉搜索树。
  • ⑤ 在内部,set中的元素总是按照其内部比较对象所指示的特定严格弱排序准则进行排序。当不传入内部比较对象时,set中的元素默认按照小于来比较。
  • ⑥ set容器通过key访问单个元素的速度通常比unordered_set容器慢,但set容器允许根据顺序对元素进行直接迭代。
  • ⑦ set在底层是用平衡搜索树(红黑树)实现的,所以在set当中查找某个元素的时间复杂度为logN。

                 

5.multiset

  • multiset容器与set容器的底层实现一样,都是平衡搜索树(红黑树),其次,multiset容器和set容器所提供的成员函数的接口都是基本一致的
  • multiset容器和set容器的唯一区别就是,multiset允许键值冗余,即multiset容器当中存储的元素是可以重复的。
     

                         

6.map

  • ① map是关联式容器,它按照特定的次序(按照key来比较)存储键值key和值value组成的元素,使用map的迭代器遍历map中的元素,可以得到有序序列。
  • 在map中,键值key通常用于排序和唯一地标识元素,而值value中存储与此键值key关联的内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型value_type绑定在一起,并取别名为pair。
  • map容器中元素的键值key不能被修改,但是元素的值value可以被修改,因为map底层的二叉搜索树是根据每个元素的键值key进行构建的,而不是值value。
  • ④ 在内部,map中的元素总是按照键值key进行比较排序的。当不传入内部比较对象时,map中元素的键值key默认按照小于来比较。
  • ⑤ map容器通过键值key访问单个元素的速度通常比unordered_map容器慢,但map容器允许根据顺序对元素进行直接迭代。
  • map容器支持下标访问符,即在[]中放入key,就可以找到与key对应的value。
  • ⑦ map在底层是用平衡搜索树(红黑树)实现的,所以在map当中查找某个元素的时间复杂度为logN。

                 

(1)[ ] 运算符重载

①源代码

mapped_type& operator[] (const key_type& k);
(*((this->insert(make_pair(k, mapped_type()))).first)).second

②代码解析

实际上[ ]运算符重载实现的逻辑实际上就是以下三个步骤:

  1. 调用insert函数插入键值对。
  2. 拿出从insert函数获取到的迭代器。
  3. 返回该迭代器位置元素的值value。
mapped_type& operator[] (const key_type& k)
{
	//1、调用insert函数插入键值对
	pair<iterator, bool> ret = insert(make_pair(k, mapped_type()));

	//2、拿出从insert函数获取到的迭代器
	iterator it = ret.first;

	//3、返回该迭代器位置元素的值value
	return it->second;
}

③小结

  • 如果k不在map中,则先插入键值对<k, V()>,然后返回该键值对中V对象的引用。
  • 如果k已经在map中,则返回键值为k的元素对应的V对象的引用。

④使用

                                 

(2)统计次数的方式

	// 1、统计次数 
	string arr[] = { "香蕉", "苹果", "香蕉", "榴莲", "草莓", "苹果", "香蕉", "苹果","西瓜","西瓜", "香蕉", "草莓", "西瓜" };

	 //统计次数的方式1:
	map<string, int> countMap;
	for (const auto& str : arr)
	{
		// 思路:第一次出现,插入<str, 1>, 后续再出现就++次数ret->second
		map<string, int>::iterator ret = countMap.find(str);
		if (ret != countMap.end())
		{
			ret->second++;
		}
		else
		{
			countMap.insert(make_pair(str, 1));
		}
	}

	 //统计次数的方式2: 
	map<string, int> countMap;
	for (const auto& str : arr)
	{
		// 先插入,如果str已经在map中,insert会返回str所在节点的迭代器,我们++次数即可
		//pair<map<string, int>::iterator, bool>  ret = countMap.insert(make_pair(str, 1));
		auto ret = countMap.insert(make_pair(str, 1));
		if (ret.second == false)
		{
			ret.first->second++;
		}
	}

	// 统计次数方式3:
	map<string, int> countMap;
	for (const auto& str : arr)
	{
		countMap[str]++;
	}

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

                         

(3)统计次数+排序

struct MapItCompare
{
	bool operator()(map<string, int>::iterator x, map<string, int>::iterator y)
	{
		return x->second < y->second;
	}
};


	// 一、统计次数  二、找出大家最喜欢(出现次数最多)的三种水果
	string arr[] = { "香蕉", "苹果", "香蕉", "榴莲", "草莓", "苹果", "香蕉", "苹果", "西瓜", "西瓜", "香蕉", "草莓", "西瓜" };
	map<string, int> countMap;
	for (const auto& str : arr)
	{
		countMap[str]++;
	}

	// 1. 对所有水果次数排序的思路
	//vector<pair<string, int>> v;
	vector<map<string, int>::iterator> v;
	map<string, int>::iterator countMapIt = countMap.begin();
	while (countMapIt != countMap.end())
	{
		v.push_back(countMapIt);
		++countMapIt;
	}
	sort(v.begin(), v.end(), MapItCompare());//自定义排序方式

	// 2. 利用map排序  -- 拷贝pair数据
	//map<int, string> sortMap;
	map<int, string, greater<int>> sortMap;
	for (auto e : countMap)
	{
		sortMap.insert(make_pair(e.second, e.first));
	}

	// 3. 利用set排序  --不拷贝pair数据
	set<map<string, int>::iterator, MapItCompare> sortSet;
	countMapIt = countMap.begin();
	while (countMapIt != countMap.end())
	{
		sortSet.insert(countMapIt);
		++countMapIt;
	}
     
    //4.优先级队列
	typedef map<string, int>::iterator M_IT;
	priority_queue<M_IT, vector<M_IT>, MapItCompare> pq;
	countMapIt = countMap.begin();
	while (countMapIt != countMap.end())
	{
		pq.push(countMapIt);
		++countMapIt;
	}
}

 

7.multimap

  • multimap容器与map容器的底层实现一样,也都是平衡搜索树(红黑树),其次,multimap容器和map容器所提供的成员函数的接口都是基本一致的
  • multimap容器和map容器的区别与multiset容器和set容器的区别一样,multimap允许键值冗余,即multimap容器当中存储的元素是可以重复的。
  • 由于multimap容器允许键值冗余,调用[ ]运算符重载函数时,应该返回键值为key的哪一个元素的value的引用存在歧义,因此在multimap容器当中没有实现[ ]运算符重载函数。

         

 8.具体的使用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值