当前位置: 首页 > news >正文

set/map学习

我们要开始学习map和set的使用,虽然使用更加复杂,但是STL整体的设计,本身就具有很强的前瞻性和延续性,比如说迭代器等,我们顺着文档来看。这也是除了vector之外最重要的容器,当然还有unordered_map 和 unord_set,其实这几个也是C++里面用的最多的。

Set学习

模版参数

这里的set呢,它只有一个模版参数可就是T,然后还有一个比较器,默认是小于,它给一个比较器呢,就是为了,有些地方默认的operator <重载的比较不符合你的要求,假设你用的是指针,但是指针是按地址比较大小,当这个T是一个指针的时候,但是你不想按地址比较大小,那么原生的行为不符合怎么办,那你是不是就可以传仿函数呀,去控制里面的比较规则呀。在下面就是空间配置器,它是负责申请内存。
在这里插入图片描述

insert()

在这里插入图片描述
我们看到函数参数中有一个,value_type类型,但是不知道这个是什么类型。我们先写一个最简单来看看。
》set s;set 没有push() ,线性表、链表、队列等都是线性结构,才会有push()接口 。后面非线性结构的都是insert()接口了。我们随便插入一些数据s.insert(4);s.insert(5)…我们插入5个值。然后我们定义一个迭代器,set::iterator it = s.begin();这个set呢,它是一个双向迭代器,它底层用了特殊的算法迭代,是走的另一种非递归,后面也会讲,不用像我们在前面的二叉树OJ那里用栈模拟,但是它这里非递归有前提的,前提就是,它要有三叉链,它的逻辑底层是有parent成员变量的,反正我们先不管,后面我们才讨论底层。
》然后我们while循环遍历,while(it != s.end()),cout<< *it << " ";it++然后我们看到,走出来就是有序的!其实它不仅仅是有序的,它是在有序的情况下还加了一些东西,如果值已经有了,它还会不会插入?不会了!所以严格来说,它不是排序,而是一个排序➕去重!
》它这块支持迭代器,是不是有普通迭代器支持可读可写,还有const迭代器只读。然后它还有反向迭代器,就不演示了。
》它既然支持迭代器,那么就会支持范围for。其实范围for和我们迭代器在底层上是一样的,只是在上层看来有些区别。
》我们发现,const迭代器和普通迭代器都是不支持修改的 ,虽然一个叫iteratro,一个叫const iterator,但是底层都是用的一个const_iterator来实现的。
》另外重载的两个insert()函数用的很少。平时也很少用在某一个位置插入,这个接口价值很小。
》insert()不会有迭代器失效的问题,不像顺序表,挪动数据会影响相对关系,它的erase()是会有迭代器失效的问题。

void test_set1()
{set<int> s;s.insert(4);s.insert(5);s.insert(2);s.insert(1);s.insert(1);s.insert(3);s.insert(2);s.insert(1);// 排序 + 去重 set<int>::iterator it = s.begin();while (it != s.end()){//*it = 10;cout << *it << " ";++it;}cout << endl;for (auto e : s){cout << e << " ";}cout << endl;
}

empty()/size()/max_size()

erase()/find()—两个是要配合的

在这里插入图片描述
在这里插入图片描述

erase()函数主要有两种,一种是在某个位置删除,另外一种是删除某一个具体的值。第三种删除特定的区间,这个用的很少。
》这块要玩删除的话,就得配合find()函数。我们前面插入了很多值,我们随便set::iterator it = find(2)一个值,那么我们**怎么判断是找到还是没找到呢?**如果元素被找到了,是会返回value值对应的迭代器,查找失败就会返回end()迭代器。是不是就是if(pos != s.end())就是找到了呀。
》我们来对比一下,set自带的find()和算法里面的find()有什么区别呢? set::iterator it = find(20) 和 pos = find(s.begin(),s,end(),20),算法里面的find,能不能行呢?是行的。它们的区别很大,数据量小还好,数据量大就会差别很大。set的find()的效率是logN,而算法的查找效率是O(N),因为算法的是暴力查找。这个算法的find()查找不是给set用的,而是给像vector它们用的。

void test_set2()
{set<int> s;s.insert(4);s.insert(5);s.insert(2);s.insert(1);s.insert(1);s.insert(3);s.insert(2);s.insert(1);set<int>::iterator pos = s.find(20);  // O(logN)if (pos != s.end()){cout << "set.find找到了" << endl;}pos = find(s.begin(), s.end(), 2); // O(N)if (pos != s.end()){cout << "find找到了" << endl;}
}

》如果我们要erase了呢?,我想将我从键盘中输入的值erase()掉,通过find()查找我们输入的值再去erase(迭代器)。当erase()成功之后,对应的位置就不能访问了,已经是失效了。我们可以通过查找进行持续的删除。我们还可以直接传参数去删除,即erase(3);也就是你可以理解成,这个erase(3),相当于调用了find()➕erase(迭代器)。
》前面第一种是先是找到了再去删除,它们会有什么区别呢?第二种用法,如果没有找到直接去删除是不会报错的;而你第一种调用了find()没有找到,你还是去删除,是会报错的。
》size_type erase(value)是会返回一个size_type类型的数据,他为什么不用bool类型呢,是因为它和另外讲的东西契合起来。我们能够看到,删除成功是返回1,删除失败是会返回0,为什么是返回这个,而不是返回bool呢?因为待会儿我们还要学另外一个版本,另外一个版本是允许冗余的。

void test_set3()
{set<int> s;s.insert(4);s.insert(5);s.insert(2);s.insert(1);s.insert(1);s.insert(3);s.insert(2);s.insert(1);cout << s.erase(3) << endl;cout << s.erase(30) << endl;for (auto e : s){cout << e << " ";}cout << endl;set<int>::iterator pos = s.find(3);if (pos != s.end())s.erase(pos);for (auto e : s){cout << e << " ";}cout << endl;/*int x;while (cin >> x){set<int>::iterator pos = s.find(x);if (pos != s.end()){s.erase(pos);cout << "删除" << x << "成功" << endl;}else{cout << x << "不在set中" << endl;}for (auto e : s){cout << e << " ";}cout << endl;}*/if (s.count(5)){cout << "5在" << endl;}if (s.find(5) != s.end()){cout << "5在" << endl;}
}

swap()

swap()就跟我们以前讲的一样,比如说你要交换两个set的对象,用算法的swap()直接交换是深拷贝,是树的拷贝,代价很大,所以set自己有一个swap(),它们交换指针,代价小很多。

count()

在这里插入图片描述

count()函数这个设计是和一个版本对应起来的,就是你给我一个值,我看一下这个值在我set里面有几个,在这里的set而言,无非就是1个和0个。但是待会儿还有一个对称的版本。我们说了,有些场景可能需要的不是排序➕去重,而只是排序,那么它们就设计了multile版本,它是多样性的意思,它是允许冗余的,它们的接口设计都是保持的一致。
》count()函数有一个非常有意义的点就是,判断在不在,比用find()方便。比如说,我要判断一个值在还是不在,用count()是不是就很方便呀,比如说我要count(3),if(s.count(3))看一下这个3在不在,在的话是会返回1,不在的话,是会返回0。如果用find()的话就没那么方便了吗,要这样if(s.find(3) != s.end())是不是相对来说不太方便了。

lower_bound()函数

在这里插入图片描述
lower_bound()是干嘛呢?比如lower_bound(30)是帮你去找 >= 30的值的迭代器,但是它在这儿又有一点问题,lower_bound()和upper_bound()的返回值还有点不一样。我们试验一下。
》你看set::iterator lowIt = s.lower_bound(3);如果有3,它会返回3位置的迭代器,没有就返回 > 3的值,我们插入的值里面是有3的,所以返回的是3 的迭代器。如果是set::iterator lowIt = s.lower_bound(6);我们插入里面没有6,那它会返回什么呢?返回的是7的迭代器。
》它什么情况下比较有用么,比如说:要求删除 >= x的所有值,那么你就不用遍历去删除了,这里的lower_bound()函数就可以帮干这件事情,怎么删除呢?不要忘了,我们的erase()是支持迭代器区间的删除!那么erase()左区间可以给个lowIt变量,右区间可以给end(),相当于是左闭右开【),即erase(lowIt,s.end());

void test_set4()
{set<int> s;s.insert(4);s.insert(5);s.insert(1);s.insert(3);s.insert(2);s.insert(7);s.insert(9);// 返回>= val得位置迭代器  3返回3位置  6 返回7位置/*set<int>::iterator lowIt = s.lower_bound(3); 存在lowIt = s.lower_bound(6); 不存在*/for (auto e : s){cout << e << " ";}cout << endl;// 要求删除>=x的所有值int x;cin >> x;set<int>::iterator lowIt = s.lower_bound(x);s.erase(lowIt, s.end());for (auto e : s){cout << e << " ";}cout << endl;
}

upper_bound()

在这里插入图片描述
比如upper_bound(60),如果有60的话,它是不回返回60,而是返回 > 60的值,我们来验证一下。这里我们比如说是,set::iterator upIt = s.upper_bound(5),我们传一个5,然后再来一个,set::iterator upIt = s.upper_bound(6),我们传的是6,这两个区别就是一个值在我们插入的里面,一个是不存在。我们来看看结果,我传的是5,有5它返回5了吗?答案是:不会的!它返回的是7,我传的是6,它返回的是7。所以,这里的upper_bound(),它要找的不是 >=的值,它是返回 > x位置的迭代器
》所以upper_bound和lower_bound,一个是返回 >=,一个是返回 >。其实他们**两个可以配合起来并配合ease(),贯彻“左闭右开【)”。**比如说要删除 x <= [] <=y的区间,是不是就可以将这两个配合起来使用了呀。比如auto leftIt = s.lower_bound(x),auto rightIt = s.upper_bound(y),erase(letfIt,rightIt)所以它会删除【x,y】

void test_set5()
{set<int> s;s.insert(4);s.insert(5);s.insert(1);s.insert(3);s.insert(2);s.insert(7);s.insert(9);for (auto e : s){cout << e << " ";}cout << endl;// 返回>x位置的迭代器  -》 都是返回 7位置的迭代器//set<int>::iterator upIt = s.upper_bound(5);  // 存在//upIt = s.upper_bound(6); // 不存在// 删除x <=  <= y的区间 删除 [x,y]int x, y;cin >> x >> y;auto leftIt = s.lower_bound(x);  // [auto rightIt = s.upper_bound(y); // )s.erase(leftIt, rightIt);for (auto e : s){cout << e << " ";}cout << endl;
}

讲讲multiset版本

multiset它是允许键值冗余的,它的用法和set一样的,但就是要记一下它的名字,比如mutilset mS;它是排序不去重
》真正有区别的就是count()函数,比如说你要统计一个值s.count(1);set版本那边的count没有给bool类型的返回值,而是给int类型返回值,这其实是为保证接口的设计一致。其实set不需要count,是个bool就行了,但是为了和multiset保持一致,返回值类型也是int。
》同样的,set版本里面的erase()函数的返回值不是bool,而是size_type,也是为了和multiset里面的erase()设计保持一致,可以返回删除了几个对应的值。
》还有一个区别就是这里的find()函数,比如我这里有多个同样的值,那么我auto pos = s.find(3)会返回哪个3的迭代器呢?那么这里就可以做一件事情,它会返回中序的第一个,可以由while(pos != s.end())cout << *pos << " ";++pos验证。我们以后讲了搜索树,会知道,插入的时候,树是会旋转的,所以同样的值插入在左边还是右边都不影响。

void test_set6()
{multiset<int> s;s.insert(4);s.insert(5);s.insert(2);s.insert(1);s.insert(1);s.insert(3);s.insert(2);s.insert(1);s.insert(3);s.insert(3);s.insert(3);s.insert(3);// 排序 set<int>::iterator it = s.begin();while (it != s.end()){//*it = 10;cout << *it << " ";++it;}cout << endl;for (auto e : s){cout << e << " ";}cout << endl;cout << s.count(1) << endl;cout << s.erase(1) << endl;for (auto e : s){cout << e << " ";}cout << endl;// 多个x的话,find返回中序第一个xauto pos = s.find(3);while (pos != s.end()){cout << *pos << " ";++pos;}cout << endl;
}

看一下题目

题一:

两个数组的交集,找交集怎么找呢?是不是可以考虑这样,你也有,我也有的就是交集。
》最简单的方案就是,将其中一个数组放进set里面,我直接去你里面找,是不是太慢了。我们可以遍历将值inser()插入进set里面。然后继续遍历另一个数组,在我们的set里面找对应的值,那其中是不是就可以用count()函数而不必调用find()函数呀,然后,存在的话,就将值插入到我们另一个开辟的数组里面。但是这里有可能过不去,为什么呢?因为在我们开辟的vector来存交集的值,会有重复!因为交集是隐形的要求你去重的。
》那你的另一个数组是不是也用set来去重一下呀。但是这个算法不够高效,因为,假设你set里面有n个元素,那么每调用一次count()的时间复杂度是logN,那么总的时间复杂度是不是N*logN。
》还有一个优化算法,这个优化算法在很多找交集都能有效。找并集的话,只需要一个set就行了。现在来说一个交集/差集的优化算法,这个算法不一定要set,但是set用在这里刚刚好。
》我们inset()到set之后是不是就相当于有序了呀,有了排序就好说。如果你要求并集的话,只要放到一个set里面就行了,它是可以去重的。那么交集和差集怎么搞呢,有些地方需要同步,同步数据的话就要找交和差集。
》两个数组已经是有序了的,然后分别有一个指针先是指向各自的开始。交集的话:两个相比较的话,1.小的++;2.相等的就是交集,同时++;3.有一个结束就结束了。这个优化之后就变成了时间复杂度是2N了。
》这个算法除了可以找交集,还可以找差集。1.相比较,小的就是差集,小的++;2.相等的话,同时++;3.一个走到头了,另一个剩下的是不是也属于差集了。
》差集和交集在,服务器和你网盘需要同步的时候是有用到的。比如你有一个服务器文件夹和一个本地文件夹,你要将本地文件同步到服务器文件里面,相当于备份嘛,那是不是一方删除,另一方也会删除,你添加的,另一方是不是也要添加呀。
在这里插入图片描述
在这里插入图片描述

map

首先map数据结构里面呢,有一个Key和Value。它里面有一个valuse_type类型,它里面不是把数据分开存储的,不像我们前面讲的,自己写的搜索树里面,单独分开定一个key和value,而map,它是将key和value是放在一个结构里面的,这个结构是在C++里面被定义成pair<>类型,pair就是一对一对的意思,在map这里呢叫做键值对。

insert()

在这里插入图片描述
》我们先用map来存一个字典的例子,map的insert()插入的一个值是value_type类型的,value_type其实就是pair<>类型的数据。pair<>类型呢,有两个参数,第一个参数叫做first,第二个参数叫做second。
在这里插入图片描述
》如果我们想插入的话,map<string, string>dict;dict.insert(pair<string,string>(“sort”,“排序”));这是第一种,匿名对象的一种传参数,用起来比正常的要舒服很多。第二种,正常点是这样的,pair<string,string>kv(“insert”,“插入”);dict.insert(kv);还有第三种方式,用起来更爽:用到make_pair,make_pair就是制造一个pair对象,即dict.insert(make_pair(“left”,左边));
》前面第一、第二种都是借用的pair的构造函数来进行。第三种,make_pair是一个函数模版,它的优势是不用声明参数,让它自动推导。第四种,还能这么写,dict.insert({“right”,右边});虽然知道可以这么写,但是目前还是不太清楚为什么,这个涉及到C++11点类型转换,C++11再讲。
》map的遍历:map<string,string>::iterator it = dict.begin();我们也说过,如果你清楚的情况下可以用auto让它自己推导。while(it != dict.end())coyt << it << " ";++it;**发现不支持it**。有没有人想过,你直接分别单独用变量表示key和value不就好了嘛,为什么要封装一个pair?这个地方就能看出来,为什么要搞一个pair了。这个map的迭代器和链表的迭代器有些相似,它用自定义类型去封装,封装节点,只不过在map这里是一个树的节点,那么解引用是不是去调用operator呀,那C++是否支持一个函数返回两个值呢?这里it本质是it->operator(),它是不是要返回节点里面的数据呀,对于set还好,set里面只有一个key,但是map这里是一个key和value,那C++是否支持一次返回两个值呢?不支持是吧,但是你这里用到了模拟解引用的行为,那就要返回节点,如果你这里分别单独用变量表示key和value就没办法搞了,所以我把key和value给你打包成一个结构数据,这个结构数据就叫做pait<>类型,所以operator,返回的是节点数据,这个节点数据是一个pair<>类型。
》但是你这
类型pair<>支不支持流呢,即cout << it吗?答案是:不支持。有同学说,我们来支持一下就可以了,但是支持的话是不是就要改库里面的代码呀,那么我们换一种方法玩。你不是一个结构嘛,那么我们就是cout << (*it).first << “:” << (*it).second << endl;但是在这种地方,你的迭代器是不是指针的行为呀
,那你还用解引用*吗?不会,而是
用迭代器重载的->运算符来用*,即cout << it->first << “:” << it->second << endl;
》那这里的范围for,for(const auto& kv : dict),kv其实是返回的*解引用后的pair数据类型,用kv.first()和kv.second()来访问。

void test_map1()
{map<string, string> dict;// pair构造函数dict.insert(pair<string, string>("sort", "排序"));pair<string, string> kv("insert", "插入");dict.insert(kv);// make_pairauto ret1 = dict.insert(make_pair("left", "左边"));auto ret2 = dict.insert(make_pair("left", "剩余"));dict["operator"] = "重载"; // 插入+修改dict["left"] = "左边、剩余"; // 修改dict["erase"];  // 插入cout << dict["left"] << endl; // 查找// C++11 再讲//dict.insert({ "right", "右边" });// 遍历//map<string, string>::iterator it = dict.begin();auto it = dict.begin();while (it != dict.end()){//cout << *it << " "; // it->operator*()//cout << (*it).first << ":" << (*it).second << endl;cout << it->first << ":" << it->second << endl;++it;}cout << endl;for (const auto& kv : dict){cout << kv.first << ":" << kv.second << endl;}
}//mapped_type& operator[] (const key_type& k)
//{
//	pair<iterator,bool> ret = insert(make_pair(k, mapped_type()));
//
//	//return (*(ret.first)).second;
//	return ret.first->second;
//}

那你现在统计水果出现的次数是不是就很方便了呀,先构建一颗map树,map<string,int> countMap;for(aoto& str : arr)是不是就可以先调用map的find()函数,auto it = countMap.find(str);如果水果没有插入到我们的树里面,那么我们就构建一个pair<string,int>,即countMap.insert(make_pair(str,1));如果已经存在了,就it->second++;

void test_map2()
{string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };// 17:08继续/*map<string, int> countMap;for (auto& str : arr){map<string, int>::iterator it = countMap.find(str);if (it != countMap.end()){it->second++;}else{countMap.insert(make_pair(str, 1));}}*///map<string, int> countMap;//for (auto& str : arr)//{//	//pair<map<string, int>::iterator, bool> ret = countMap.insert(make_pair(str, 1));//	auto ret = countMap.insert(make_pair(str, 1));//	if (ret.second == false)//	{//		ret.first->second++;//	}//}map<string, int> countMap;for (auto& str : arr){countMap[str]++;}for (const auto& kv : countMap){cout << kv.first << ":" << kv.second << endl;}
}int main()
{//test_set6();test_map1();return 0;
}

其实map这里最重要的就是刚刚讲的,那么刚刚上面讲的呢还有优化的空间,这个优化的空间是要帮我们去搞后面的事情。

map<string, int> countMap;for (auto& str : arr){map<string, int>::iterator it = countMap.find(str);if (it != countMap.end()){it->second++;}else{countMap.insert(make_pair(str, 1));}}

这个代码其实是可以改进优化的。find()这里有这么一个问题,我去find()是不是一直去找,不在的话是不是我就相当于从根找到尾了呀,没有的话,我就得去插入,插入是不是又得去找一次,那是不是重复了呀,find()找一次,没找到后,insert又找了一次。其实不优化也无关痛痒,但是这里讲优化是为下面做铺垫,为下面的玩法做铺垫。
》所以这里就可以用insert()来做一件事情,正常的呢,insert()的返回值是一个pair<>类型的数据pair的first数据被设置成为一个iterator迭代器这个迭代器指向两种可能,要么指向新插入的元素(插入成功),要么指向与key相等的元素(插入不成功)。意味着是这样的,我insert()返回的pair<>类型的数据,first是一个迭代器,这个迭代器呢,一定是指向一个位置的,要么是你新插入元素的迭代器,要么是没有插入成功,因为反冗余的嘛,那么itreator就是已经有了的元素的迭代器。那么pair<>的second数据类型是一个bool,如果是新插入的,那就是true,如果已经有相等的了,就是flase。也就是说,它插入成功或者失败是会返回bool值的。我insert()返回的pair的迭代器不管你是插入成功还是失败都会返回一个元素的位置,那么就可以利用起来。
在这里插入图片描述
》就拿上面统计水果个树来说,如果水果已经存在map里面了,还会插入吗?不会。**这个待会儿对我们理解"[]"的使用非常重要 。**它insert()返回的pair类型的值非常有意义和我们待会儿要讲的“【】”有很大关系。我们插入atuo ret1 = dict.insert((“left”,左边))和auto ret2 = dict.insert((“left”,剩余)),按理来说第一次插入的left是成功的,ret1的second数据是true,first数据是新插入元素的迭代器;第二次插入left,ret2的second数据是flase,然后ret2的first数据和ret2一样的。
》我们就可以进行对上面代码改进,不需要再调用find()函数了,就调用insert()就行。我们不管有没有插入,都调用insert()函数,即pair<map<string,int>::iterator,bool> ret = countMap.insert(make_pair(str,1));写简单一点就是auto ret = countMap.insert(make_pair<str,1>);有两种情况,插入成功我用不用管,我不管,也就是第一次出现,那么返回的pair的second是一个true;插入失败,说明水果已经有了,那么insert()是不是同时充当了find()的作用,因为它把对应的元素的迭代器也给我了。 if(ret.second == false)那就是插入失败,已经有该水果了,所以ret.first->second++;

map<string, int> countMap;for (auto& str : arr){pair<map<string, int>::iterator, bool> ret = countMap.insert(make_pair(str, 1));auto ret = countMap.insert(make_pair(str, 1));if (ret.second == false){ret.first->second++;}}

operator[]

》其实我们统计也不会用这个方法,这个方法太憋屈了,我们**讲这个真正的目的是为了玩“【】”**这个重载运算符,而且C++里面的map非常喜欢用“【】”,如果说map的话,我们重点学习的就是这个“【】”重载运算符。我们统计次数怎么统计,我先写好:

map<string, int> countMap;for (auto& str : arr){countMap[str]++;}

此书成功统计出来,没有毛病,这个“【】”很绝。一句代码统计完次数!就用了operator []这么一个重载运算符。
在这里插入图片描述
》operator[]返回值是一个mapped_type&类型。我们想想,我们什么时候重载过operator[]了?我们在vector、string的时候重载了operator[],但是这里map支持随机访问吗?并不支持,目的也不是。
》其实这里map的operator[]已经改变了[]的意义了,它不是随机访问,它的参数也不是下标。它的参数是key,返回值是value的引用&。
》那么这里怎么做到一句话就统计了此书呢?insert()都没有了吗?我们来看看源码实现,
在这里插入图片描述
这个代码能把人看崩掉,我们简化一下代码,我们先提取出(this->insert(make_pair(K,mapped_type()))).first,这一串其实是谁呢?实际上就是调用了insert()嘛。
》insert的返回值是不是pair<map<>::iterator,bool>类型数据呀,即pair<map<>::iterator,bool> ret = insert(make_pair(K,mapped_type())),然后它取了ret的first数据,first是个什么呢,不就是指向节点的迭代器嘛。
》所以一开始的代码就可以理解成

pair<map<>::iterator,bool> ret = insert(make_pair(K,mapped_type()))
return (*(ret.first)).second;

》其实这个也绕了一点,其实等价于:

pair<map<>::iterator,bool> ret = insert(make_pair(K,mapped_type()))
return (ret.first)->second;

我们在“【】”里面给的是key值,有两种情况:1.Key在map对象中;2.Key不在map对象中。这里是精华,很多面试中都会考这里。
》第一种,Key在map对象中,我们要进行插入,make_pair(k,mapped_type())中的mapped_type()是个啥?我们先来看看mapped_type是谁?是不是T类型,就是Value的类型呀,也就是第二个模版参数的类型,第一个模版参数的类型是key的类型,K嘛。那么mapped_type()是不是用Value类型进行构造的匿名对象呀!
》也就是说,你来了一个Key,我不管你在不在,我先insert(0。假设第一种情况,Key是存在的,那么插入失败,那么insert()也就会插入失败,那么你pair<map<>::iterator,bool> ret = insert(make_pair(K,mapped_type()))中的ret的second数据bool类型就是flase!first数据就是Key所在节点的迭代器。然后return (*(ret.first)).second;可能早期没有支持“->”这个符号,所以我们现在支持,就这样写:ret.first->second------>这个是不是就是节点的Value值呀。并且不要忘了**,我operator[]的返回值,是Value的引用&!引用的作用是:1.减少拷贝;2.支持修改!**
》所以此时operato[]的作用有两层:1.查找K对应的Value;2.修改K对应的Value值。
》第一种情况,插入失败,insert()完全没有起作用,它反而充当的是查找的作用。它这里的operator[]返回值只用的是insert()返回值的first,即返回节点的迭代器。插入成功还是失败无所谓呀,为什么呢?如果Key在map对象中,肯定插入失败嘛;
》第二种情况,如果Key不在对象中,那么insert()是不是插入成功,operator[]的返回值是不是节点的迭代器中的Value值的引用&呀,这个值一开始是匿名构造的对象,上面有说(如果Value是stirng类型就是空串,如果是int类型就是0,如果是指针类型就是空nullptr)。我返回的Value值的引用,那也就意味着充当着 插入 和 修改两层作用,你可以修改,也可以不修改。
》我们再来分析一下,我们写的代码:

countMap[str]++;

水果要是第一次出现,那会先插入,插入的时候节点的个数是intt类型,它内部会匿名构造int类型对象就是0,但是operator[]会返回这个int的引用,我们在外面用了++,所以0会++;如果水果已经有了,它们operator[]不会插入成功,它会返回水果所在节点的Value值的引用,然后Value值++。
》我们再从另外一个角度,我们知道这个之后呢,有些人插入字典就不会像我们一开始那样写了,而是这样写:

auto ret1 = dict.insert(make_pair("left", "左边"));
auto ret2 = dict.insert(make_pair("left", "剩余"));
dict["operator"] = "重载"; // 插入+修改
dict["left"] = "左边、剩余"; // 查找 + 修改
dict["erase"];  // 插入
cout << dict["left"] << endl; // 查找

所以operator[]是一个多元化功能,map的其他函数参考set就行了。

multimap

它和map不同的是,它是允许冗余的,它是可以插入Key相同的键值对的,哪怕你的Value值相同也是可以的,其他的东西和map其实是一样的。

multimap<string,string> dict;
dict.insert(make_pair("left",“左边”));
dict.insert(make_pair("left",“剩余”))

》相比map ,multimap使用函数接口最大的区别是什么?multimap不支持operator[];为什么呢?如果对于map你给一个Key,我是不是要么有,要么没有,有的话,我就返回对应的Value,没有的话我好可以插入。但是mutiple有多个Key的时候,那么Key是不是面临一对多的关系呀,所以不支持operator []也是情理之中的。

题目:前K个高频单词

这个题目是一个统计次数➕topK的问题。我们不管三七二十一,先把各个单词出现的次数统计出来,当然可以用map来帮我们做KV映射,map<string, int> countMap;然后将words遍历一遍就可以得到各个单词出现的次数。
》接下来就是TopK的问题了。我们以前应该是讲过,如果建堆来解决,我们是不是要建小堆来解决是吧,但是我们不考虑用堆来解决,其实优先级也就是堆,因为优先级队列就是用堆来封装的。因为如果用堆的话,选出K个之后还需要进行排队。
》我们的map<string, int>它所插入的值,是按照string来进行排序的,但是我们要的是按int来进行排序。sort()能对map进行排序吗?答案是:不能!因为sort()要求的是随机迭代器,map显然不满足。那我们是不是要将map里面的值放到vector里面,然后就可以用sort()来进行排序了呀,当然vector存放的元素类型就是pair<string, int>了呀,即vrctor<pair<string, int>> vSort;
》当然也可以不放pair类型,而是vector<map<string, int>::iterator> v;这和我们放pair<string, int>有什么区别呢?这种方案行不行呢? 大家想想待会儿用sort()是不是必然要自己控制比较方式呀,如果要自己控制比较方式的话,你放pair<>我要写仿函数,我放迭代器你是不是也得控制比较方式呀。大家想一想,我在vector里面放迭代器iterator后,能不能访问到里面的Key和Value?是可以的。但是区别在哪里呢?如果你放的是pair<string, int>你待会儿得拷贝,把map里面的每一个数据都拷贝到pair里面,而我iterator迭代器就是一个指针,所以针对于拷贝的代价,我们还是选择迭代器iterator比较好。
》由于迭代器写出来很长,我们先typedef一下,typedef map<string, int>::iterator countIrer;然后我们就是遍历map,将每一个节点的iterator都插入到vector里面。
》接下来就是用到sort()函数进行排序了。 sort(v.begin(), v.end())这个时候能不能支持排序呢?是不能支持到,因为vector的迭代器v.begin()解引用之后是map节点的迭代器,那么它支持排序吗?不支持。不支持怎么办?我们是不是得写一个仿函数来控制排序使其支持呀。
》所以我们来写一个仿函数,struct IteCompare{}在其里面支持一个bool operator() (countIter ite1, countIter ite2)这里是支持迭代器进行比较。大家想清楚,因为vector里面的每一个数据是迭代器,我们将vector的迭代器传给sort,sort解引用*是map节点pair的迭代器,所以是迭代器比较;那么迭代器比较,要用仿函数来控制比较并使其能支持比较,所以我们写了bool operator() (countIter ite1, countIter ite2),然后函数体内写:return ite1->second > ite2->second;然后我们的仿函数就完成了。
》接下来就是将仿函数一并传给sort(),即sort(v.begin,v.end(),IterCompare);记住IterCompare是类型,我们应该传一个对象给sort(),所以是用到匿名对象IteComapre(),即sort(v.begin,v.end(),IterCompare());
》然后我们排序好了,我们还得取出前K个,是不是还得再来一个vector ret;然后将排完序后的vector循环K次呀,并且将值push_bacnk()进我们的ret里面。
》但是光光这样还是不能提交成功,因为我们的选出来的K个words单词顺序不对,因为如果单词出现的次数相同的话,还得进行ASICC码排序,将出现次数相同的单词进行排序。
》按道理,一开始我们的countMap是按string的大小(也就是按照Key去排序的)进行将每一个节点插入的,所以一开始就是将按string将words排序好了,然后紧接着我们再遍历将每一个节点的迭代器放入vector了呀,此时也是按照words的string来进行排序的,但是我们后面用了sort()之后,sort()是一个不稳定的排序,它会将我们原来的words正确顺序给打乱的。所以,你sort()完之后,选出来了K个,但是words的顺序你还得再排一次,除非你用一个稳定的排序。
》在sort()文档里面它有告诉你,它sort()是一个不稳定排序,它提供了一个stable_sort()是能够保证稳定性的。所以,我们将sort()换成stable_sort()试一下能不能通过,是能通过的。
》或者还有方法,你如果提前考虑到了这点的话,你可以在仿函数阶段也将words的顺序也控制好,即(ite1->second > ite2->second || ite1->second == ite2->second && ite1->first > ite2->first),所以我认为次数大的肯定大,如果次数相等,再比较words来进行排序。这样就可以调用sort()了。这不是改变sort()的稳定性,而是改变了比较的规则。但是会有一些性能的影响。
》我们还有别的方法,如果要排序,除了用sort(),是不是map本身也支持排序呀,只不过一开始是对string来排序的,我再定义一个map<int, string> sortMap;那么就是遍历一遍countMap,然后sortMap.insert(make_pair(kv.second,kv.string));就是将K和V调个位置,但是map会进行去重,所以我们还得用mutilmap才行,即mutilmap<int, string> sortMap;然后我们再去sortMap里面去取元素就行了,即for()循环K次,然后ret.push_back(it->second) ++it。但是取出来K个之后,它是按升序来的,显然不符合我们的要求,此时我们的map、mutilmap都是支持自己定义比较方式的,这不就体现出来了吗,所以,我们在定义sortMap的时候,还得传入另外一个参数,mutilmap<int, string, greater> sortMap;要传入一个greater来进行控制比较方式。
》最靠谱的还是sort或者srable_sort,因为如果用map的太依赖其底层的实现了。
在这里插入图片描述
在这里插入图片描述

相关文章:

set/map学习

我们要开始学习map和set的使用&#xff0c;虽然使用更加复杂&#xff0c;但是STL整体的设计&#xff0c;本身就具有很强的前瞻性和延续性&#xff0c;比如说迭代器等&#xff0c;我们顺着文档来看。这也是除了vector之外最重要的容器&#xff0c;当然还有unordered_map 和 unor…...

JavaScript Web APIs学习总结

以后声明变量我们有限使用哪一个&#xff1f; const 有了变量先给const&#xff0c;如果发现它后面是要被修改的&#xff0c;再改为let 为什么const声明的对象可以修改里面的属性&#xff1f; 因为对象是引用类型&#xff0c;里面存储的是地址&#xff0c;只要地址不变&…...

萤石摄像头RTSP流获取(黑屏解决)

前言 在获取萤石摄像头RTSP视频流时&#xff0c;视频流获取不成功&#xff0c;黑屏并且一直显示缓冲中。下面对获取过程中查阅的资料和解决方案做一下汇总。 打开RTSP 在萤石云视频APP中打开RTSP&#xff0c;【我的】-【工具】-【局域网设备预览】-【开始扫描】-【选择摄像头…...

ThreadLocal引发的内存泄漏分析

预备知识&#xff08;引用&#xff09; Object o new Object(); 这个o&#xff0c;我们可以称之为对象引用&#xff0c;而new Object()我们可以称之为在内存中产生了一个对象实例。 当写下 onull时&#xff0c;只是表示o不再指向堆中object的对象实例&#xff0c;不代表这个…...

银行数据治理:数据质量管理实践

现代商业银行日常经营活动中积累了大量数据&#xff0c;这些数据除了支持银行前台业务流程运转之外&#xff0c;越来越多地被用于决策支持领域&#xff0c;风险控制、产品定价、绩效考核等管理决策过程也都需要大量高质量数据支持。银行日常经营决策过程的背后&#xff0c;实质…...

2.7V至25V宽输入电压15A 峰值电流

HT7179是一款高功率异步升压转换器&#xff0c;集成 20mΩ功率开关管&#xff0c;为便携式系统提供高效的 小尺寸解决方案。 HT7179具有2.7V至25V宽输入电压范围&#xff0c;可为 采用单节或两节锂电池&#xff0c;或12V铅酸电池的应 用提供支持。该器件具备15A开关电流能力&a…...

Vue 父子组件应用指南:从基础到实战

文章目录 一、创建父组件二、创建子组件三、在父组件中使用子组件四、父子组件之间的通信1. 数据传递2. 事件传递 Vue.js 是一种流行的 JavaScript 框架&#xff0c;用于构建用户界面。其中&#xff0c;父子组件的概念是 Vue 开发中非常重要的一部分。本文将介绍如何使用 Vue 创…...

todotodo

todotodo...

创建autotool项目

GNU Autotools是linux系统一套自动化编译工具&#xff0c;生成的项目可移植&#xff0c;通过configure && make即可生成目标程序。GNU Autotools组件有&#xff1a;autoscan, aclocal, autoconf, automake,autoheader等。 不用管这些工具的原理&#xff0c;只要知道他们…...

计算机概念

计算机的体系结构 计算机俗称“电脑”computer(kəmˈpjuːtə(r))哈哈&#xff0c;本质上就是一台在各个领域被广泛使用的设备&#xff0c;主要由硬件和软件两大部分组成。 常见的硬件&#xff1a;CPU、内存、硬盘、显卡、主板、键盘、显示器、鼠标、... CPU - 中央处理…...

【数学建模系列】TOPSIS法的算法步骤及实战应用——MATLAB实现

文章目录 TOPSIS简介方法和原理数学定义数学语言描述现实案例 正负理想解定义实例 量纲 TOPSIS法的算法步骤1.用向量规范化的方法求得规范决策矩阵2.构成加权规范阵C(c~ij~)~m*n~3.确定正负理想解的距离4.计算各方案到正理想解与负理想解的距离5.计算各方案的综合评价指数6.排列…...

网络安全(黑客)工具

1.Nmap 它是网络管理员 必用的软件之一&#xff0c;以及用以评估网络系统安全。正如大多数被用于网络安全的工具&#xff0c;nmap 也是不少黑客及骇客&#xff08;又称脚本小子 &#xff09;爱用的工具 。系统管理员可以利用nmap来探测工作环境中未经批准使用的服务器&#xff…...

探究前后端数据交互方式

前端和后端在 Web 开发中扮演着不同的角色&#xff0c;两者需要进行数据的传递和交互。本篇文章将主要讨论前后端数据交互方式的不同类型和应用场景。 一、什么是前后端数据交互&#xff1f; 在 Web 开发中&#xff0c;前端负责用户界面的设计和交互&#xff0c;后端负责数据…...

Yolov5轻量化:CVPR2023|RIFormer:无需TokenMixer也能达成SOTA性能的极简ViT架构

1.RIFormer介绍 论文:https://arxiv.org/pdf/2304.05659.pdf 本文基于重参数机制提出了RepIdentityFormer方案以研究无Token Mixer的架构体系。紧接着,作者改进了学习架构以打破无Token Mixer架构的局限性并总结了优化策略。搭配上所提优化策略后,本文构建了一种极致简单且…...

Spring-Retry实现及原理

前言 重试&#xff0c;其实我们其实很多时候都需要的&#xff0c;为了保证容错性&#xff0c;可用性&#xff0c;一致性等。一般用来应对外部系统的一些不可预料的返回、异常等&#xff0c;特别是网络延迟&#xff0c;中断等情况。还有在现在流行的微服务治理框架中&#xff0…...

Java中的锁

为什么会有这些锁呢&#xff1f; 因为一种类型的锁很难应对线程操作同步资源的情况。 乐观锁和悲观锁 自旋锁和适应性自旋锁 无锁、偏向锁、轻量级锁和重量级锁 公平锁和非公平锁 可重入锁和非可重入锁 乐观锁和悲观锁 悲观锁认为当它操作数据的时候&#xff0c;必然用一…...

学习系列:5种常见的单例模式变体及其实现方式

单例模式是一种创建型设计模式&#xff0c;它保证一个类只有一个实例&#xff0c;并提供了一个全局访问点。在实际应用中&#xff0c;我们可能会遇到一些特殊情况&#xff0c;需要对单例模式进行一些变体&#xff0c;以满足不同的需求。下面介绍几种常见的单例模式变体。 1. 懒…...

三菱FX5U系列PLC之间进行简易PLC间链接功能的具体方法

三菱FX5U系列PLC之间进行简易PLC间链接功能的具体方法 功能介绍: 在最多8台FX5U或者FX3U PLC之间通过RS-485通信方式连接,进行软元件相互链接的功能。 接线注意事项: 根据链接模式和所使用的从站数量的不同,链接软元件的占用点数也有所变化。根据链接软元件的起始编号,对占…...

基于DBACAN的道路轨迹点聚类

目录 前言道路栅格化轨迹聚类参考资料 前言 很多针对道路轨迹的挖掘项目前期都需要对道路进行一段一段的分割成路段&#xff0c;然后对每一个路段来单独进行考察&#xff0c;如设定路段限速标识&#xff0c;超速概率等&#xff0c;如何对道路进行划分&#xff0c;其实是一个很…...

【项目】接入飞书平台

前言 项目有和飞书打通的需求&#xff0c;因为是第一次打通&#xff0c;摸索过程还是花了些时间的&#xff0c;现在相关笔记分享给大家。 步骤 1、熟悉开发文档 熟悉飞书的开发文档&#xff1a;开发文档 &#xff0c;找到你需要的接口&#xff0c;拿我为例&#xff0c;我需…...

c++11 标准模板(STL)(std::ios_base)(三)

定义于头文件 <ios> class ios_base; 类 ios_base 是作为所有 I/O 流类的基类工作的多用途类。它维护数种数据&#xff1a; 1) 状态信息&#xff1a;流状态标志&#xff1b; 2) 控制信息&#xff1a;控制输入和输出序列格式化和感染的本地环境的标志&#xff1b; 3)…...

在线协同办公小程序开发搭建开发环境

目录 介绍 开发环境说明 虚拟机 原因 VirtualBox虚拟机 VMware虚拟机v15 安装MySQL数据库 安装步骤 导入EMOS系统数据库 安装MongoDB数据库 启动Navicat&#xff0c;选择创建MongoDB连接 创建用户 搭建Redis数据库 配置Maven 安装IDEA插件 Lombok插件 …...

【编译、链接、装载六】汇编——目标文件

【编译和链接六】汇编——目标文件 一、目标文件_存储格式1、生成目标文件2、目标文件存储格式3、file查看文件格式 二、查看目标文件的内部结构——objdump三、代码段四、 数据段和只读数据段五、 ELF文件结构描述1、头文件2、段表2.1、重定位表2.2、字符串表2.3、查看重定位表…...

王道计算机考研408计算机组成原理汇总(下)

提示:真正的英雄是明白世界的残酷,也遭受了社会带给他的苦难,他依然能用心的说“我热爱这个世界,我愿竭尽所能去为我的世界而好好战斗 文章目录 前言4.1.1 指令格式4.1.2 扩展操作码指令格式4.2.1 指令寻址4.2.2 数据寻址4.2.3 偏移寻址4.2.4 堆栈寻址汇总前言4.3.1 高级语…...

偏向锁、轻量级锁、重量级锁、自旋锁、自适应自旋锁

1. 偏向锁 偏向锁就是在运行过程中&#xff0c;对象的锁偏向某个线程。即在开启偏向锁机制的情况下&#xff0c;某个线程获得锁&#xff0c;当该线程下次再想要获得锁时&#xff0c;不需要重新申请获得锁&#xff08;即忽略synchronized关键词&#xff09;&#xff0c;直接就可…...

Delta 一个新的 git diff 对比显示工具

目录 介绍git diff 介绍delta介绍 一、安装1.下载 Git2.下载 delta3.解压4.修改配置文件5. 修改主题6.其他配置和说明 二、对比命令1.在项目中 git diff 常用命令2.对比电脑上两个文件3.对比电脑上的两个文件夹 三、在Git 命令行中使用效果四、在idea 的Terminal命令行中使用效…...

C# 二进制序列化和反序列化示例

.NET框架提供了两种种串行化的方式&#xff1a; 1、是使用BinaryFormatter进行串行化&#xff1b; 2、使用XmlSerializer进行串行化。 第一种方式提供了一个简单的二进制数据流以及某些附加的类型信息&#xff0c;而第二种将数据流格式化为XML存储。可以使用[Serializable]属…...

【CSS】文字扫光 | 渐变光

码来 可调整角度与颜色值来改变效果 <p class"gf-gx-color">我是帅哥</p> <style>.gf-gx-color {background: -webkit-linear-gradient(135deg,red,red 25%,red 50%,#fff 55%,red 60%,red 80%,red 95%,red);-webkit-text-fill-color: transparen…...

Overhaul Distillation(ICCV 2019)原理与代码解析

paper&#xff1a;A Comprehensive Overhaul of Feature Distillation official implementation&#xff1a;GitHub - clovaai/overhaul-distillation: Official PyTorch implementation of "A Comprehensive Overhaul of Feature Distillation" (ICCV 2019) 本文的…...

<Linux开发>驱动开发 -之-内核定时器与中断

&#xff1c;Linux开发&#xff1e;驱动开发 -之-内核定时器与中断 交叉编译环境搭建&#xff1a; &#xff1c;Linux开发&#xff1e; linux开发工具-之-交叉编译环境搭建 uboot移植可参考以下&#xff1a; &#xff1c;Linux开发&#xff1e; -之-系统移植 uboot移植过程详…...

会做网站的公司/2345网址导航智能主板

工具 window10 django2.2.2 mysql5.6 django默认使用mysqlclient连接mysql&#xff0c;但是在window下直接安装mysqlclient不容易成功&#xff0c;可以使用pymysql 代替。 但使用如下方式报错&#xff1a; #settings.py import pymysql pymysql.intall_as_MySQLdb()django 3.…...

网站做乘法表/手机百度免费下载

在 Rss Bandit 和 SharpDevelop 之间的权衡 在 Rss Bandit 和 SharpDevelop 之间的权衡 &#xff08;心理变化过程&#xff0c;没有技术含量&#xff0c;只是胡诌&#xff0c;呵呵&#xff09; 距离上次的WebLog居然过了一个月有半&#xff0c;其实心理是一直希望可以经常写点…...

个人网站首页/网络培训机构

转自&#xff1a;https://blog.csdn.net/wwt18811707971/article/details/107551124 1. 概述 电源完整性&#xff1a; 如何保证电源分配系统&#xff08;Power Distribution Network—— PDN&#xff09;满足负载芯片对电源的要求&#xff0c;即为电源完整性。 解释一下&…...

郑州高端做网站汉狮/新浪博客

我们都知道&#xff0c;在谷歌浏览器里面的flash是默认关闭的&#xff0c;如果是以前的版本还好&#xff0c;以前的版本至少还可以自己设置开启关闭&#xff0c;但是现在的版本无论你设不设置都是默认关闭状态&#xff0c;这样的话你每次进入需要flash的网站都要手动开启flash&…...

中学生网站源码/媒体宣传推广方案

硬件工程师跟结构工程师交互的文件&#xff0c;就只有结构图了&#xff0c;也就是PCB板框&#xff0c;这类文件一般是由AutoCAD导出的DWG、DXF文件&#xff0c;当然&#xff0c;也有只给你3D图的&#xff08;如SolidWorks、Pro-E等&#xff09;&#xff0c;让你自己导。 这里以…...

东莞网站制作培训/管理人员课程培训

背 景MySQL高可用方案有很多种&#xff0c;常见的有&#xff1a;keepalived、MHA、Galera、MGR、Orchestrator、replication-manager等。本系列将介绍在GitHub被使用的Orchestrator方案。本篇文章先介绍最基础的单节点模式的安装。环 境orchestrator机器&#xff1a;10.10.30.1…...