目录
一、set
1、set的相关函数:
下面会挑选常用的进行用法示例
2、set的使用
下面示范常用的几个函数
①、insert
首先插入几个值,用迭代器和范围for遍历结果打印
set的结果有两个作用分别是排序和去重:
我们插入了5个数,其中有两个相同的,但是结果只有4个,说明有去重的作用
观察打印结果是升序的,原因是默认的仿函数是less<int>,下图可见
默认排序为升序,是因为set的底层是搜索二叉树,而搜索二叉树的中序遍历就是升序的
如果想改为降序,只需在set<int>后加greater<int>即可,即set<int,greater<int>>(记得包头文件function)
这时范围for语法,也可以进行遍历
如果支持迭代器,那就支持范围for,因为范围for的底层就是迭代器实现的
set并不支持改的操作
②、erase
为了方便观察,下面都用范围for遍历,删去了迭代器遍历的代码
直接删除指定的值
③、find
find可以与erase结合使用。是由于下面的第一个重载,上面直接使用erase直接删除指定的值是第二个重载
例如我们想删除3,但是是传入3的位置进行删除的
注意需要判断pos是否与s.end()相等,不相等才能删除,因为若find没有找到输入的值,则返回s.end(),这时不判断是否等于s.end()直接删除有可能会出错
④、count
count可以检测该值在不在set中,存在返回1,否则返回0
set中的count更多的还是为了和multiset保持接口的一致性
3、关于multiset
multiset与set的用法几乎一致,但是也有一些差别
multiset允许冗余,上面说到set的作用有去重,即插入两个相同的数时只会插入一个,而multiset允许插入两个相同的数
下面是两个插入时的区别:
①、insert区别
set:
multiset:
所以multiset作用就是单纯的排序,没有了去重的作用
②、count的区别
set是返回值只有1和0,而multiset会返回个数,如下:
插入的数有几个就返回几
③、find的区别
如果find查找出现多次的相同数值,那么返回排序后的第一个数值的位置
④、erase的区别
如果删除的是出现多次的数,那么会全部删除
二、map
1、map的相关函数
map和set的功能比较相似,有下面的一些函数
2、map的使用
map中是pair<key,value>
①、insert
这里的insert插入的类型是value_type&,而value_type是pair结构体,如下图:
而pair结构体中,里面有两个成员,一个是first,一个是second,
所以map中insert插入的是一个结构体,而不是像set一样插入的是一个数
并且insert的返回值也是pair,这里是为下面的[]做准备的,因为[]的底层就是insert实现的,下面也会提到
下面就是map的insert的常规使用方法:调用构造插入
打印结果也是根据第一个string内容的ASCII码排序的:
第二种插入方法:make_pair
make_pair是函数模板,可以自动推出类型
当然也可以用范围for遍历,加引用是担心拷贝代价太大
②、find
find这里的使用,统计出现的个数的样例,这里涉及的知识在二叉搜索树的key/value中也讲到过
第一种统计出现的个数的方法:
用于查找出现的次数,map的find和set的find类似,若没有找到则返回map的end(),所以使用前要先判断返回值是否等于end()
③、[]
[]是在map里找key(key和value就对应pair中的first和second)
若map中有这个key,则返回value的引用,这里就可以有查找和修改value的作用
group中存在car,那么第一个group["car"]就是查找作用,有则返回car对应的value引用
第二个 group["car"] = "小汽车";就是修改的作用,因为返回的是value的引用,所以可以修改car的value值
如果map中没有这个key,那么就插入pair(key,V()),即调用默认构造,并返回value的引用,这里有插入和修改的作用
group["car"];是插入作用的体现
group["car"] = "小汽车";是查找加修改的作用体现
group["bus"] = "公交车";是插入加修改作用的体现
下面是第二种利用[]进行统计出现个数的方法:
其中的Count[cur]就是在Count中找与cur相等的key,若存在则++该key对应的int值,若没有则插入pair(cur,int()),调用默认构造,int默认为0,++int变为1
[]的底层其实就是使用的insert:
insert的返回值描述:
意思就是若key已经在map中,返回pair(key_iterator,false) ,即已经存在结点的迭代器,bool为false
若key不在map中,返回pair(newkey_iterator,false) ,即新出现的key的迭代器,bool为true
下面是[]的底层的代码
中间的.first是insert的返回值pair的key_iterator或newkey_iterator,即该key值所在的结点的迭代器,接着解引用后.second是迭代器中的pair的second,即为所对应的value,满足了[]的返回值是value的引用
这里用到了两个pair,一个是insert的pair<iterator,bool>,一个是迭代器中的pair<key,value>
上面图片的代码也可以写成:
pair<iterator,bool> ret = insert(make_pair(key,V())) ;
return ret.first->second;
④、at
at比[]功能少很多
map中存在key,则返回value引用
map中不存在key,则抛异常处理
3、关于multimap
multimap与map大致功能都差不多,但是没有[],因为multimap既然允许冗余,那么统计次数时,若插入多个相同的string值如:铅笔,无法确定到底返回哪个铅笔所对应的value值,所以multimap就不能解决统计次数的问题了
所以也有用multimap的场景,比如说一个单词有两个意思,那么插入两次这个单词,key相同,但是对应的value不相同,这个场景就能用multimap,但是使用的场景不多,大部分情况还是用map
其他用法参考set的multiset的用法,相差不大
三、相关OJ题
下面举两个OJ题,里面有使用set、map和multimap的场景,以便更好的理解
1、前k个高频单词
题目描述:
给定一个单词列表 words
和一个整数 k
,返回前 k
个出现次数最多的单词。
返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率, 按字典顺序 排序
示例:
输入: words = ["i", "love", "leetcode", "i", "love", "coding"], k = 2 输出: ["i", "love"] 解析: "i" 和 "love" 为出现次数最多的两个单词,均为2次。 注意,按字母顺序 "i" 在 "love" 之前。
第一种思路分析:
我们可以想到先用map的[]统计每个单词出现的次数,由于返回的答案是按单词出现频率由高到低排序,所以可以将map的数据全部插入到优先级队列中,因为优先级队列可以默认排升序,并且我们可以自己写仿函数控制排序规则,然后挨个取出所存数据的first(string类型的单词,要求返回单词)进vector,然后返回vector即可
第一种思路代码实现:
class Solution {
public:
//使用优先级队列需要自己写仿函数,解决相同频率下按字典顺序排序的问题
struct Less
{
bool operator()(const pair<string, int>& kv1, const pair<string, int>& kv2) const
{
if(kv1.second < kv2.second)
return true;
//由于是按字典顺序排,所以是kv1.first > kv2.first,不是小于
if(kv1.second == kv2.second && kv1.first > kv2.first)
return true;
return false;
}
};
vector<string> topKFrequent(vector<string>& words, int k) {
map<string ,int> count;
//使用map来统计次数
for(auto& e : words)
{
count[e]++;
}
vector<string> v;
//初始化优先级队列pq,数据排升序(大堆)
priority_queue<pair<string, int>, vector<pair<string, int>>, Less> pq;
for(auto& e : count)
{
pq.push(e);
}
//前k个单词循环k次,依次取出数据
while(k--)
{
v.push_back(pq.top().first);
pq.pop();
}
return v;
}
};
第二种思路分析:
首先先用map的[]统计每个单词出现的次数,我们知道map中的数据是默认按key值进行排序的,那接下来我们再用sort算法按map中value值进行排序,并且sort算法是按照自己实现的一个降序且频率相同时按字典顺序排序的Greater的仿函数,这个仿函数需要注意的是,map中数据是按照string排列的,在使用sort排序后,在相同频率时,应该是string小的算大,因为按字典顺序排序需要先取小的,所以要注意符号问题
并且sort算法必须是连续的存储空间的容器才能使用,map并不是连续的存储空间,而vector是连续的存储空间,所以先将数据全部转到vector中,再使用sort算法
第二种思路代码实现:
class Solution {
public:
//自己模拟实现仿函数,sort函数的排序中使用
struct Greater
{
bool operator()(const pair<string, int>& kv1, const pair<string, int>& kv2) const
{
if(kv1.second > kv2.second)
return true;
//由于是按字典顺序排,因此将first小的先输出,所以是小于
if(kv1.second == kv2.second && kv1.first < kv2.first)
return true;
return false;
}
};
vector<string> topKFrequent(vector<string>& words, int k) {
map<string ,int> count;
//使用map来统计次数
for(auto& e : words)
{
count[e]++;
}
//使用vector存map的数据,为了使用sort
//因为vector有连续的存储空间
vector<pair<string, int>> vp;
for(auto& e : count)
{
vp.push_back(e);
}
//使用sort排序,使用了自己实现的Greater仿函数
sort(vp.begin(),vp.end(),Greater());
//将排好序的vp中数据的first即单词传入v中
vector<string> v;
for(size_t i = 0;i < k; ++i)
{
v.push_back(vp[i].first);
}
return v;
}
};
第三种思路分析:
第三种思路就是,我们不用sort排序了,我们用multimap<int,string,greater<int>>排降序,这里将string和int更换位置,因为默认是按照key排序的,不用map是因为map默认去重,如果有相同次数直接去重了,不满足要求
第三种思路代码实现:
class Solution {
public:
vector<string> topKFrequent(vector<string>& words, int k) {
map<string ,int> count;
//使用map来统计次数
for(auto& e : words)
{
count[e]++;
}
//使用multimap,将int和string换位置
//并传入greater排key(即出现次数int)的降序
multimap<int, string, greater<int>> m;
//将count的数据翻转int和string插入m中
for(auto& e : count)
{
m.insert(make_pair(e.second, e.first));
}
vector<string> v;
//使用迭代器传入multimap的前k个数据的second即单词
multimap<int, string>::iterator it = m.begin();
for(size_t i = 0;i < k; ++i)
{
v.push_back(it->second);
++it;
}
return v;
}
};
2、求两个数组的交集
题目描述:
给定两个数组 nums1
和 nums2
,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
示例:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4] 输出:[9,4] 解释:[4,9] 也是可通过的
思路分析:
首先用set将两个数组去重加排序,接着用双指针的方法,挨个判断是否相等,小的那个++,等于时就是交集,于是尾插到vector中,直到其中一个指针指向数组的末尾就结束
代码实现:
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
//用set将两个数组去重加排序
set<int> s1(nums1.begin(),nums1.end());
set<int> s2(nums2.begin(),nums2.end());
vector<int> v;
//给两个指针
auto it1 = s1.begin();
auto it2 = s2.begin();
//小的++,等于就说明是交集
while(it1 != s1.end() && it2 != s2.end())
{
if(*it1 < *it2)
{
it1++;
}
else if(*it2 < *it1)
{
it2++;
}
else
{
v.push_back(*it1);
it1++;
it2++;
}
}
return v;
}
};
3、单词识别
题目描述:
输入一个英文句子,把句子中的单词(不区分大小写)按出现次数按从多到少把单词和次数在屏幕上输出来,次数一样的按照单词小写的字典序排序输出,要求能识别英文单词和句号。
示例:
输入:A blockhouse is a small castle that has four openings through which to shoot.
输出:
a:2
blockhouse:1
castle:1
four:1
has:1
is:1
openings:1
shoot:1
small:1
that:1
through:1
to:1
which:1
思路分析:
首先,应该注意英文句子有空格,所以应该用getline输入,其次不区分大小写,那就遍历一遍统一将大写化为小写
接着再遍历这个string字符串,遍历过程中将其中的单词取出来插入到vector<string>中
然后利用map<string,int>统计单词出现次数,然后就和上面第一题一样,有三种方法,这里只列举一种:我们用multimap<int,string,greater<int>>排降序,将string和int更换位置插入到multimap中,用multimap而不用map的理由和第一题一样,map会去重,我们的int位于key位置,如果有出现相同次数的单词去重会不符合题意
最后按要求格式一个个打印出来即可
代码实现:
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <functional>
using namespace std;
int main() {
string s;
getline(cin, s);
//将大写换为小写
for(size_t i = 0; i < s.size(); ++i)
{
if (s[i] >= 'A' && s[i] <= 'Z')
s[i] += 32;
}
vector<string> v;
//临时字符串tmp,用于插入每一个单词
string tmp;
for (int i = 0; i < s.size(); ++i)
{
//如果是空格或句号结尾,说明刚好经过一个单词
//尾插入vector后,将tmp置空
if (s[i] == ' ' || s[i] == '.')
{
v.push_back(tmp);
tmp = "";
}
//如果不是空格或句号,说明单词没结束
else
{
tmp += s[i];
}
}
//用map的[]统计单词出现次数
map<string, int> m;
for (auto& e : v)
{
m[e]++;
}
//使用multimap进行降序排序,并交换string,int位置
//因为multimap都是按照key排序的
multimap<int, string, greater<int>> mp;
for (auto& e : m)
{
mp.insert(make_pair(e.second, e.first));
}
//依次打印出结果
auto pos = mp.begin();
while (pos != mp.end())
{
cout << pos->second << ":" << pos->first << endl;
pos++;
}
return 0;
}