目录
哈喽,小伙伴们大家好。今天我们继续来学习STL容器,今天我将主要介绍map和set的使用。话不多说,拿好小本本,我们赶快开始吧。
在之前我们接触过STL的部分容器,比如vector,deque,list。这些容器统称为序列式容器,底层为线性结构,存储的是元素本身。那什么是关联式容器呢?
关联式容器也是用来存储数据的,与序列式容器不同的是,关联式容器中存储的
根据应用场景不同,STL创建了两种关联式容器,分别是树形结构和哈希结构,今天我们主要学习树形结构的容器。树型结构的关联式容器主要有四种:map、set、multimap、multiset。这四种容器的共同点是:使用平衡搜索树(即红黑树)作为其底层结果,容器中的元素是一个有序的序列。
在插入过程中,遇到重复的值会自动跳过。遍历时借助迭代器,迭代器采用的是树结构的中序遍历,这样遍历出来的数据是有顺序的。
- void test_set1()
- {
- set<int> s;
- s.insert(3);
- s.insert(1);
- s.insert(2);
- s.insert(14);
- s.insert(36);
- s.insert(4);
- s.insert(3);
- s.insert(3);
- // 遍历方式1:
- set<int>::iterator it = s.begin();
- while (it != s.end())
- {
- // 不能修改已经插入的值
- // *it += 1;
- cout << *it << " ";
- ++it;
- }
- cout << endl;
- // 遍历方式2:
- for (auto e : s)
- {
- cout << e << " ";
- }
- cout << endl;
- }
查找会返回特定值对应位置的迭代器。如果没有找到则返回尾部的迭代器。删除通常有两种方式,通过值删除或通过查找到的位置删除。
区别是通过值删除,如果要删除的值没在set中则不处理,也不会报错。而通过查找位置删除,如果找不到还依旧删除则会报错。
- void test_set2()
- {
- set<int> s;
- s.insert(3);
- s.insert(1);
- s.insert(2);
- s.insert(14);
- s.insert(36);
- s.insert(4);
- s.insert(3);
- s.insert(3);
-
- // 先查找,找到了再删。没找到,也去删会报错
- auto pos = s.find(4);
- if (pos != s.end())
- {
- s.erase(pos);
- }
-
- pos = s.find(40);
- //s.erase(pos);
- if (pos != s.end())
- {
- s.erase(pos);
- }
-
- // 在就删除,不在就不处理也不报错
- s.erase(3);
- s.erase(30);
- }
multiset与set的唯一区别是允许键值冗余,可以存储相同的数据,在插入时会统一插入到相同值的右边或最左边,依然保持着大小关系。除了下面几点外,其它与set几乎没有区别。
如果multiset中有相同的值,在遍历时会遍历多次。
- void test_set3()
- {
- multiset<int> s;
- s.insert(3);
- s.insert(1);
- s.insert(2);
- s.insert(14);
- s.insert(36);
- s.insert(4);
- s.insert(3);
- s.insert(3);
- s.insert(3);
-
- // 排序
- multiset<int>::iterator it = s.begin();
- while (it != s.end())
- {
- cout << *it << " ";
- ++it;
- }
- cout << endl;
- }
find查到的值有多个时,它得到的是中序的第一个。count函数可以用在计算相同值的个数,这个函数在set中几乎没有意义,因为都是1,但在multiset中可能会用到。
- // find查找的val有多个的时候,那么他找到的是中序的第一个
- multiset<int>::iterator pos = s.find(3);
- while (*pos == 3)
- {
- cout << *pos << endl;
- ++pos;
- }
- cout << s.count(3) << endl;
如果在multiset中存在多个相同的值,使用erase函数会删除掉所有值。
- void test_set3()
- {
- multiset<int> s;
- s.insert(3);
- s.insert(1);
- s.insert(2);
- s.insert(14);
- s.insert(36);
- s.insert(4);
- s.insert(3);
- s.insert(3);
- s.insert(3);
-
- multiset<int>::iterator it = s.begin();
- while (it != s.end())
- {
- cout << *it << " ";
- ++it;
- }
- cout << endl;
-
- s.erase(3);
- it = s.begin();
- while (it != s.end())
- {
- cout << *it << " ";
- ++it;
- }
- cout << endl;
- }

- map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元素。
- 在map中,键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的。
内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型value_type绑定在一起,为其取别名称为pair: typedef pairvalue_type; - 在内部,map中的元素总是按照键值key进行比较排序的。
- map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。
- key的值是唯一的,一个key值只能对应一个value,但一个value可以对应多个key。
STL中提供了pair类把key和value绑定到了一起,所以插入数据时需要插入pair类型对象。可以理解为pair类就是为了map而生的。
![]()
插入pair类型数据时有两种方法,一种是调用pair的构造函数,构造一个匿名对象插入,另一种是直接调用接口。
- void test_map1()
- {
- map<int, double> m;
- // 调用pair的构造函数,构造一个匿名对象插入
- m.insert(pair<int, double>(1, 1.1));
- m.insert(pair<int, double>(5, 5.5));
- m.insert(pair<int, double>(2, 2.2));
- m.insert(pair<int, double>(2, 3.3)); // key相同就会插入失败
-
- // 调用函数模板,构造对象,
- // 好处:是不需要去声明pair的参数,让函数模板自己推演,用起来方便些
- m.insert(make_pair(2, 2.2));
- }
当类型名称过长时,我们可以通过typedef来简化代码,增强可读性。
- void map2()
- {
- //项目不展开命名空间,都要指定std,写起来会比较长
- //std::map
dict; - //dict.insert(std::pair
("insert", "插入")); - std::map
::iterator it = dict.begin(); - //auto dit = dict.begin();
- //while (dit != dict.end())
- //{
- // cout << dit->first << ":" << dit->second << endl;
- // ++dit;
- //}
- //cout << endl;
-
- // 通过typedef 简化命名
- typedef std::map
DICT; - typedef std::pair
DICT_KV; - typedef std::map
::iterator DICT_ITER; -
- DICT dict;
- dict.insert(DICT_KV("insert", "插入"));
- DICT_ITER dit = dict.begin();
- //auto dit = dict.begin();
- while (dit != dict.end())
- {
- cout << dit->first << ":" << dit->second << endl;
- ++dit;
- }
- cout << endl;
- }
- dit = dict.begin();
- while (dit != dict.end())
- {
- cout << dit->first << ":" << dit->second << endl;
- ++dit;
- }
- cout << endl;
map中的key是不能改变的,但是value可以。map的迭代器返回的是pair元素的位置,可以通过迭代器对pair中的second成员进行修改。
- void map3()
- {
- typedef std::map
DICT; - typedef std::pair
DICT_KV; - typedef std::map
::iterator DICT_ITER; - DICT dict;
- dict.insert(DICT_KV("insert", "插入"));
- dict.insert(DICT_KV("sort", "排序"));
- dict.insert(DICT_KV("left", "左边"));
- // 修改map的value数据
- auto ret = dict.find("left");
- if (ret != dict.end())
- {
- //ret->second.insert(ret->second.size() - 1, "、剩余");
- // 可读性优化技巧
- string& str = ret->second;
- str.insert(str.size() - 1, "、剩余");
- }
- }
方法一:
查找是否存在,如果第一次存在就插入
- void count1
- {
- string arr[] = { "香蕉", "苹果", "香蕉", "榴莲", "草莓", "苹果","西瓜", "香蕉", "草莓","西瓜"};
- map
int> countMap; - for (const auto& str : arr)
- {
- // 思路:第一次出现,插入
, 后续再出现就++次数ret->second - map
int>::iterator ret = countMap.find(str); - if (ret != countMap.end())
- {
- ret->second++;
- }
- else
- {
- countMap.insert(make_pair(str, 1));
- }
- }
- }
-
方法二:
我们来看一下map中insert函数的返回值。
![]()
insert会返回一个pair类型的数据,pair的second为一个bool类型,插入成功为true,插入失败为false。而first为一个迭代器,如果新插入的数据不存在,则返回新插入数据的迭代器。如果新插入的数据存在,则返回之前数值相等的旧数据的迭代器。
- void count2()
- {
- string arr[] = { "香蕉", "苹果", "香蕉", "榴莲", "草莓", "苹果","西瓜", "香蕉", "草莓","西瓜" };
- map
int> countMap; - for (const auto& str : arr)
- {
- // 先插入,如果str已经在map中,insert会返回str所在节点的迭代器,我们++次数即可
- //pair
- auto ret = countMap.insert(make_pair(str, 1));
- if (ret.second == false)
- {
- ret.first->second++;
- }
- }
- }
方法三:
我们先来看一下map中[]是怎样进行重载的。

等价于下面的代码:

结合insert的返回值看可以的值,[k]等价于k对应的value。
所以第三种计数方法代码如下:
- void count3()
- {
- string arr[] = { "香蕉", "苹果", "香蕉", "榴莲", "草莓", "苹果","西瓜", "香蕉", "草莓","西瓜" };
- map
int> countMap; - for (const auto& str : arr)
- {
- countMap[str]++;
- }
-
- for (const auto& e : countMap)
- {
- cout << e.first << ":" << e.second << endl;
- }
- }
- // 关于[]的一些扩展学习
- map
dict; - dict.insert(make_pair("sort", "排序"));
- dict["left"] = "左边"; // 插入+修改
- dict["insert"]; // 插入
- dict["insert"] = "插入"; // 修改
- dict["left"] = "左边、剩余"; // 修改
- dict.erase("left");
multimap和map的唯一不同就是:map中的key是唯一的,而multimap中key是可以重复的。
实际问题:找出大家最喜欢的三种水果
过程:(1)统计次数(2)排序
排序方法一:使用multimap
- void search_fruit()
- {
- string arr[] = { "香蕉", "苹果", "香蕉", "榴莲", "草莓", "苹果","西瓜", "香蕉", "草莓","西瓜" };
- //统计次数
- map
int> countmap; - for (const auto& str : arr)
- {
- countmap[str]++;
- }
- //因为int的次数可能重复,所以需要使用multimap
- multimap<int, string> sortmap;
- for (const auto& e : countmap)
- {
- sortmap.insert(make_pair(e.second, e.first));
- }
- for (const auto& e : sortmap)
- {
- cout << e.second << ":" << e.first << endl;
- }
- }
排序方法二:使用vector传countmap的迭代器
- struct mapit_com
- {
- //函数对象
- bool operator()(map
int >::iterator x,mapint >:: iterator y) - {
- return x->second < y->second;
- }
- };
-
- void search_fruit()
- {
- string arr[] = { "香蕉", "苹果", "香蕉", "榴莲", "草莓", "苹果","西瓜", "香蕉", "草莓","西瓜" };
- //统计次数
- map
int> countmap; - for (const auto& str : arr)
- {
- countmap[str]++;
- }
- //利用vector排序
- vector
- auto countmapit = countmap.begin();
- while (countmapit != countmap.end())
- {
- sortv.push_back(countmapit);
- countmapit++;
- }
- sort(sortv.begin(), sortv.end(), mapit_com());
- }
排序方法三:利用set排序,把countmap的迭代器存起来
- //......
- // 利用set排序 --不拷贝pair数据
- set
- countMapIt = countMap.begin();
- while (countMapIt != countMap.end())
- {
- sortSet.insert(countMapIt);
- ++countMapIt;
- }
-
本文主要介绍了map和set的使用,在之后的文章中我将介绍AVL树,并用AVL树来模拟实现map和set,如果小伙伴们有兴趣可以关注我。感谢大家的阅读,山高路远,来日方长,我们下次见。