【C++】STL——set和map及multiset和multiset的介绍及使用


🚀 作者简介:一名在后端领域学习,并渴望能够学有所成的追梦人。
🚁 个人主页:不 良
🔥 系列专栏:🛸C++  🛹Linux
📕 学习格言:博观而约取,厚积而薄发
🌹 欢迎进来的小伙伴,如果小伙伴们在学习的过程中,发现有需要纠正的地方,烦请指正,希望能够与诸君一同成长! 🌹


map和set底层结构都是二叉搜索树。

关联式容器

在前面学过的STL中的部分容器,比如:vector、list、deque、forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身

关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高。本篇介绍的set和map就是关联式容器。

关联式容器中数据和数据之间有非常强的关联关系,不能随意插入。

键值对

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

比如:现在要建立一个英汉互译的字典,那该字典中必然有英文单词与其对应的中文含义,而且英文单词与其中文含义是一一对应的关系,即通过输入单词,在词典中就可以找到与其对应的中文含义。

SGI-STL中关于键值对的定义:

template <class T1, class T2>
struct pair
{typedef T1 first_type;typedef T2 seco_type;T1 first;//keyT2 second;//valuepair():first(T1()),second(T2()){}//默认构造pair(const T1& a, const T2& b):first(a),second(b){}//构造函数
};

在上面的pair类中:

  • 是一个模板类,可以存放任意类型的键对值;
  • 有两个成员变量:first和second;

一般使用first表示key,second表示value。 键值对要在类外进行访问,所以使用struct定义类而不用class。

make_pair

该函数用来创建pair类型的匿名对象。

根据上面pair类的定义,我们要想创建一个pair类型的匿名对象需要像下面这样写:

pair<string,int>("苹果",1);

上面的方式还需要指定参数类型,所以为了方便,就封装了一个函数模板make_pair,用来快速创建一个匿名对象:

make_pair("苹果",1);

make_pair函数模板的定义如下:

image-20230725171320265

树形结构的关联式容器

根据应用场景的不同,STL总共实现了两种不同结构的管理式容器:树型结构与哈希结构。

树型结构的关联式容器主要有四种:map、set、multimap、multiset。这四种容器的共同点是:使用平衡搜索树(即红黑树)作为其底层结果,容器中的元素是一个有序的序列。 与树形结构容器不同的是,哈希结构容器中的元素是一个无序的序列。

set

认识set

  • set是按照一定次序存储元素的容器,使用set的迭代器遍历set中的元素,可以得到有序序列(默认是升序);

  • set虽然是采用了键值对的方式存储数据,但是在set中,value就是key,即相当于是K模型,并且每个value必须是唯一的,不可以重复,所以可以使用set进行去重;

  • 与map/multimap不同,map/multimap中存储的是真正的键值对<key, value>,set中只放value,但在底层实际存放的是由<value, value>构成的键值对,因此在set容器中插入元素时,只需要插入value即可,不需要构造键值对;

  • set中的元素不能在容器中修改(元素总是const),因为set在底层是用二叉搜索树来实现的,若是对二叉搜索树当中某个结点的值进行了修改,那么这棵树将不再是二叉搜索树。但是可以从容器中插入或删除它们;

  • 在内部,set中的元素总是按照其内部比较对象所指示的特定严格弱排序准则进行排序。当不传入内部比较对象时,set中的元素默认按照小于来比较(默认是升序);

  • set容器通过key访问单个元素的速度通常比unordered_set容器慢,但set容器允许根据顺序对元素进行直接迭代;

  • set在底层是用平衡搜索树(红黑树)实现的,所以在set当中查找某个元素的时间复杂度为logN。

set模板参数列表

image-20230725174304374

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

构造函数

image-20230725174650796

构造函数有三个,分别是默认构造函数、使用迭代器区间的构造函数、拷贝构造函数。

下面用代码来使用上面三个构造函数:

#include <iostream>
#include <set>
#include <vector>
using namespace std;
int main()
{set<int> s1;//默认构造函数vector<int> v = { 66,5,4,11,66,66,77,8,9,22,10,99 };set<int> s2(v.begin(), v.end());//迭代器区间构造set<int> s3(s2);//拷贝构造//打印s1for (auto& e : s1){cout << e << " ";}cout << endl;//s1内容为空//打印s2for (auto& e : s2){cout << e << " ";}cout << endl;//s2打印出来为4 5 8 9 10 11 22 66 77 99//打印s3for (auto& e : s3){cout << e << " ";}cout << endl;//s3打印出来为4 5 8 9 10 11 22 66 77 99
}

运行结果分析:

image-20230726075359482

根据结果我们可以看到set具有排序的功能(默认是升序),而且在数组中相同的元素经放在set容器之后就没有相同的元素了,说明了set具有去重的功能。set具有去重的功能,其底层是二叉搜索树,不能存放相同的元素。

迭代器

set的输出结果是升序的,其底层是二叉搜索树,打印二叉搜索树时使用中序打印出来的就是升序,所以set的迭代器在++时,移动的顺序就是中序遍历的顺序,所以使用迭代器遍历时得到的结果和二叉搜索树中序遍历得到的结果一致。

set迭代器的使用和之前学过的容器一样:

image-20230726080313203

具体使用示范代码:

#include <iostream>
#include <set>
#include <vector>
using namespace std;
int main()
{vector<int> v = { 66,5,4,11,66,66,77,8,9,22,10,99 };set<int> s1(v.begin(), v.end());//迭代器区间构造//正向迭代器的使用set<int>::iterator it = s1.begin();while (it != s1.end()){cout << *it << " ";it++;}cout << endl;//反向迭代器的使用set<int>::reverse_iterator rit = s1.rbegin();while (rit != s1.rend()){cout << *rit << " ";++rit;}cout << endl;
}

运行结果分析:

image-20230726080905995

插入(insert)

image-20230726081526910

1、插入一个指定值并且返回一个键值对,返回的键值对中迭代器指向新插入的位置。

返回的是pair类型的对象,其中pair的第一个成员是迭代器,其指向的是插入的元素在set中的位置;第二个成员是bool类型,如果成功插入pair类中的第二个成员为true,插入失败pair类中的第二个成员为false。所以这里insert能够做到两个功能:查找和插入(查找成功返回false,查找失败返回true)。

pair<iterator,bool> insert (const value_type& val);

2、在指定的位置插入数据,并且返回插入的位置:

iterator insert (iterator position, const value_type& val);

3、将一段迭代器区间插入到set对象中:

template <class InputIterator>void insert (InputIterator first, InputIterator last);

向set中插入一个对象,并且能够保持结构不变,依然符合平衡搜索树。

代码示范:

#include <iostream>
#include <set>
#include <vector>
using namespace std;
int main()
{vector<int> v = { 66,5,4,11,66,66,77,8,9,22,10,99 };set<int> s1(v.begin(), v.end());//迭代器区间构造//正向迭代器的使用set<int>::iterator it = s1.begin();while (it != s1.end()){cout << *it << " ";it++;}cout << endl;//构造一个pair类型的对象pair<set<int>::iterator, bool> p;//接收插入之后的返回值p = s1.insert(67);it = s1.begin();//打印插入的值和是否成功cout << *(p.first) << ":" << p.second << endl;p = s1.insert(67);//打印插入的值和是否成功cout << *(p.first) << ":" << p.second << endl;
}

打印结果:

image-20230726083811202

指定位置插入有可能会破环二叉搜索树的结构,所以不建议使用指定位置插入。

删除(erase)

image-20230726084117189

删除指定迭代器位置的值:

void erase (iterator position);

删除指定值并且返回删除指定值的个数(在set容器中没有重复值所以返回值为1,在multiset中才能体现出来):

size_type erase (const value_type& val);

删除迭代器区间中所有的值(迭代器区间是set的迭代器区间):

void erase (iterator first, iterator last);

在set中删除一个数据,并且保持结构不变。

查找(find)

iterator find (const value_type& val) const;

查找set中某个元素,如果存在返回所在位置的迭代器,如果不存在,就返回迭代器end()。

代码示范:

#include <iostream>
#include <set>
#include <vector>
using namespace std;
int main()
{vector<int> v = { 66,5,4,11,66,66,77,8,9,22,10,99 };set<int> s1(v.begin(), v.end());//迭代器区间构造//正向迭代器的使用set<int>::iterator it = s1.begin();while (it != s1.end()){cout << *it << " ";it++;}cout << endl;set<int>::iterator it1 = s1.find(66);//存在返回所在值的迭代器位置set<int>::iterator it2 = s1.find(666);//不存在返回end()if (it1 != s1.end())cout << "该值存在" << ":" << *(it1) << endl;elsecout << "该值不存在" << endl;
}

这里和find功能类似的还有一个count函数:

size_type count (const value_type& val) const;

返回指定数据的个数,如果不存在则返回0。

但是由于set具有去重的功能,所以这里的返回值要么是1,要么是0,在set容器中可以用来查找数据是否存在。

获取上下边界

获取下边界(以传入的值为下限,返回一个迭代器):

iterator lower_bound (const value_type& val) const;

获取上边界(以传入的值为上限,返回一个迭代器):

iterator upper_bound (const value_type& val) const;

代码示范:

#include <iostream>
#include <set>
#include <vector>
using namespace std;
int main()
{vector<int> v = { 66,5,4,11,66,66,77,8,9,22,10,99 };set<int> s1(v.begin(), v.end());//迭代器区间构造//正向迭代器的使用set<int>::iterator it = s1.begin();while (it != s1.end()){cout << *it << " ";it++;}cout << endl;//输出4 5 8 9 10 11 22 66 77 99set<int>::iterator it1 = s1.lower_bound(11);//获取11所在位置的迭代器set<int>::iterator it2 = s1.upper_bound(66);//获取66下一个数据的迭代器s1.erase(it1, it2);//删除是左闭右开的,获取66下一个位置的迭代器才能将66也删除it = s1.begin();while (it != s1.end()){cout << *it << " ";it++;}cout << endl;//输出4 5 8 9 10 77 99
}

其他相关操作

函数函数说明
swap交换两个set对象
clear清除set对象中所有数据,但是不删除set对象
empty()判断set是否为空,如果为空返回true,否则返回flase
size()返回容器的数据个数,类型为size_t(unsigned int)
max_size()返回容器中最多能够存放的数据个数

multiset

multiset和set几乎一致,在同一个头文件中,set的功能是排序和去重,而multiset容器仅仅完成排序,容器中允许有相同的数据存在(区别在于multiset支持数据重复,而set不可以)。

在set容器中count函数和erase函数中第二个重载函数(返回被删除个数)没有实际作用,但在multiset中就能够体现它们的作用:

#include <iostream>
#include <set>
#include <vector>
using namespace std;
int main()
{vector<int> v = { 66,5,4,11,66,66,9,22,10,4};multiset<int> s1(v.begin(), v.end());//迭代器区间构造//正向迭代器的使用multiset<int>::iterator it = s1.begin();while (it != s1.end()){cout << *it << " ";it++;}cout << endl;//输出4 4 5 9 10 11 22 66 66 66int num1 = s1.count(66);//统计元素66的数据个数cout << num1 << endl;//输出3int num2 = s1.erase(66);//删除元素66并且返回删除个数cout << num2<< endl;//输出3it = s1.begin();while (it != s1.end()){cout << *it << " ";it++;}cout << endl;
}

在查找函数find中返回的迭代器位置是查找到的第一个数据的迭代器位置。

map

map是一个关联式容器,底层是二叉搜索树。

image-20230726093903423

map中存放的是键值对,有两个模板参数Key和T,组成键值对。

认识map

  • map是关联式容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元素;
  • 在map中,键值key通常用于排序和唯一的标识元素,而值value中存储与此键值key关联的内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型value_type绑定在一起,为其取别名称为pair:typedef pair<const key, T> value_type;
  • 在内部,map中的元素总是按照键值key进行比较排序的;
  • map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列);
  • map支持下标访问符,即在[]中放入key,就可以找到与key对应的value;
  • map的底层结构为平衡二叉搜索树(红黑树)。

构造函数

image-20230726095507222

默认构造函数创建的map对象为空

explicit map (const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());

插入时可以使用pair创建对象,也可以使用make_pair创建匿名对象:

#include <iostream>
#include <map>
using namespace std;
int main()
{map<string, int> fruit;for (auto& e : fruit){cout << e.first << ":" << e.second << endl;}//插入时可以使用pair创建对象,也可以使用make_pair创建匿名对象pair<string, int> p("苹果", 1);fruit.insert(p);fruit.insert(make_pair("香蕉", 1));for (auto& e : fruit){cout << e.first << ":" << e.second << endl;}return 0;
}

打印结果:

image-20230726100234187

也可以使用迭代器区间进行构造:

template <class InputIterator>map (InputIterator first, InputIterator last,const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());

代码示范:

#include <iostream>
#include <vector>
#include <map>
using namespace std;
int main()
{vector<pair<string, int>> v = { {"香蕉",2},{"苹果",3}, {"梨",2}, {"西瓜",6} };map<string, int> fruit(v.begin(),v.end());//使用vector迭代器区间进行构造for (auto& e : fruit){cout << e.first << ":" << e.second << endl;}//进行拷贝构造//auto f(fruit);//可以使用自动推导类型map<string, int> f(fruit);for (auto& e : f){cout << e.first << ":" << e.second << endl;}
}

打印结果:

image-20230726100759204

注意:

map具有去重的功能,不能插入相同的键值对(不支持重复的数据),同时不能插入key值相同value值不同的键值对。

map的判断逻辑都是只看key值,即first所表示的变量。

插入(insert)

image-20230726102534838

map的插入函数和set的一样,只是map中插入的是键值对。

第一个函数中返回的是一个键值对,返回的键值对pair<iterator,bool>,first是插入键值对所在位置的迭代器,second是bool值,如果插入成功bool值为true,插入失败bool值为false。

迭代器区间插入时,如果map中已经存在key值就不再进行插入。

代码示范:

#include <iostream>
#include <vector>
#include <map>
using namespace std;
int main()
{map<string, int> fruit;//使用默认构造函数pair<map<string, int>::iterator, int> ret = fruit.insert(make_pair("黄瓜", 6));//插入一个键值对,返回键值对for (auto& e : fruit){cout << e.first << ":" << e.second << endl;}cout << endl;vector<pair<string, int>> v = { {"香蕉",2},{"苹果",3}, {"梨",2}, {"西瓜",6} };fruit.insert(v.begin(), v.end());//插入迭代器区间的数据for (auto& e : fruit){cout << e.first << ":" << e.second << endl;}
}

打印结果:

image-20230726103605498

除了像插入这种增加操作,需要一个键值对,其他操作都是根据键值对中的键值key来处理的。

查找(find)

      iterator find (const key_type& k);
const_iterator find (const key_type& k) const;

根据指定要查找键值对中的键值key去查找,find函数根据key去查找是否存在的。

如果存在,找到以后返回键值对所在位置的迭代器,没有找到返回end迭代器。

代码示范:

#include <iostream>
#include <vector>
#include <map>
using namespace std;
int main()
{map<string, int> fruit;vector<pair<string, int>> v = { {"香蕉",2},{"苹果",3}, {"梨",2}, {"西瓜",6} };fruit.insert(v.begin(), v.end());for (auto& e : fruit){cout << e.first << ":" << e.second << endl;}cout << endl;map<string,int>::iterator ret = fruit.find("香蕉");if (ret != fruit.end())cout << (*ret).first << ":" << (*ret).second << endl;//cout << ret->first << ":" <<  ret->second << endl;elsecout << "找不到" << endl;
}

在上面的代码中cout << (*ret).first << ":" << (*ret).second << endl;输出的时候我们可以先对迭代器解引用再调用first和second,但是平时使用更多的还是->cout << ret->first << ":" << ret->second << endl;这种方式,这里应该有两个->但是这里省略了一个。

打印结果:

image-20230726105107481

map和set一样,不支持修改,因为可能会破坏二叉搜索树的结构

operator[]

我们可以使用map统计字符数组中水果出现的次数:

#include <iostream>
//#include <set>
#include <vector>
#include <string>
#include <map>
using namespace std;
int main()
{string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };map<string, int> countMap;for (auto& e : arr){//通过查找函数,找到返回所在位置的迭代器,没找到返回end迭代器map<string, int>::iterator it = countMap.find(e);if (it == countMap.end()){//等于end迭代器时,插入countMap.insert(make_pair(e, 1));}else{//不等于时second++it->second++;}}for (auto& e : countMap){cout << e.first << ":" << e.second << endl;}
}

打印结果:

image-20230726111618617

但是在map中也可以使用[],返回的是第二个模板参数,所以上面的代码可以这样写:

#include <iostream>
#include <vector>
#include <string>
#include <map>
using namespace std;
int main()
{string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };map<string, int> countMap;for (auto& e : arr){countMap[e]++;}for (auto& e : countMap){cout << e.first << ":" << e.second << endl;}
}

operator[]在库中的声明:

image-20230726112056475

可以看到函数参数是键值对中的key值,也就是说key值充当下标。

image-20230726154128391

调用此函数相当于调用:

(*((this->insert(make_pair(k,mapped_type()))).first)).second

image-20230726160822860

总结来说三个步骤:

1、调用insert函数插入键值对;

2、获取insert函数插入成功之后返回的键值对中的first迭代器参数;

3、拿到该迭代器位置键值对中的second,并返回。

对应分解代码如下:

mapped_type& operator[](const key_type& k)
{//1、调用insert函数插入键值对pair<map<k,mappd_typed()>::iterator, bool> ret = insert(make_pair(k, mapped_type()));//2、获取insert函数插入成功之后返回的键值对中的first迭代器参数;map<k,mappd_typed()>::iterator it = ret.first;//3、拿到该迭代器位置键值对中的second,并返回return (*it).second;//return it->second;
}

也可以这样理解:

V& operator[](const K& key)
{//1、查找 2、插入pair<map<k,V()>::iterator, bool> ret = insert(make_pair(key, V());//3、修改return ret.first->second;
}

那么countMap[e]++;是如何实现插入、查找、修改的呢?

首先插入e,map中如果不存在e,则boo设置为true,成功插入,此时返回的是插入位置second的引用,引用为0,++之后变成1;如果map中已经存在e,bool值为false,直接返回e位置键值对的second的引用,并且++。

其他相关操作

函数函数说明
begin和end获取容器中第一个元素的迭代器和最后一个元素下一个位置的迭代器
rbegin和rend获取容器中最后一个元素的迭代器和第一个元素前一个位置的迭代器
size获取容器中元素的个数
empty判断容器是否为空
clear清空容器
swap交换两个容器中的数据
count获取容器中指定key值的元素个数

multimap

multimap和map都在头文件map中,multimap的使用和map一样(multimap支持相同数据,而map不支持)。

#include <iostream>
#include <vector>
#include <string>
#include <map>
using namespace std;
int main()
{vector<pair<string, int>> v = { {"西瓜",1},{"香蕉" ,2},{"西瓜",2},{"芒果",3} };multimap<string, int> countMap;for (auto& e : v){countMap.insert(e);}for (auto& e : countMap){cout << e.first << ":" << e.second << endl;}
}

打印结果:

image-20230726163617862

但是要注意的是multimap中没有重载[],因为multimap中允许有多个重复键值对,如果重载的话不知道返回哪个。

image-20230726163832067

multimap的inser函数中,没有返回键值对的,返回的是键值对插入之后所在位置的迭代器。

map和set在OJ中的使用

复制带随机指针的链表

题目:复制带随机指针的链表

给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。

构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。

例如,如果原链表中有 XY 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 xy ,同样有 x.random --> y

返回复制链表的头节点。

用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:

  • val:一个表示 Node.val 的整数。
  • random_index:随机指针指向的节点索引(范围从 0n-1);如果不指向任何节点,则为 null

你的代码 只 接受原链表的头节点 head 作为传入参数。

示例 1:

img

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

示例 2:

img

输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]

示例 3:

img

输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]

提示:

  • 0 <= n <= 1000
  • -104 <= Node.val <= 104
  • Node.randomnull 或指向链表中的节点。

思路:

可以使用map容器让源节点和拷贝结点之间建立kv关系,可以根据map中的kv关系找到random指针指向。

class Solution {
public:Node* copyRandomList(Node* head) {map<Node*,Node*> copyNodeMap;Node* cur = head;Node* copyhead = nullptr;Node* copytail = nullptr;//先将链表中的结点都拷贝while(cur){//创建新结点Node* copy = new Node(cur->val);//源节点和拷贝结点建立链接关系copyNodeMap[cur] = copy;if(copytail == nullptr){copyhead = copytail = copy;}else{copytail->next = copy;copytail = copytail->next;}cur = cur->next;}cur = head;Node* copy = copyhead;while(cur){if(cur->random == nullptr)copy->random = nullptr;else{//根据链接关系找到random指针指向copy->random = copyNodeMap[cur->random];}cur = cur->next;copy = copy->next;}return copyhead;}
};

前K个高频单词

题目:前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, 21 次。

注意:

  • 1 <= words.length <= 500
  • 1 <= words[i] <= 10
  • words[i] 由小写英文字母组成。
  • k 的取值范围是 [1, 不同 words[i] 的数量]

思路一:

可以先使用map将words中的单词统计出现次数,因为要根据出现频率由高到低排序,所以必须要将出现次数设置为key,所以可以将second设置为key,first设置为value放入vector数组中(插入之后不能使用sort对key进行排序,sort是不稳定的排序算法,可以使用库中提供的稳定的排序算法stable_sort,),因为stable_sort函数默认是升序,所以可以实现一个仿函数用来由高到低排序;最后将排完序之后的vector中前k个键值对的second(单词)插入到要返回的vector。

要注意在实现仿函数时,参数是pair类型,注意是first参数的比较还是second参数的比较。

思路一:

可以先使用map将words中的单词统计出现次数,然后将其放入vector数组中,对vector数组进行排序,(sort是不稳定的排序算法,可以使用库中提供的稳定的排序算法stable_sort)因为stable_sort/sort函数默认是升序,所以可以实现一个仿函数用来由高到低排序(要注意在实现仿函数时,参数是pair类型,注意是first参数的比较还是second参数的比较);最后将排完序之后的vector中前k个键值对中的first参数(字符)尾插到要返回的vector中。

map不能直接使用sort,因为sort底层是一个随机迭代器,而map底层是一个双向迭代器,所以可以先将数据放到vector中再排序。

代码实现:

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> countwords;//统计单词出现次数for(auto& e : words){countwords[e]++;}//放入vector数组中vector<pair<string,int>> v1(countwords.begin(),countwords.end());//排序,放入仿函数stable_sort(v1.begin(),v1.end(),Greater());//sort(v1.begin(),v1.end(),Greater());vector<string> v2;//将符合条件的放入vector数组中for(int i = 0; i < k; i++){v2.push_back(v1[i].first);}return v2;}
};

思路二:

上面的思路我们使用的是sort进行排序,虽然map本身就能排序,但是因为map的本质是排序加去重,所以不能使用,但是可以使用multimap,这里要注意当两个键值相同的时候要如何保证value的顺序,可以通过仿函数来控制。

代码实现:

class Solution {
public://仿函数struct Greater{bool operator()(const pair<string,int>& kv1,const pair<string,int>& kv2) const{//出现次数大的排前面,当出现次数相同时字母小的排前面return kv1.second > kv2.second || (kv1.second == kv2.second && kv1.first < kv2.first);}};vector<string> topKFrequent(vector<string>& words, int k) {map<string,int> countwords;//统计单词出现次数for(auto& e : words){countwords[e]++;}//这里使用multiset,这里使用仿函数比较multiset<pair<string,int>,Greater> sortset(countwords.begin(),countwords.end());vector<string> v2;//放入vector数组中multiset<pair<string,int>,Greater>::iterator it = sortset.begin();//auto it = sortset.begin();while(k--){v2.push_back(it->first);++it;}return v2;}
};

注意这里的仿函数后面要加上const(避免权限放大问题),不然multiset<pair<string,int>,Greater> sortset(countwords.begin(),countwords.end());这里会报错。

两个数组的交集

题目:两个数组的交集

给定两个数组 nums1nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。

示例1:

输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]

示例2:

输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的

提示:

  • 1 <= nums1.length, nums2.length <= 1000
  • 0 <= nums1[i], nums2[i] <= 1000

思路:

首先就要对已有的两个数组去重,算法库里面有一个去重算法函数unique(必须是有序数组才能去重,可以先sort再去重);这里可以直接使用set;排好序之后找交集:

image-20230726193949626

代码:

class Solution {
public:vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {//去重+排序// set<int> s1;// set<int> s2;// for(auto& e : nums1)// {//     s1.insert(e);// }// for(auto& e : nums2)// {//     s2.insert(e);// }//也可以直接使用迭代器区间构造set<int> s1(nums1.begin(),nums1.end());set<int> s2(nums2.begin(),nums2.end());//set<int>::iterator it1 = s1.begin();//set<int>::iterator it2 = s2.begin();auto it1 = s1.begin();auto it2 = s2.begin();vector<int> v;//在s1里面找s2里的数据while(it1 != s1.end() && it2 != s2.end()){if(*it1 == *it2){v.push_back(*it1);++it1;++it2;}else if(*it1 < *it2){it1++;}else{it2++;}}return v;}
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/20130.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

kafka 理论知识

1 首先要了解kafka是什么 Kafka是一个分布式的消息订阅系统 1.1 kafka存储消息的过程 消息被持久化到一个topic中&#xff0c;topic是按照“主题名-分区”存储的&#xff0c;一个topic可以分为多个partition&#xff0c;在parition(分区)内的每条消息都有一个有序的id号&am…

wxwidgets Ribbon使用简单实例

// RibbonSample.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <wx/wx.h> #include "wx/wxprec.h" #include "wx/app.h" #include "wx/frame.h" #include "wx/textctrl.h" #include "…

大数据学习教程:Linux 高级教程(上)

一、Linux用户与权限 1. 用户和权限的基本概念 1.1、基本概念 用户 是Linux系统工作中重要的一环, 用户管理包括 用户 与 组 管理 在Linux系统中, 不论是由本级或是远程登录系统, 每个系统都必须拥有一个账号, 并且对于不同的系统资源拥有不同的使用权限 对 文件 / 目录 的…

[C++项目] Boost文档 站内搜索引擎(1): 项目背景介绍、相关技术栈、相关概念介绍...

项目背景 Boost库是C中一个非常重要的开源库. 它实现了许多C标准库中没有涉及的特性和功能, 一度成为了C标准库的拓展库. C新标准的内容, 很大一部分脱胎于Boost库中. Boost库的高质量代码 以及 提供了更多实用方便的C组件, 使得Boost库在C开发中会被高频使用 为方便开发者学…

C语言实现定时器,定时触发函数

最近想到使用C语言实现一个简单的定时器。使用操作系统windows.h提供的多线程API就能实现 首先定义一个定时器结构体&#xff0c;包含定时时间和触发的函数指针 typedef struct Stimer{int valid;//定时器有效long timingMS;//定时时间TriggerFunc tf;//触发函数 }Stimer;创建…

【数据结构|二叉树遍历】递归与非递归实现前序遍历、中序遍历、后序遍历

递归与非递归实现二叉树的前序遍历、中序遍历、后序遍历。 二叉树图 定义 前序遍历&#xff08;Preorder Traversal&#xff09;&#xff1a; 前序遍历的顺序是先访问根节点&#xff0c;然后按照先左后右的顺序访问子节点。对于上面的二叉树&#xff0c;前序遍历的结果是&…

Stable Diffusion教程(8) - X/Y/Z 图表使用

1. 介绍 这项功能可以在 文生图/图生图 界面的左下角种 “脚本” 一栏内选择 “X/Y/Z 图表” 以启用。 它创建具有不同参数的图像网格。使用 X 类型和 Y 类型字段选择应由行和列共享的参数&#xff0c;并将这些参数以逗号分隔输入 X 值 / Y 值字段。支持整数、浮点数和范围。…

【工具使用】git基础操作1

目录 一.拉取git代码1.首次拉取命令2.使用图形化拉取代码3.Idea 开发工具拉取代码 二.查看当前状态1.查看在你上次提交之后是否有对文件进行再次修改 三.创建分支3.1.创建分支3.2.创建分支并切换至分支3.3.提交分支至远程仓 远程没有自动创建 四.查看分支4.1.查看本地分支 当前…

【iOS】json数据解析以及简单的网络数据请求

文章目录 前言一、json数据解析二、简单的网络数据请求三、实现访问API得到网络数据总结 前言 近期写完了暑假最后一个任务——天气预报&#xff0c;在里面用到了简单的网络数据请求以及json数据的解析&#xff0c;特此记录博客总结 一、json数据解析 JSON是一种轻量级的数据…

AP5179 高端电流采样降压恒流驱动IC SOP8 LED车灯电源驱动

产品描述 AP5179是一款连续电感电流导通模式的降压恒流源&#xff0c;用于驱动一颗或多颗串联LED输入电压范围从 5 V 到 60V&#xff0c;输出电流 最大可达 2.0A 。根据不同的输入电压和外部器件&#xff0c; 可以驱动高达数十瓦的 LED。内置功率开关&#xff0c;采用高端电流…

PHP8的运算符-PHP8知识详解

运算符是可以通过给出的一或多个值&#xff08;用编程行话来说&#xff0c;表达式&#xff09;来产生另一个值&#xff08;因而整个结构成为一个表达式&#xff09;的东西。 PHP8的运算符有很多&#xff0c;按类型分有一元运算符、二元运算符、三元运算符。 一元运算符只对一…

选择适合的项目管理系统,了解有哪些选择和推荐

随着科技的进步和全球竞争的加剧&#xff0c;项目管理已经成为企业成功的关键要素。为了更好地组织和监控项目&#xff0c;许多企业和组织正在采用项目管理系统(PMS)。本文将探讨项目管理系统的主要组成部分以及其在实际应用中的优势。 “项目管理系统有哪些?国际上比较常见的…

侧边栏的打开与收起

侧边栏的打开与收起 <template><div class"box"><div class"sideBar" :class"showBox ? : controller-box-hide"><div class"showBnt" click"showBox!showBox"><i class"el-icon-arrow-r…

天气API强势对接

&#x1f935;‍♂️ 个人主页&#xff1a;香菜的个人主页&#xff0c;加 ischongxin &#xff0c;备注csdn ✍&#x1f3fb;作者简介&#xff1a;csdn 认证博客专家&#xff0c;游戏开发领域优质创作者,华为云享专家&#xff0c;2021年度华为云年度十佳博主 &#x1f40b; 希望…

分布式系统的 38 个知识点

天天说分布式分布式&#xff0c;那么我们是否知道什么是分布式&#xff0c;分布式会遇到什么问题&#xff0c;有哪些理论支撑&#xff0c;有哪些经典的应对方案&#xff0c;业界是如何设计并保证分布式系统的高可用呢&#xff1f; 1. 架构设计 这一节将从一些经典的开源系统架…

静态路由下一跳地址怎么确定(静态路由配置及讲解)

一、用到的所有命令及功能 ①ip route-static 到达网络地址 子网掩码 下一跳 // 配置静态路由下一跳指的是和当前网络直接连接的路由器的接口地址非直连网段必须全部做路由路径是手工指定的&#xff0c;在大规模网络上不能用&#xff0c;效率低&#xff0c;路径是固定的稳定的…

瑞吉外卖实战-笔记

软件开发的流程 角色分工 软件环境 开发环境的搭建 数据库环境 maven环境 1.创建完成后&#xff0c;需要检查一下编码、maven仓库、jdk等 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</a…

Python实现GA遗传算法优化卷积神经网络分类模型(CNN分类算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 遗传算法&#xff08;Genetic Algorithm&#xff0c;GA&#xff09;最早是由美国的 John holland于20世…

TSINGSEE青犀视频安防监控EasyCVR视频汇聚平台电子地图定位偏移的排查与解决

安防监控EasyCVR视频汇聚综合管理平台具有强大的数据接入、处理及分发能力&#xff0c;平台可提供视频监控直播、云端录像、云存储、录像检索与回看、告警上报与查询、平台级联、云台控制、语音对讲、电子地图、轨迹跟踪、H.265自动转码等视频能力。 在视频监控管理平台TSINGSE…

word转pdf两种方式(免费+收费)

一、免费方式 优点&#xff1a;1、免费&#xff1b;2、在众多免费中挑选出的转换效果相对较好&#xff0c;并且不用像openOffice那样安装服务 缺点&#xff1a;1、对字体支持没有很好&#xff0c;需要安装字体库或者使用宋体&#xff08;对宋体支持很好&#xff09;2、对于使…