C++进阶之map与set

前言:本篇博客只介绍这两个容器比较难的接口,其余接口可以通过官方文档去查询了解

文档入口:cplusplus.com - The C++ Resources Networkicon-default.png?t=M4ADhttp://m.cplusplus.com/

目录

一、关联式容器

二、键值对

树形结构的关联式容器

set

set的介绍

set的使用

1. set的模板参数列表

2.set的使用

3.erase

multiset

multiset的介绍

multiset的使用

multiset中面对重复的数据它先找到的是那一个重复的数据呢? 

如何删除所有的重复数据?

我们发现set/multiset是没有修改接口的,我们可以借助迭代器进行修改吗?

map

map的介绍

1. map的模板参数说明

2.Pair

map的使用

insert

map的遍历

[ ]的使用

multimap

multiset的介绍

multimap的使用

cout

erase

练习题

一、统计前k种水果

方法一:

方法一的优化:

方法二:

方法三:

​编辑方法三的优化:

二、前K个高频单词 


一、关联式容器

STL中将数据结构叫做容器。我们已经接触过STL中的部分容器,比如:vectorlistdequeforward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。那什么是关联式容器?它与序列式容器有什么区别?

关联式容器 也是用来存储数据的,与序列式容器不同的是,其 里面存储的是 结构的键值对,在 数据检索时比序列式容器效率更高关联史容器:map/set.. 数据之间有强烈的关联性底层是搜索树。

ps:栈和队列是适配器

二、键值对

用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量keyvaluekey代表键值,value表示与key对应的信息。比如:现在要建立一个英汉互译的字典,那该字典中必然有英文单词与其对应的中文含义,而且,英文单词与其中文含义是一一对应的关系,即通过该应该单词,在词典中就可以找到与其对应的中文含义。

SGI-STL 中关于键值对的定义:
template 
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)
 {}
};

树形结构的关联式容器

根据应用场景的不同, STL 总共实现了两种不同结构的关联式容器:树型结构与哈希结构。 树型结构的关联式 容器主要有四种: map set multimap multiset 。这四种容器的共同点是:使用平衡搜索树 ( 即红黑树 )作为其底层结果,容器中的元素是一个有序的序列。下面一依次介绍每一个容器。

set

set的介绍

set的官方文档介绍:set - C++ Reference (cplusplus.com)

  • 1. set是按照一定次序存储元素的容器
  • 2. set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。
  • 3. 在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行排序。
  • 4. set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对子集进行直接迭代。
  • 5. set在底层是用二叉搜索树(红黑树)实现的。 ​​​​​​​

注意:

  • 1. map/multimap不同,map/multimap中存储的是真正的键值对set中只放value,但在底层实际存放的是由构成的键值对。
  • 2. set中插入元素时,只需要插入value即可,不需要构造键值对。
  • 3. set中的元素不可以重复(因此可以使用set进行去重)
  • 4. 使用set的迭代器遍历set中的元素,可以得到有序序列
  • 5. set中的元素默认按照小于来比较
  • 6. set中查找某个元素,时间复杂度为:
  • 7. set中的元素不允许修改(为什么?)因为修改了以后它就不是一颗搜索树了
  • 8. set中的底层使用二叉搜索树(红黑树)来实现

set的使用

1. set的模板参数列表

C++进阶之map与set_第1张图片

T: set 中存放元素的类型,实际在底层存储 的键值对。
Compare set 中元素默认按照小于来比较(就是个仿函数)
Alloc set 中元素空间的管理方式,使用 STL 提供的空间配置器管理

2.set的使用

void test_set1()
{
	//排序+去重
	set s;
	s.insert(3);
	s.insert(1);
	s.insert(8);
	s.insert(2);
	s.insert(5);
	s.insert(5);
	s.insert(5);

	//set::iterator it = s.begin();
	auto it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
}

排序加去重,排序也就是进行中序遍历。

C++进阶之map与set_第2张图片

3.erase

删除一个位置必须保证删除的位置的迭代器是有效的。

C++进阶之map与set_第3张图片删除一个值

C++进阶之map与set_第4张图片这个地方为什么不用bool值呢?这就与multiset有关。

multiset

multiset的介绍

multiset的官方文档介绍:multiset - C++ Reference (cplusplus.com)

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

multiset的使用

此处只简单演示setmultiset的不同,其他接口接口与set相同,大家可参考set

如果我们不想去重仅仅就想排序该怎么办呢?

multi的意思是多样的,多种的。它依然是在我们set里面的。

C++进阶之map与set_第5张图片

我们发现multiset是不进行去重操作的,multi就是多种多样,允许我们的数据有重复,冗余。

multiset中面对重复的数据它先找到的是那一个重复的数据呢? 

C++进阶之map与set_第6张图片  

我们用find进行验证。find的val有多个值的时候,返回中序第一个val值所在节点的迭代器。

C++进阶之map与set_第7张图片 我们将1改成8再次验证。

C++进阶之map与set_第8张图片

答案就是返回中序遍历的第一个重复值。

如何删除所有的重复数据?

在这里我们以删除1为例

C++进阶之map与set_第9张图片

因为找到的1是从中序的第一个位置开始,所以就可以进行遍历,只要判断出pos是1就将它删除,知道遍历结束。但是我们目前的写法是有问题的。

程序崩溃了。 

C++进阶之map与set_第10张图片它是二叉树,它的迭代器类似于节点的指针,当第一个1这个节点被干掉了,它里面的pos就成了野指针,++pos是找不到下一个位置的。

方法1:

解决方法:定义一个next,将pos赋值给next,对next++,然后删除pos,再将next赋值给pos,也就是提前保存好pos的值。

C++进阶之map与set_第11张图片

方法2:用值去删

所以这时候就明白为什么erase的返回值是返回被删除值的个数,对于set没意义,对于multiset就有意义了。

有几个就去删几个。 

C++进阶之map与set_第12张图片

可以认为这个erase的实现就是依据方法1实现二来的,在方法一中加个计数器,进行封装,本质都是一样的。

我们发现set/multiset是没有修改接口的,我们可以借助迭代器进行修改吗?

经过验证是不可以进行修改的。*pos是个常量。如何做到的呢?

这里的迭代器要去调用operator*和operator->,他俩返回const T的引用就可以了。也就是set的底层普通迭代器和const迭代器在这个地方的实现是一样的,都不允许修改。因为修改了以后就不能保证你还是一个搜索树了。

map

map的介绍

1. map的模板参数说明

C++进阶之map与set_第13张图片

  • key: 键值对中key的类型
  • T: 键值对中value的类型
  • Compare: 比较器的类型,map中的元素是按照key来比较的,缺省情况下按照小于来比较,一般情况下(内置类型元素)该参数不需要传递,如果无法比较时(自定义类型),需要用户自己显式传递比较规则 (一般情况下按照函数指针或者仿函数来传递)
  • Alloc:通过空间配置器来申请底层空间,不需要用户传递,除非用户不想使用标准库提供的空间配置器
  • 注意:在使用map时,需要包含头文件。

2.Pair

map这我们首先得了解pair,因为map是标准的key,value结构,也就是每个节点的位置除了存key,还存了value,那map的key,value是怎么存的呢?它和我们之前实现的不太一样,并不是给一个key给一个value,而是将keyvalue封装到了一个类结构里面去,这个结构叫做pair,pair也叫做键值对。

pair本身的结构也是一个类

 里面有两个成员,

C++进阶之map与set_第14张图片

first就是带一个模板参数也就是key,second就是第二个模板参数也就是value。

注意:
  • 1. map中的的元素是键值对
  • 2. map中的key是唯一的,并且不能修改
  • 3. 默认按照小于的方式对key进行比较
  • 4. map中的元素如果用迭代器去遍历,可以得到一个有序的序列
  • 5. map的底层为平衡搜索树(红黑树),查找效率比较高
  • 6. 支持[]操作符,operator[]中实际进行插入查找。

map的使用

insert

这里我们主要讨论插入的方法1

C++进阶之map与set_第15张图片insert的是一个value_type,value_type就是一个pair。pair第一个参数就是key,第二个参数是mapped_type也就是T,也就是value。

第一种插入方法:

C++进阶之map与set_第16张图片

第二种插入方法:

第三种插入方法:借助make_pair 

make_pair

C++进阶之map与set_第17张图片

是一个函数模板,返回值是pair,返回pair的匿名对象。

C++进阶之map与set_第18张图片

map的遍历

像往常一样写时会报错的

C++进阶之map与set_第19张图片

C++进阶之map与set_第20张图片

解引用返回节点里面的数据,节点里面是数据不是一个值,而是把key和value放到了一个结构里去,也就是pair,所以它的返回值是pair,这里不支持输出pair,因为pair没有重载流提取和流插入 

正确方法:将它的两个值都打印出来 

C++进阶之map与set_第21张图片 或者使用->两者更常用下面这种

C++进阶之map与set_第22张图片

同时可以用范围for

这里的打印出来还是有排序的意思的,按照key去排序,这里我们的key是string类型,所以按照ascll码去排序。 

[ ]的使用

我们假设现在要统计水果的次数

map的key是不支持修改的,value是支持修改的。

C++进阶之map与set_第23张图片

因为key修改会影响树的结构,但value修改是不影响的。

传统写法:

void test2_map()
{
	string arr[] = { "苹果","苹果","香蕉","苹果","香蕉","苹果","樱桃" };

	//key是水果的名称,value是次数
	map countMap;
	for (auto& str : arr)
	{
		//看下水果有没有出现过,如果没有出现过我们就插入,如果已经出现过,就进行修改
		auto ret = countMap.find(str);
		if (ret == countMap.end()) //表示没有找到
		{
			countMap.insert(make_pair(str, 1));
		}
		else
		{
			//find返回的是迭代器
			ret->second++;
		}
	}
	for (auto& kv : countMap)  
	{
		cout << kv.first << ":" << kv.second << endl;
	}
}

C++进阶之map与set_第24张图片缺陷:如果key不在的情况下,查找会走一次这个搜索树,插入的时候也会走一次这个搜索树。

C++进阶之map与set_第25张图片

 优化:

首先明白insert的返回值

C++进阶之map与set_第26张图片 insert的第一个重载有一个返回值,它返回的不是单纯的真假,返回一个pair,pair里的bool很好理解如果key已经有了返回false,没有返回true,那这个迭代器该如何理解呢?

也就是说如果key没有,插入后返回true,迭代器指向新插入的这个节点。

如果key已将存在,则返回false,迭代器还是指向原来的这个节点。     

ps:迭代器含义    

void test2_map()
{
	string arr[] = { "苹果","苹果","香蕉","苹果","香蕉","苹果","樱桃" };

	//key是水果的名称,value是次数
	map countMap;

	for (auto& str : arr)
	{
		auto kv = countMap.insert(make_pair(str, 1)); //先不管你在不在,直接插入
		//一种情况是这个水果不在,插入成功,给1次 二、这个水果已经出现过,插入失败应该对次数++
		if (kv.second == false)
		{
			kv.first->second++;
		}
	}

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

C++进阶之map与set_第27张图片

C++进阶之map与set_第28张图片 这个方法最大有点就是insert既充当了插入,又充当了查找。

方法二:

	for (auto& str : arr)
	{
		countMap[str]++;
	}

C++进阶之map与set_第29张图片

原理解释: 

C++进阶之map与set_第30张图片

之前我们的[ ] 支持的是随机访问,但是这里的[ ]肯定不是随机访问,因为它是一颗树。

 同时它还等价于这个,大概认为它是这一样实现的

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

C++进阶之map与set_第31张图片

我们简化下这句代码:

C++进阶之map与set_第32张图片

C++进阶之map与set_第33张图片

我们结合我们统计水果次数的例子深入理解一下:

这里我们的key_type就是string ,mapped_type就是int ,他去调用insert的时候,我也不知道水果到底出现过了没,所以我们先插入,这里的k就给的是水果,value给的是mapped_type的匿名对象,这个匿名对象对于int来说就是0(C++对内置类型也进行了升级,使得他们也有默认的构造函数),插入的时候可能插入成功或者插入失败,如果这个水果没有就插入成功了,插入成功后就返回pair,pair的迭代器就是新插入水果节点的迭代器,再对这个迭代器解引用就是(我们的简化版就是直接用的箭头)这个节点里面所在水果的key,value,key是刚插入的水果,value是0。而且这里返回的是这个次数的引用。刚开始返回的是0次,countMap[str]++就变成了1次,这个++是作用在函数调用的返回值上面的;第二次苹果再来了以后,insert的时候,k就给的是水果,value给的是mapped_type的匿名对象,还是0。但此时苹果已经有了,会插入失败,迭代器就返回之前那个苹果的迭代器,上次苹果中的次数已经变成1次了,然后返回第一次苹果的节点里面的second的别名,再进行countMap[str]++,就变成了2次。以此类推...

[ ]的功能

1、插入 2、查找(key对应value) 3、修改(key对应value)

如果水果第一次出现,对应的是插入+修改,水果不是第一次出现,对应查找+修改

对应用法举例

C++进阶之map与set_第34张图片

 但是建议查找最好还是用find,因为如果没有key,[ ]就成了插入。

multimap

multiset的介绍

multiset的官方文档介绍:multimap - C++ Reference (cplusplus.com)

  • 1. multimaps是关联式容器,它按照特定的顺序,存储由keyvalue映射成的键值对,其中多个键值对之间的key是可以重复的。
  • 2. multimap中,通常按照key排序和惟一地标识元素,而映射的value存储与key关联的内容。key和value的类型可能不同,通过multimap内部的成员类型value_type组合在一起,value_type是组合key和value的键值对: typedef pair value_type;
  • 3. 在内部,multimap中的元素总是通过其内部比较对象,按照指定的特定严格弱排序标准对key进行排序的。
  • 4. multimap通过key访问单个元素的速度通常比unordered_multimap容器慢,但是使用迭代器直接遍历multimap中的元素可以得到关于key有序的序列。
  • 5. multimap在底层用二叉搜索树(红黑树)来实现。
注意: multimap map 的唯一不同就是: map 中的 key 是唯一的,而 multimap key 是可以重复的

multimap的使用

multimap 中的接口可以参考 map ,功能都是类似的。
注意:
  • 1. multimap中的key是可以重复的。
  • 2. multimap中的元素默认将key按照小于来比较
  • 3. multimap中没有重载operator[]操作
  • 4. 使用时与map包含的头文件相同:

同样是允许数据冗余

C++进阶之map与set_第35张图片

multimap就没有[ ]了,因为我现在有很多的key,到时候返回的时候我就不知道到底该返回那个key对应的value。其他操作都一样

cout

cout的作用也是查找,但更大的意义是用来计数,适合multimap使用

C++进阶之map与set_第36张图片

erase

multimap中的erase就是把所有的key都删除调

C++进阶之map与set_第37张图片

练习题

一、统计前k种水果

1. 本公司现在要给公司员工发波福利,在员工工作时间会提供大量的水果供员工补充营养。由于水果种类比较多,但是却又不知道哪种水果比较受欢迎,然后公司就让每个员工报告了自己最爱吃的k 种水果,并且告知已经将所有员工喜欢吃的水果存储于一个数组中。然后让我们统计出所有水果出现的次数,并且求出大家最喜欢吃的前k 种水果。

以该组数据为例,k值传3. 

思路:利用一个map(countMap)进行统计次数,然后在进行排序,取出前k种即可。

方法一:

统计次数我们已轻车熟路,重要的就是进行排序,我们首先使用sort进行排序,但是sort要求的是随机迭代器,因为sort底层是快排,需要三数取中,也就是需要迭代器可以做减法操作,显然这里传map的迭代器是不行的。

但是我们可以将countMap中的数据放到vector中,sort是支持vector的迭代器的。

C++进阶之map与set_第38张图片但此时我们排序的结果是这样的,那么pair支持排序吗? 

pair是支持比较大小的,但是它比较大小是先比first,first相等再去比second

C++进阶之map与set_第39张图片

但是我们不想去比first,只需要去比较second,我们写个仿函数就可以解决了。

仿函数:

struct CountVal
{
	//仿函数最大的优势就是你可以控制它排序的方式
	bool operator()(const pair& l, const pair& r)
	{
		return l.second > r.second; //>就是降序 ,<就是升序
	}
};

完整代码演示:

struct CountVal
{
	//仿函数最大的优势就是你可以控制它排序的方式
	bool operator()(const pair& l, const pair& r)
	{
		return l.second > r.second; //>就是降序 ,<就是升序
	}
};

void GetFavoriteFruit(const vector& fruits, size_t k)
{
	//统计次数
	map countMap;
	for (auto& str : fruits)
	{
		countMap[str]++;
	}

    //排序
	vector> sortV;
	for (auto& kv : countMap)
	{
		sortV.push_back(kv);
	}
	sort(sortV.begin(), sortV.end(), CountVal());//这里的sort是不知道如何排pair,pair支持比较吗?
	for (auto&kv : sortV)
	{
		cout <

C++进阶之map与set_第40张图片

方法一的优化:

目前我们方法一存在的问题:sortV插入数据的时候得开很大的空间,如果pair里面存的很大的话。同时存在深拷贝问题。

优化方法:pair很大,但是迭代器很小,里面只存一个节点的指针,直接在vector里面存map的迭代器,只有4或者8个字节。所以我们要对仿函数进行修改,vector中存的是迭代器则访问成员时使用->即可。

完整代码:

struct CountValiterator
{
	//仿函数最大的优势就是你可以控制它排序的方式
	bool operator()(const map::iterator& l, const map::iterator& r)
	{
		return l->second > r->second; //>就是降序 ,<就是升序
	}
};
void GetFavoriteFruit2(const vector& fruits, size_t k)
{
	//统计次数
	map countMap;
	for (auto& str : fruits)
	{
		countMap[str]++;
	}

	//上面方法存在的问题:sortV插入数据的时候得开很大的空间,如果pair里面存的很大的话
	vector::iterator> sortV; //pair很大,但是迭代器很小,里面只存一个节点的指针,直接在vector里面存map的迭代器,只有4或者8个字节
	auto it = countMap.begin();
	while (it != countMap.end())
	{
		sortV.push_back(it);
		++it;
	}
    
	sort(sortV.begin(), sortV.end(), CountValiterator());//这里的sort是不知道如何排pair,pair支持比较吗?
	for (int i = 0; i < k; i++)
	{
		cout << sortV[i]->first << ":" << sortV[i]->second << endl;
	}
}

C++进阶之map与set_第41张图片

方法二:

C++进阶之map与set_第42张图片

 利用multimap进行排序,因为它不会去重,但这里我们是要对数字进行排序,所以要让map中的value,做multimap中的key。但multimap默认排序时升序,因为它compare的缺省值是less

C++进阶之map与set_第43张图片

取出前三种水果可以用反向迭代器倒着取遍历。

C++进阶之map与set_第44张图片

如果不使用反向迭代器,我们就可以使用仿函数:great官方提供的降序排序

C++进阶之map与set_第45张图片

完整代码(使用反向迭代器)

void GetFavoriteFruit3(const vector& fruits, size_t k)
{
	//统计次数
	map countMap;
	for (auto& str : fruits)
	{
		countMap[str]++;
	}

	//排序,它不会去重
	multimap sortMap;
	for (auto& kv : countMap)
	{
		sortMap.insert(make_pair(kv.second, kv.first));
	}

	//取出前三种水果可以用反向迭代器倒着取遍历
	auto it = sortMap.rbegin();
	while (it != sortMap.rend() && k != 0)
	{
		cout << it->second << ":" << it->first << endl;
		++it;
		k--;
	}
	
}

完整代码(不使用反向迭代器)

void GetFavoriteFruit3(const vector& fruits, size_t k)
{
	//统计次数
	map countMap;
	for (auto& str : fruits)
	{
		countMap[str]++;
	}
	
	//取出前三种水果,不使用反向迭代器,就使用仿函数
	multimap> sortMap;
	for (auto& kv : countMap)
	{
		sortMap.insert(make_pair(kv.second, kv.first));
	}

	for (auto& kv : sortMap)
	{
		if (k)
		{
			cout << kv.second << ":" << kv.first << endl;
			k--;
		}
	}

}

方法三:

利用优先级队列也就是堆

struct CountValpq
{
	//仿函数最大的优势就是你可以控制它排序的方式
	bool operator()(const pair& l, const pair& r)
	{
		return l.second < r.second; //>建大堆 ,<就是建小堆
	}
};
void GetFavoriteFruit4(const vector& fruits, size_t k)
{
	//统计次数
	map countMap;
	for (auto& str : fruits)
	{
		countMap[str]++;
	}
    
	//利用优先级队列--堆
	priority_queue < pair, vector>, CountValpq>  pq; //类模板传类型,函数模板传对象
	for (auto& kv : countMap)
	{
		pq.push(kv);
	}

	while (k--)
	{
		cout << pq.top().first << ":" << pq.top().second << endl;
		pq.pop();
	}
	cout << endl;

}

C++进阶之map与set_第46张图片方法三的优化:

类似于方法一的优化我们改为传迭代器

struct CountValiteratorpq
{
	//仿函数最大的优势就是你可以控制它排序的方式
	bool operator()(const map::iterator& l, const map::iterator& r)
	{
		return l->second < r->second; //>建大堆 ,<就是建小堆
	}
};
void GetFavoriteFruit5(const vector& fruits, size_t k)
{
	//统计次数
	map countMap;
	for (auto& str : fruits)
	{
		countMap[str]++;
	}

	//利用优先级队列--堆
	priority_queue < map::iterator, vector::iterator>, CountValiteratorpq>  pq; //类模板传类型,函数模板传对象
	auto it = countMap.begin();
	while (it != countMap.end())
	{
		pq.push(it);
		++it;
	}

	while (k--)
	{
		cout << pq.top()->first << ":" << pq.top()->second << endl;
		pq.pop();
	}
	cout << endl;

}

C++进阶之map与set_第47张图片

二、前K个高频单词 

给定一个单词列表 words 和一个整数 k ,返回前 k 个出现次数最多的单词。返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率, 按字典顺序排序

示例 1:

输入: words = ["i", "love", "leetcode", "i", "love", "coding"], k = 2
输出: ["i", "love"]
解析: "i" 和 "love" 为出现次数最多的两个单词,均为2次。
    注意,按字母顺序 "i" 在 "love" 之前。

示例 2:

输入: ["the", "day", "is", "sunny", "the", "the", "the", "sunny", "is", "is"], k = 4
输出: ["the", "is", "sunny", "day"]
解析: "the", "is", "sunny" 和 "day" 是出现次数最多的四个单词,
    出现次数依次为 4, 3, 2 和 1 次。

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/top-k-frequent-words
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/top-k-frequent-words
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

在讲过上道题以后,这个题就显得比较简单了,但是不同的是这道题的难度在于排完序后,如果k值相同,还要将它们按照字符顺序进行输出。也就是说如果k值相同还要将他们按照ascii码值进行排序。这里我们用sort就不行了,因为sort的底层是快排,所以它是一个不稳定的排序。(稳定性博主在讲排序的时候介绍过)。

解决方法:博主在这采用的是multimap进行排序,因为起初用map统计的时候,map会对key进行排序,对应到这道题map就默认会将这些字符串按照ASCII码进行排序。之后将map中的数据放到multimap中的时候是会按照这些字符串的顺序进行插入的,然后根据int值进行比较。所以就解决了稳定性的问题。

完整代码演示:

class Solution {
public:
    vector topKFrequent(vector& words, int k) {
        
        //统计次数
        map countMap;
        for(auto&str : words)
        {
            countMap[str]++;
        }

        //排序 
        multimap> sortMap;
        for(auto&kv : countMap)
        {
            sortMap.insert( make_pair(kv.second, kv.first) );
        }
         
        //排好序后放到vector中 
        vector v;
        for(auto&kv : sortMap)
        {
            if(k)
            {
                v.push_back(kv.second);
                k--;
            }
        }

        return v; 
    }
};

C++进阶之map与set_第48张图片

其他解决方法:既然sort不能用,但是我们可以使用stable_sort,这个算法就可以保持稳定性

C++进阶之map与set_第49张图片

你可能感兴趣的:(C++,c++,map,set,multimap,multiset)