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

map和set介绍

1.set的基本使用    

    下面来看map(key/val模型)和set(key模型),它们主要可用来做搜索,要用它们包map和set头文件就可以了。先来说说set:

它的模板参数比我们以前学的容器多了一个compare,因为它的底层是红黑树,红黑树是一种搜索树,搜索树要把key比较大小,所以这是一个仿函数支持key比较大小。这块的容器叫关联式容器,以前学的容器喜欢叫做序列式容器;现在叫关联式是因为数据和数据之间有强烈的关联,插入数据不能随意的插入,也不能想放哪里就放哪里。下面来看看它的使用,成员函数中看看构造:

一个是全缺省的(1),一个是用迭代器区间初始化(2)。它的拷贝构造是树的拷贝,析构是树的析构(其余接口结合文档简单看一下)。下面写一点程序来感受一下:

它的迭代器走的是有序,说明迭代器默认走的是中序遍历。再插入一些重复的数:

说明它是排序+去重,去重原理是一个值已经有了我们就不插入了。现在遍历set还有其它方式吗?可以直接使用范围for:

再看看erase:

erase这支持迭代器位置、值、迭代器区间,比如要删某个值:

上面演示的两种删除表面其实没有差异,第二种其实使用第一种实现的,先找再删;区别是第一种删的位置不在就崩了,第二种删的值不在也没事。除非这样:

那库里面的find和算法提供的find有没有区别?差异还是很大的,库里的find最多查高度次O(logN),大了往右边查,小了往左边查;算法提供的是暴力查O(N),都要遍历。

    前面说了set,set其实就是key的搜索模型(在不在的问题)。set这里除了find还有一个count:

find找到了会返回那个元素所在的迭代器,没找到会返回end,find搜在不在这样用:

下面看看count:

count是传一个值,返回这个值出现了几次,对于这而言它的值是唯一的。所以容器中包含访问的元素返回1,不包含返回0,所以判断在不在可以这样写:

(既然有了find为啥还要有count呢?这个问题暂时放一下)还有lower_bound、upper_bound、equal_range,它可以帮助我们去找一些边界,可看看样例:

lower_bound找的是比给的值大于等于的,upper_bound找的是比给的值大于等于的,因为给区间用的左闭右开。还有个样例:

它的返回值里有first和second,左边界给大于等于30的,右边界给比30大的。以上接口真正意义上是为multiset准备的,这是允许建值冗余的set,以上三个接口multiset里是有的,set有是为了保持两边接口的一致性,下面演示一下,它的头文件还是在set里面,set是排序+去重,multiset是纯排序;功能上和set一样:

lower_bound upper_bound equal_range count对它很有意义,假设现在想删除所有的7:

那么这里有多个7的时候调用find找的是哪一个7呢?中序的第一个。

2.map的基本使用

    下面来看一下map:

它比起set而言要存两个值,一个Key,一个T(val),还有个1仿函数用于比较(只有key参与比较)。

map在这插入数据时插入的是value_type类型,看一下文档:

从文档看到value_type是一个pair,这给的是const key,猜测是key不允许被修改,val可以;set也是不能修改的,可以若可以修改搜索树就被破坏了,可以看到set的迭代器和const迭代器都是const迭代器:

map内部用了pair结构存数据,它是个类模板结构:

它有两个核心成员分别是first和second。也就是一般map结点里面存数据是用一个pair存的,first是key,second是val,而不是像之前给个key和val存结点。以后进行insert插入的是value_type,value_type是一个pair,pair的第一个值是const_key,第二个值是val。下面简单写一个字典出来感受一下(string提供了const char*到string的隐式构造函数):

上面是定义了一个对象或插入了一个匿名对象,但是有一些繁琐。pair这还提供了一个函数叫make_pair,这是它的实现:

它是函数模板,传两个值自动构造一个pair返回,所以可以这样写:

还可以这样写(暂时了解):

C++98说的是单参数构造支持隐式类型转化,C++11支持了多参数构造函数隐式类型转化,这里121行等价于118行去调pair构造。插入完后用迭代器遍历(make_pair可自动推参数,会调构造,建议用,它会被定义为内联,无消耗):

发现遍历不通过,报错说pair不支持流插入,说明pair类在库中没实现它的流插入和流提取。结合想一想,我们以前写K-V结构的搜索树时结点里弄个key和value就行了,map这里为啥还要放个pair?因为设计迭代器最终要重载一个operator*,迭代器里面包含一个结点指针,如果是key-val不能很好的返回,不支持返回两个值;想返回多个值要返回一个结构,所以这里这样设计。因此*it后是一个pair,获取值直接这样:

还可以用箭头,迭代器里箭头的设计大概是这样:

调用时it->去调operator->返回pair*,如果不是编译器优化应该是it->->first:

当然如果熟练后类型那里可直接用auto:

(如果set、list里面也是pair,*it后都是结构)也可以用范围for(加引用,因为是1结构可以减少拷贝):

    结合文档在过一下map:

ersae可以查一个位置删,也可以给一个key来删除(像插入的时候key相同val不同不插入,不覆盖,说明插入过程只比较key,val是否相同无所谓),迭代器区间也和key有关。find也类似:

给个key返回迭代器,没有找到返回end。下一部分来看看方括号:

首先来看统计次数的场景:

先用最朴素的方法统计次数:首先定义一个map,key是string,val是int(key并不是任意给任意类型都行,需要支持可以比较大小,因为大了往右走,小了往左走;但如果一定不支持可以给仿函数来控制)。现在是来了个水果看水果有没有出现,第一次出现插入水果,出现过了对second++:

还有种简单的写法,只用一行代码可统计:

map的方括号不再是常规的方括号,结合来看:

map第一个模板参数传给了pair的第一个模板参数,第二个模板参数给了pair的第二个。以前[]是返回第i个数据的引用,这里是key,返回对应val的引用。比如苹果已经出现了,再出现返回val++可以理解,那这里第一次怎么处理?

调用方括号等价于调用上图,函数类似是这样写的。可以发现[]是通过insert实现的,所以再来研究insert:

以前觉得insert就是返回bool值,实际上返回了pair,这里pair的first是个迭代器,second是个bool值。可以猜测bool值那里是插入成功为true,失败是false。下面有段话:

说了insert返回一个pair,pair的first被设置成迭代器,这个迭代器要么指向新插入的元素,或者和key已经相等的元素(也就是key已经在树里面,返回pair<树里面key所在的结点的iterator,flase>;key不在树里面,返回pair<新插入key所在结点的iterator,true>),这样也理解了insert除了插入作用还有查找价值。如果自己尝试模拟一下:首先insert返回值是个pair(一个是迭代器,一个是bool),insert要insert一个pair,key已经有了,没有val,那给什么呢?不能直接给0,val的类型不一定都能用0处理,所以默认用val的匿名对象(创建对象调构造时整型是0,指针是nullptr,类就调默认构造)。不管那个情况,取到这个结点的迭代器然后取里面的second(取到val),是新的返回新val,旧的返回树里的:

理解上述再来梳理一下:

第一次出现去insert,key是水果,val匿名对象是int值为0,成功后返回val++。再出现时插入失败,返回已有的val再++。再用字典感受一下[]的巧妙:

[]里面去调用insert,insert里的参数是pair,key是map,val是string的匿名对象;没有map返回新结点的迭代器,进而返回val,然后调赋值进而有了map对应的val。这样可以有对应的这些功能:

 3.题目练习

下面尝试用这里改几个题,比如括号匹配问题,以前判断可以这样改:

再比如复杂链表复制问题:

    还有一个multimap:

它是允许key值重复的,它的接口里没有方括号,因为一个key可能有多个val,不知道返回哪一个。下面看一个题:

349. 两个数组的交集 - 力扣(LeetCode)https://leetcode.cn/problems/intersection-of-two-arrays/首先想到的是能不能把nums1放进set里,然后依次遍历nums2看在不在set里,通过这样找交集。这样是不可以的,比如示例一中:

nums1放进set里会去重,这样遍历nums2时会找到两个2,不符合题目的唯一,除非最后再去一次重。那么就可以把nums1和nums2都放set里先去重,然后遍历其中一个set看在不在另一个set里,在的就是交集。这里还有个新方法,假设有两组数有重复值,最终放进set后中序遍历是这样的:

这样我们就可以找到它的交集、差集、并集。并集很简单,就是把两数组合并后去重。下面看看怎么找交集,两个数组各自从头开始遍历,依次比较:1.小的数++(因为已经有序了,1比3小,3后面不可能有和1一样的,所以小的++) 2.相等的就是交集,保存后同时++ 3.有一个走完就结束了。下面再看看怎么找差集,还是依次比较:1.小的就是差集,小的++ 2.相等时同时++ 3.一个走完就结束了。下面来回到这道题实现:

下面再看一个题目:

692. 前K个高频单词 - 力扣(LeetCode)https://leetcode.cn/problems/top-k-frequent-words/description/题目中说统计次数可以用map解决,前k个用排序也能完成。频率相同的还要用字典序排序,相当于两次排序。我们可以先统计一下次数:

下一步要找前k个,那是不是map的前k个?不是,map也排序,只不过按key排序,这里的key是string,倒是符合字典序。但题目要求按照频率的高低来排序,频率一样才按字典序排。排序这里有两种方法:1:直接用sort,但是不能用sort排map。因为map是双向迭代器,sort要求传的是随机迭代器,那不支持怎么办?把数据导入了vector里(用迭代器区间初始化),pair也重载了比较,它是first和second一起比,有一个小的就小:

但不想用这样的方法比,所以上个仿函数,给个大于排降序,按second比:

排好后取前k个:

上述提交后有报错,从报错例子中看到我们找的前k个都是对的,但顺序有问题。这说明sort不是一个稳定的排序,因为遍历countMap放kvVec中时已经按字典序排过了,再次排序时如果排序稳定频率一样的仍然会保持字典序。算法库中有个stable_sort,它是稳定的排序:

那如果非要用sort可以解决问题吗?可以控制仿函数比较规则,频率大的在前,频率一样的看字典序:

除了sort排序map也能再排序,再次map<int, string> sortMap,但map会把频率相同的去重了,所以用multimap(插入顺序走可以认为时稳定的)。默认排完是升序,可以控制一下map的第三个参数,最后取前k个:

http://www.dtcms.com/a/557820.html

相关文章:

  • 做网站总费用广告公司业务员小刘与客户马经理
  • C++ 面向对象三大特性之一——继承
  • seo 网站描述长度统计wordpress访问量
  • 校园网站建设的系统分析东莞网站的建设
  • 网站上传完成后要怎么做wordpress手机中文版
  • C#数据级联操作的法宝DataRelation
  • 摄影网站在线建设wordpress 文章编辑框插件
  • 一般的网站是由什么语言做的wordpress挂黑页
  • Springboot微信小程序在线考试系统w47h61gy(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • 3.1.1.Java基础知识
  • 2025年江西省职业院校技能大赛高职组“区块链技术应用”任务书(5卷)
  • docker安装mongo
  • Langgraph研究
  • 企业网站都没的百度快照咋办单位网站建设与管理
  • 【分布式缓存】Redis持久化和集群部署攻略
  • 下载 | Win11 24H2 正式版更新!(ISO映像、多合一版本、26100.7019、Windows 11)
  • 第五章Langchain4j之基于内存和redis实现聊天持久化
  • 微信如何建立网站如何制作营销网站模板下载
  • 做网站图片多少钱推广普通话手抄报一等奖
  • android面试题2
  • AI学习日记——Transformer的架构:编码器与解码器
  • 如何推广自己网站的关键词网络营销方案例文
  • 网站文章收录慢微信小程序制作费用
  • Nginx第三方模块集成:丰富功能实战
  • ms-swift框架微调qwen3-0.6b模型
  • 企业网站架构德阳建设局网站
  • 电子电力技术的准谐振电路和LLC电路相关习题学习记录分享
  • 陕西省档案馆建设网站淘宝客建网站怎么做
  • 2025年江西省职业院校技能大赛高职组“区块链技术应用”任务书(4卷)
  • 大型电商网站开发成本wordpress远程媒体库