目录
一. set
set的模板参数列表
T: set中存放元素的类型,实际在底层存储的键值对。Compare:set中元素默认按照小于来比较
set接口的使用
ps:用法其实与之前的容器差不多,并且底层是二叉搜索树。
#include <iostream> #include <set> #include <map> using namespace std; void test_set() { set<int> s; s.insert(2); s.insert(3); s.insert(1); s.insert(5); s.insert(8); set<int>::iterator it = s.begin(); while (it != s.end()) { cout << *it << " "; it++; } cout << endl; } int main() { test_set(); return 0; }
迭代器所用的遍历方式为中序遍历~
ps:set容器没有push,只有insert插入,而且其中的key是无法修改的,因为随意修改会影响二叉搜索树的结构~ set与二叉搜索树一样,具有排序去重的功能~
set<int> s; s.insert(2); s.insert(3); s.insert(1); s.insert(1); s.insert(1); s.insert(5); s.insert(8); set<int>::iterator it = s.find(2); if (it != s.end()) { cout << "find success" << endl; } else { cout << "find failed" << endl; }
若找不到会返回一个开区间的位置~
一般我们erase不需要依靠find去寻找迭代器,用key值寻找然后删除即可~
set<int> s; s.insert(2); s.insert(3); s.insert(1); s.insert(1); s.insert(1); s.insert(5); s.insert(8); set<int>::iterator it = s.find(2); s.erase(it); for (auto k : s) { cout << k << " "; } cout << endl; s.erase(8); for (auto k : s) { cout << k << " "; } cout << endl;
ps:如果我们用迭代器去删除,若该key值不存在编译器会报错~而直接通过key去删除,那么存在即删除,不存在也无所谓~
set<int> s; s.insert(2); s.insert(3); s.insert(1); s.insert(1); s.insert(1); s.insert(5); s.insert(8); if (s.count(5)) { cout << "在" << endl; } else { cout << "不在" << endl; }
count函数可以对当前的key进行计数~ 不过这在去重的set中并无多大作用~
lower_bound函数:让迭代器指向大于等于当前值的位置
upper_bound函数:让迭代器指向大于当前值的位置
我们可以利用这两个函数的特性来实现一个左闭右开的迭代器区间~
set<int> s; s.insert(2); s.insert(3); s.insert(1); s.insert(1); s.insert(1); s.insert(5); s.insert(8); auto it1 = s.lower_bound(2); auto it2 = s.upper_bound(5); s.erase(it1, it2);//删除范围[2,5]的所有数 for (auto k : s) { cout << k << " "; } cout << endl;
set<int> s; s.insert(2); s.insert(3); s.insert(1); s.insert(1); s.insert(1); s.insert(5); s.insert(8); auto it1 = s.lower_bound(2); auto it2 = s.upper_bound(5); while (it1 != it2)//搜索范围[2,5]的所有数 { cout << *it1 << " "; it1++; } cout << endl;
这里就挺适合用count函数的~
二. map
map的模板参数列表
key: 键值对中key的类型T:键值对中value的类型Compare: 比较器的类型,map中的元素是按照key来比较的,缺省情况下按照小于来比较,一般情况下(内置类型元素)该参数不需要传递,如果无法比较时(自定义类型),需要用户自己显式传递比较规则(一般情况下按照函数指针或者仿函数来传递)
map接口的使用
map其实也就是我们前面所学的二叉搜索树的kv模型~
在map中,键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型value_type绑定在一起,为其取别名称为pair:![]()
void map_set() { map<string, string> dict; dict.insert(pair<string, string>("left", "左边")); pair<string, string> kv("right", "右边"); dict.insert(kv); dict.insert(make_pair("hello", "你好")); }
由于pair也有模板所以传参的时候也挺麻烦,我们一般用第三种使用make_pair函数自动推导类型~
map<string, string> dict; dict.insert(pair<string, string>("left", "左边")); pair<string, string> kv("right", "右边"); dict.insert(kv); dict.insert(make_pair("hello", "你好")); auto it = dict.begin(); while (it != dict.end()) { cout << (*it).first << " " << (*it).second << " "; cout << it->first << " " << it->second << " "; it++; cout << endl; } cout << endl;
由于我们想要访问的对象存在pair结构体里,所以用it访问的时候需要特殊处理一下
map<string, string> dict; dict.insert(pair<string, string>("left", "左边")); pair<string, string> kv("right", "右边"); dict.insert(kv); dict.insert(make_pair("hello", "你好")); auto it = dict.begin(); while (it != dict.end()) { cout << (*it).first << " " << (*it).second << " "; cout << it->first << " " << it->second << " "; it++; cout << endl; } cout << endl;
我们在用for+auto遍历的时候记得加上引用,因为我们kv获取的是dict里面的结构体,为了避免太多内容的拷贝所以建议用引用~
map<string, string> dict; dict.insert(pair<string, string>("left", "左边")); pair<string, string> kv("right", "右边"); dict.insert(kv); dict.insert(make_pair("hello", "你好")); for (auto& kv : dict) { cout << kv.first << " " << kv.second << endl; }
demo测试:
string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "西瓜", "香蕉", "草莓" }; map<string, int> countMap; for (auto& e : arr) { map<string, int>::iterator it = countMap.find(e); if (it != countMap.end()) { it->second++; } else { countMap.insert(make_pair(e, 1)); } } for (auto& kv : countMap) { cout << kv.first << " " << kv.second << endl; }
我们对arr数组进行遍历,若在countMap中找不到说明是第一次遇见,插入即可。若在countMap中找到说明我们只需要增加其出现的次数即可~
然而写这么一大堆都不如一句话有用~
我们来尝试解析一下这个【】运算符到底有多大的魔力吧~
运算符【】底层是用insert实现的,那么我们再转到Insert的视角~
我们会发现insert的返回值也有一个pair,但这和类模板中的pair可不一样~
在insert中若插入的key在树中已经存在,那么迭代器指针就会指向与key值相同的位置,然后bool为false。若插入的key未在树中,那么迭代器指针就会指向新插入的key值,然后bool为true~
所以this指针其实是指向的insert返回值中的pair,然后.first是去访问pair里面的iterator,里面的iterator又是指向kv的pair,对iterator指向解引用后就可以用.second去访问到kv中pair里面的value~
string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "西瓜", "香蕉", "草莓" }; map<string, int> countMap; for (auto& e : arr) { pair<map<string, int>::iterator, bool> ret; ret = countMap.insert(make_pair(e, 1)); if (ret.second == false) { ret.first->second++; } } for (auto& kv : countMap) { cout << kv.first << " " << kv.second << endl; }
如果在插入时发现已经存在,那么就在kv里面的pair中对value进行++
所以实际上的运算符[ ]应该是类似上面这种,其中value通过默认构造赋值~
若第一次遇见,那么key对应的value会由0(默认构造) ++变为1 ,若已在树中也没事,照样对其++~
由于operator【】底层是由Insert辅助实现的,我们可以拿来对map对象作修改,插入,查找等操作~
有点无法理解,[]返回的不是对象value吗?怎么还能搞这么多操作????
是返回的value,但是本质是调用了Insert函数插入,所以得先按照插入的逻辑走,至于value那得看运算符++。
最后再来给大家补充一些关于迭代器具体指向的问题~
ps:map也是有multimap,对key具有冗余性~
三. OJ题
138. 随机链表的复制
题目解析:
本道题的难点就是我们可以拷贝出新的节点,但是却不能拷贝出rondom的指向,这个得由我们直接去解决,而当我们学习了map的特性之后我们利用kv模型来帮助我们解决问题~
别小看了我们这里使用的kv模型,到了下一步就有大用途了~
到下一步random链接时就不用慢慢去找所要链接的节点,因为都被map存储起来并且原节点与拷贝节点还形成了映射关系~
代码:
class Solution {
public:
Node* copyRandomList(Node* head) {
map<Node*, Node*> copymap;
Node* cur = head;
Node* copyhead = nullptr;
Node* copytail = nullptr;
//拷贝节点
while (cur)
{
if (copytail == nullptr)
{
copyhead = copytail = new Node(cur->val);
}
else
{
copytail->next = new Node(cur->val);
copytail = copytail->next;
}
//关键,建立原节点与拷贝节点的联系
copymap[cur] = copytail;
cur = cur->next;
}
Node* copycur = copyhead;
cur = head;
//拷贝random链接
while (cur)
{
if (cur->random == nullptr)
{
copycur->random = nullptr;
}
else
{
copycur->random = copymap[cur->random];
}
cur = cur->next;
copycur = copycur->next;
}
return copyhead;
}
};
349. 两个数组的交集
题目解析:
这里我们也可以用set去解决会很轻松~本质就是利用ser去重+排序~
法一:
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
set<int> s1(nums1.begin(),nums1.end());
set<int> s2(nums2.begin(),nums2.end());
vector<int> v;
for(auto e:s2)
{
if(s1.count(e))
{
v.push_back(e);
}
}
return v;
}
};
如果在s1中找到e,那就说明该数为交集之一。
法二:
这个方法能够解决大部分交差集问题~
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
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)
{
it2++;
}
else if(*it1<*it2)
{
it1++;
}
else
{
v.push_back(*it1);
it1++;
it2++;
}
}
return v;
}
};
692. 前K个高频单词
题目解析:
经典kv模型,不过这道题是要求我们对value进行排序,而我们map一般是对key作排序的~
法一:
首先在第一步中map插入数据后已经变成了对字典排序,要想改变排序规则我们只能用sort再排序一次,所以我们再拿一个vector容器对其进行存储~在利用仿函数时需要注意我们让频次进行比较进而达到频次降序的效果~然后再利用匿名对象作参数即可~
第二步中用的是stable——sort可以保持稳定性(即保留之前字典排序中的相对顺序),普通的sort是不稳定的,排序后会出现单词频次降序但字典排序错乱的问题~
class Solution {
public:
struct KvComp
{
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> dict;
//先进行数据的插入与字典排序
for(auto&e :words)
{
dict[e]++;
}
vector<pair<string,int>> v(dict.begin(),dict.end());
//再重新排序:根据出现频率
stable_sort(v.begin(),v.end(),KvComp());
vector<string> vv;
auto it = v.begin();
while(k--)
{
vv.push_back(it->first);
it++;
}
return vv;
}
};
法二:
我们不使用stable_sort进行稳定排序,而是直接在仿函数里面再修改其排序规则~
当频次一样的时候我们就进行字典排序(<升序)
class Solution {
public:
struct KvComp
{
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> dict;
//先进行数据的插入与字典排序
for(auto&e :words)
{
dict[e]++;
}
vector<pair<string,int>> v(dict.begin(),dict.end());
//再重新排序:根据出现频率
sort(v.begin(),v.end(),KvComp());
vector<string> vv;
auto it = v.begin();
while(k--)
{
vv.push_back(it->first);
it++;
}
return vv;
}
};