【C++】map和set

一、关联式容器

关联式容器是用来存储数据的,与序列式容器(vector,list,deque等)不同的是,其里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高。

二、键值对

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

三、树形结构的关联式容器
根据应用场景的不同, STL 总共实现了两种不同结构的管理式容器:树型结构与哈希结构。
树型结 构的关联式容器主要有四种: map set multimap multiset
这四种容器的共同点是:
使用平衡搜索树( 即红黑树 ) 作为其底层结果,容器中的元素是一个有序的序列。
1、set
1.1 set的介绍
1)set 是按照一定次序存储元素的容器。
2) 在 set 中,元素的 value 也标识key( value 就是 key ,类型为 T) ,并且每个 value 必须是唯一的。
set 中的元素不能在容器中修改 ( 元素总是 const) ,但是可以从容器中插入或删除它们。
3) 在内部, set 中的元素总是按照其内部比较对象 ( 类型比较 ) 所指示的特定严格弱排序准则进行
排序。
4)set 容器通过 key 访问单个元素的速度通常比 unordered_set 容器慢,但它们允许根据顺序对
子集进行直接迭代。
5)set 在底层是用二叉搜索树 ( 红黑树 ) 实现的。
注意:
1) 与 map/multimap 不同, map/multimap 中存储的是真正的键值对 <key, value> , set 中只放
value ,但在底层实际存放的是由 <value, value> 构成的键值对。
2)set 中插入元素时,只需要插入 value 即可,不需要构造键值对。
3) set 中的元素不可以重复 ( 因此可以使用 set 进行去重 ) 。
4) 使用 set 的迭代器遍历 set 中的元素,可以得到有序序列。
5)set 中的元素默认按照小于来比较。
6)set 中查找某个元素,时间复杂度为:logn
7)set 中的元素不允许修改。
8)set 中的底层使用二叉搜索树 ( 红黑树 ) 来实现。
1.2 set的使用
set的模板参数列表

T: set 中存放元素的类型,实际在底层存储 <value, value> 的键值对。
Compare : set 中元素默认按照小于来比较。
Alloc : set 中元素空间的管理方式,使用 STL 提供的空间配置器管理。
set的使用举例

cpp 复制代码
#include <set>
void TestSet()
{
     // 用数组array中的元素构造set
     int array[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0, 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
     set<int> s(array, array+sizeof(array)/sizeof(array));
     cout << s.size() << endl;
     // 正向打印set中的元素,从打印结果中可以看出:set可排序+去重
     for (auto& e : s)
     cout << e << " ";
     cout << endl;
     // 使用迭代器逆向打印set中的元素
     for (auto it = s.rbegin(); it != s.rend(); ++it)
     cout << *it << " ";
     cout << endl;
     // set中值为3的元素出现了几次
     cout << s.count(3) << endl;
}

2、map
2.1 map的介绍
1)map 是关联容器,它按照特定的次序 ( 按照 key 来比较 ) 存储由键值 key 和值 value 组合而成的元
素。
2) 在 map 中,键值 key 通常用于排序和惟一地标识元素,而值 value 中存储与此键值 key 关联的
内容。键值 key 和值 value 的类型可能不同,并且在 map 的内部, key 与 value 通过成员类型
value_type 绑定在一起,为其取别名称为 pair: typedef pair<const key, T> value_type;
3) 在内部, map 中的元素总是按照键值 key 进行比较排序的。
4)map 中通过键值访问单个元素的速度通常比 unordered_map 容器慢,但 map 允许根据顺序
对元素进行直接迭代 ( 即对 map 中的元素进行迭代时,可以得到一个有序的序列 ) 。
5)map 支持下标访问符,即在 [] 中放入 key ,就可以找到与 key 对应的 value 。
6)map 通常被实现为二叉搜索树 ( 更准确的说:平衡二叉搜索树 ( 红黑树 )) 。
2.2 map的使用
map的模板参数

key: 键值对中 key 的类型。
T : 键值对中 value 的类型。
Compare: 比较器的类型, map 中的元素是按照 key 来比较的,缺省情况下按照小于来比较,一般情况下( 内置类型元素 ) 该参数不需要传递,如果无法比较时 ( 自定义类型 ) ,需要用户自己显式传递比较规则( 一般情况下按照函数指针或者仿函数来传递 )。
Alloc :通过空间配置器来申请底层空间,不需要用户传递,除非用户不想使用标准库提供的空间配置器。
注意:
1)map 中的的元素是键值对。
2)map 中的 key 是唯一的,并且不能修改。
3) 默认按照小于的方式对 key 进行比较。
4)map 中的元素如果用迭代器去遍历,可以得到一个有序的序列。
5)map 的底层为平衡搜索树 ( 红黑树 ) ,查找效率比较高O(logn)。
6) 支持 [] 操作符, operator[] 中实际进行插入查找。
map的使用举例

cpp 复制代码
void test_map1()
{
	map<string, string> dict;

	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", "字符串" });
	
	// 隐式类型的转换
	string str1 = "hello";
	A aa1 = { 1, 2 };
	pair<string, string> kv2 = { "string", "字符串" };
}

void test_map2()
{
	map<string, string> dict;
	dict.insert(make_pair("string", "字符串"));
	dict.insert(make_pair("sort", "排序"));
	dict.insert(make_pair("insert", "插入"));

	// 不插入,不覆盖;插入过程中,只比较key,value是相同无所谓
	// key已经有了就不插入了
	dict.insert(make_pair("insert", "xxxx"));

	//map<string, string>::iterator it = dict.begin();
	auto it = dict.begin();
	while (it != dict.end())
	{
		//cout << (*it).first << ":" << (*it).second << endl;
		cout << it->first << ":" << it->second << endl;

		++it;
	}
	cout << endl;

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

void test_map3()
{
	// 统计次数
	string arr[] = { "西瓜", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
	map<string, int> countMap;
	
	for (auto e : arr)
	{
		countMap[e]++;
	}

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

void test_map4()
{
	map<string, string> dict;
	dict.insert(make_pair("string", "字符串"));
	dict.insert(make_pair("sort", "排序"));
	dict.insert(make_pair("insert", "插入"));

	cout << dict["sort"] << endl; // 查找和读
	dict["map"];                  // 插入
	dict["map"] = "映射,地图";     // 修改
	dict["insert"] = "xxx";       // 修改
	dict["set"] = "集合";         // 插入+修改
}

3、multiset

3.1 multiset的介绍
1)multiset 是按照特定顺序存储元素的容器,其中元素是可以重复的。
2) 在 multiset 中,元素的 value 也会识别它 ( 因为 multiset 中本身存储的就是 <value, value> 组成的键 值对,因此value 本身就是 key , key 就是 value ,类型为 T). multiset 元素的值不能在容器中进行 修改( 因为元素总是 const 的 ) ,但可以从容器中插入或删除。
3) 在内部, multiset 中的元素总是按照其内部比较规则 ( 类型比较 ) 所指示的特定严格弱排序准则
进行排序。
4)multiset 容器通过 key 访问单个元素的速度通常比 unordered_multiset 容器慢,但当使用迭
代器遍历时会得到一个有序序列。
5)multiset 底层结构为二叉搜索树 ( 红黑树 ) 。
注意:
1)multiset 中在底层中存储的是 <value, value> 的键值对。
2)mtltiset 的插入接口中只需要插入即可。
3) 与 set 的区别是, multiset 中的元素可以重复, set 是中 value 是唯一的。
4) 使用迭代器对 multiset 中的元素进行遍历,可以得到有序的序列。
5)multiset 中的元素不能修改。
6) 在 multiset 中找某个元素,时间复杂度为O(logn)。
7)multiset 的作用:可以对元素进行排序。
3.2 multiset的使用
此处只简单演示 set 与 multiset 的不同,其他接口与 set 相同。

cpp 复制代码
#include <set>
void TestSet()
{
  int array[] = { 2, 1, 3, 9, 6, 0, 5, 8, 4, 7 };
 
 // 注意:multiset在底层实际存储的是<int, int>的键值对
 multiset<int> s(array, array + sizeof(array)/sizeof(array[0]));
 for (auto& e : s)
 cout << e << " ";
 cout << endl;
 return 0;
}

4、multimap
4.1 multimap的介绍
1)Multimaps 是关联式容器,它按照特定的顺序,存储由 key 和 value 映射成的键值对 <key,
value> ,其中多个键值对之间的 key 是可以重复的。
2) 在 multimap 中,通常按照 key 排序和惟一地标识元素,而映射的 value 存储与 key 关联的内
容。 key 和 value 的类型可能不同,通过 multimap 内部的成员类型 value_type 组合在一起,
value_type 是组合 key 和 value 的键值对 : typedef pair<const Key, T> value_type;
3) 在内部, multimap 中的元素总是通过其内部比较对象,按照指定的特定严格弱排序标准对
key 进行排序的。
4)multimap 通过 key 访问单个元素的速度通常比 unordered_multimap 容器慢,但是使用迭代
器直接遍历 multimap 中的元素可以得到关于 key 有序的序列。
5)multimap 在底层用二叉搜索树 ( 红黑树 ) 来实现。
注意:
multimap map 的唯一不同就是: map 中的 key 是唯一的,而 multimap key 是可以 重复的
4.2 multimap的使用
multimap 中的接口可以参考 map ,功能都是类似的。
注意:
1)multimap 中的 key 是可以重复的。
2)multimap 中的元素默认将 key 按照小于来比较。
3)multimap 中没有重载 operator[] 操作 。
4) 使用时与 map 包含的头文件相同。
四、在OJ中的使用
692. 前K个高频单词 - 力扣(LeetCode)

cpp 复制代码
class Solution 
{
public:
    struct Greater
    {
        bool operator()(const pair<string, int>& kv1, const pair<string, int>& kv2)
        {
            return kv1.second > kv2.second || (kv1.second == kv2.second && kv1.first < kv2.first);
        }
    };
    vector<string> topKFrequent(vector<string>& words, int k) 
    {
        map<string, int> countMap;
        for(const auto& e : words)
        {
            countMap[e]++;
        }
        vector<pair<string, int>> kv(countMap.begin(), countMap.end());
        sort(kv.begin(), kv.end(), Greater());
        vector<string> v;
        for(int i = 0; i < k; i++)
        {
            v.push_back(kv[i].first);
        }
        return v;
    }
};

349. 两个数组的交集 - 力扣(LeetCode)

cpp 复制代码
class Solution 
{
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) 
    {
        set<int> s1(nums1.begin(), nums1.end());
        set<int> s2(nums2.begin(), nums2.end());
        vector<int> v;
        set<int>::iterator it1 = s1.begin();
        set<int>::iterator it2 = s2.begin();
        while(it1 != s1.end() && it2 != s2.end())
        {
            if(*it1 < *it2)
            {
                it1++;
            }
            else if(*it1 > *it2)
            {
                it2++;
            }
            else
            {
                v.push_back(*it1);
                it1++;
                it2++;
            }
        }
        return v;
    }
};
相关推荐
拾光Ծ2 小时前
C++11实用的“新特性”:列表初始化+右值引用与偷懒艺术——移动语义
开发语言·c++
CAU界编程小白2 小时前
数据结构系列之快速排序
数据结构·c++·算法
何憶树之長青2 小时前
Kernel
开发语言·php
hardmenstudent2 小时前
Python字典--第1关:元组使用:这份菜单能修改吗?
开发语言·python
qwepoilkjasd2 小时前
C++ 虚函数与多态详解
c++
John_Rey2 小时前
Rust底层深度探究:自定义分配器(Allocators)——控制内存分配的精妙艺术
开发语言·后端·rust
逻极2 小时前
VS Code之Java 开发完全指南:从环境搭建到实战优化
java·开发语言
卡提西亚2 小时前
一本通网站1130:找第一个只出现一次的字符
数据结构·c++·笔记·算法·一本通
月月玩代码2 小时前
SLF4J,简单门面Java日志框架
java·开发语言