深入篇【C++】set和map(multiset/multimap)特性总结与使用
- 一.set/multiset总结
- 二.map/multiset总结
- 三.set/map应用
一.set/multiset总结
- set是按照一定次序存储元素的容器
- 在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。
- 在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行排序。
- set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对子集进行直接迭代(迭代完后有序)。
- set在底层是用二叉搜索树(红黑树)实现的。
底层封装着一个pair类型的对象。这个对象里面存着两个成员变量即key和value。
【函数注意1】
1.set具有排序+去重功能。
2.set的查找函数find:找到了会返回当前结点的迭代器,找不到就会返回最后一个位置的迭代器end()。
3.set还有count函数,这个是用来计算出现的个数的,但对于set而言来说没有用,因为set是去重后,的最后的结果要么是0要么是1,所以count函数还可以用来具有查找功能。
4.count函数主要用于mutilset,mutilset可以允许重复的数据。
5.set与multiset的区别就在于set里面的数据是唯一的,不能出现重复的,是去重的,而multiset是允许出现重复的,不去重的。
【结构注意2】
1. 与map/multimap不同,map/multimap中存储的是真正的键值对<key, value>,set中只放value,但在底层实际存放的是由<value,value>构成的键值对.
2. set中插入元素时,只需要插入value即可,不需要构造键值对。
3. set中的元素不可以重复(因此可以使用set进行去重)。
4. 使用set的迭代器遍历set中的元素,可以得到有序序列
5. set中的元素默认按照小于来比较compare=Less<.T.>
6. set中查找某个元素,时间复杂度为: l o g 2 n log_2 n log2n
7. set中的元素不允许修改(为什么---->const修饰)
8. set中的底层使用二叉搜索树即红黑树
二.map/multiset总结
1.在map中,键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型value_type绑定在一起,为其取别名称为pair:
typedef pair<const key, T> value_type;
template <class T1, class T2>
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)
{}
};
2.底层封装一个pair类型的成员变量。存储着也是pair类型的对象,当要插入一个数据时,这个对象是pair类型的才可以被插入,所以可以使用匿名对象的形式插入
dict.insert(pair<string, string>("run", "启动"));
也可以使用一个make_pair(T,T)函数来返回pair<.T>类型的对象
dict.insert(make_pair("string", "字符串"));
还可以以一种隐式类型转化的方式插入
dict.insert({ "int","整形" });
这里会将这个数组类型转化成pair类型,怎么转化的?调用pair的构造函数,C++11支持双参构造函数调用时会发生隐式类型转化。
3.使用迭代器访问的不是真正的数据,而是pair类型的对象,所以要真正的访问数据,还需要进一步访问。而且第一个数据是不能被修改的,只能访问,第二个数据可以被修改可以被访问。
map<string, string>::iterator it = dict.begin();
while (it != dict.end())
{
//it是迭代器就相当于指针
//正常是*it就是解引用到要访问的对象了,但这里的对象是pair不可以直接打印
cout << (*it).first << (*it).second << endl;
//it->first ="xxx";第一个数据不能被修改
//it->second = "xxxx";第二个数据可以被修改
cout << it->first << it->second << endl;
//可以直接通过指针访问到里面的数据
++it;
}
4.对于map来说也是具有排序和去重功能,而且当插入相同的值。并不会插入进去,不会覆盖原来的数据。
5. 在内部,map中的元素总是按照键值key进行比较排序的。key是无法被修改的,而value的值可以被修改。
6. map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。
7. map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))。
8. map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。只不过与正常的下标访问不同,而是传key值,给你返回的是value值的引用。[ ]的本质就是通过insert完成的。
insert的返回值是pair<iterator,bool>
①当key在树里面,返回的是pair<在树里面key对于结点的iterator,false>。
②当key不在树里面,返回的是pair<刚插入树里面对应结点的iterator,true>。
所以[]里面其实具有两个功能,一是将数据插入进去,二是返回对应key的value值的引用。
V& operator[](const K& key)
{
pair<iterator, bool> ret = insert(make_pair(key, V()));
return ret.iterator->second;
}
map<string, string>kv;
kv.insert(make_pair("apple", "苹果"));
kv.insert(make_pair("yellow", "黄色"));
kv.insert(make_pair("insert", "插入"));
//[ ]具有读取value的功能
//跟正常的[]类似,只不过map的[]读的是value值
cout << kv["apple"] << endl;
kv["sort"];//插入一个空value
kv["sort"] = "排序";
//修改
kv["man"] = "男人";
//插入+修改
【注意】
1. map中的的元素是键值对
2. map中的key是唯一的,并且不能修改
3. 默认按照小于的方式对key进行比较
4. map中的元素如果用迭代器去遍历,可以得到一个有序的序列
5. map的底层为平衡搜索树(红黑树),查找效率比较高 O ( l o g 2 N ) O(log_2 N) O(log2N)
6. 支持[]操作符,operator[]中实际进行插入查找。而multimap是不支持[],因为multimap一个key值可能对应多个value值,所以不知道返回哪一个了。
三.set/map应用
1.计算水果个数
void test3()
{
string arr[] = { "西瓜", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
//map的find查找特点:当找到要要的key值后,会返回对应key-value的迭代器
//如果没找到就会返回最后一个位置的后一个迭代器即end()
map<string, int> count;
for (auto e : arr)
{
auto ret = count.find(e);
if (ret == count.end())//说明树里没有,那就插入进去,并给1
{
count.insert(make_pair(e, 1));
}
else//说明找到了,那就将对应的数字++
{
ret->second++;
}
}
for (auto e : count)
{
cout << e.first << ":" << e.second << endl;
}
}
另一种计算方法
for (auto e : arr)
{
count[e]++;
//实现的原理:
//第一步先插入,第二步返回value值
//如果不存在就插入,并且value值就是匿名对象对应类型的缺省值,然后++就变成1
// 如果存在插入也没事,不会覆盖,返回value
}
2.两个序列的交集
思路
1.两个序列中可能存在多个相同的数,但最终结点只会有一个,所以需要先去重,这就需要使用set。使用完set后,序列就变得有序了。
2.两个有序序列如何找交集呢?
交集:两个序列同时从头开始遍历,较小的一方就++,相同的就是交集然后再同时++。
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.前K个高频单词
要求获取前k个出现最多的单词,不过这里还有个要求,当出现的次数相同时,就要按照字典序排序。
思路:
1.统计出现的次数我们可以利用map来统计。
2.再对根据出现的次数进行排序。选出前K个单词
3.不过这里的问题是当次数相同时,如何按照字典序再排序?
4.我们想map统计完后,单词的顺序是排序好的,每个单词的个数可能相同也可能不同。但如果当单词个数不相同时,对出现的个数排序就可以完成任务,因为没有出现相同的频率,但如果单词个数出现相同时,排序完后,如果它们的相对顺序没有被改变,那么也可以完成任务,因为相对顺序就是按照字典序排的,所以这个排序得要求是稳定的。
5.还有如何进行比较排序呢?我们需要使用仿函数来修改比较规则。根据次数进行比较,并且进行降序排序。
class Solution {
public:
//要求先按照频率由高到低排序也就是出现的次数,如何统计出现的次数呢?用map统计次数
struct Greater//仿函数重构比较方式,利用出现的次数进行比较,并且降序排,大的在前面,小的在后面
{
bool operator()(const pair<string,int>& kv1,const pair<string,int>& kv2)
{
return kv1.second>kv2.second;
}
};
vector<string> topKFrequent(vector<string>& words, int k) {
map<string,int> countmap;
for(auto &e:words)
{
countmap[e]++;
}
//已经统计完次数了接下来就要按照出现的频率来排序,不过map其实已经按照字典序的排序方法将数据排好
//只要要按照频率排序后稳定性不变,相对顺序不变就可以了,但sort稳定性不行
//注意sort无法对map排序,所以我们可以将map里的数据放进vector里
vector<pair<string,int>> v;
v(countmap.begin(),countmap.end());
stable_sort(v.begin(),v.end(),Greater());
//stable_sort排序比较稳定
vector<string> vs;
for(int i=0;i<k;i++)
{
vs.push_back(v[i].first);//选取前k个单词
}
return vs;
}
};
还有一种方法,可以直接使用sort,不用管稳定不稳定,那就是直接更改sort的比较排序规则,除了按照次数进行降序排序外,再添加上,当次数相同时,就按照字典序对单词进行比较。
class Solution {
public:
//要求先按照频率由高到低排序也就是出现的次数,如何统计出现的次数呢?用map统计次数
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(auto &e:words)
{
countmap[e]++;
}
//已经统计完次数了接下来就要按照出现的频率来排序,不过map其实已经按照字典序的排序方法将数据排好
vector<pair<string,int>> v;
v(countmap.begin(),countmap.end());
sort(v.begin(),v.end(),Greater());
//也可以利用仿函数的比较规则来使用sort排序,首先要按照频率比较,当频率相同时,按照字典序比较
vector<string> vs;
for(int i=0;i<k;i++)
{
vs.push_back(v[i].first);
}
return vs;
}
};