map与set容器初识:初步运用map与set

news2024/12/24 2:21:51

前言:

本文主要讲解的时对于map与set容器的初步使用,希望大家对map与set容器不熟悉的看了之后可以快速运用set与map到日常中来。(本文适合对vector等基础容器有一定基础的同学)

一、set与map容器常见接口

迭代器接口与以往的所有容器一样,同样的构造方法,同样的名称。

empty判断容器是否为空,size返回当前容器的大小,max_size返回容器的最大大小是多少,我们常用的是前两个接口,max_size一般情况下不会接触。 

值得一提是map容器的operator[],这个函数在调用时服用了insert的功能,当当前key值的元素不存在时,会插入一个当前键值元素,并返回新插入的元素地址。如果存在该key值,就返回该key值的地址。

二者都用insert函数来插入,在学了右值引用后,也可以用emplace来进行插入工作(操作与insert一样)。

之后的clear清空容器,swap交换两个容器内容,erase删除某个元素的操作都与其他容器差不多,包括find查找,count判断有无。要注意的是二者的范围查找接口,

对于二者的lower_bound(key); 都会返回第一个不小于 key 的迭代器。upper_bound(key); 都会返回第一个大于 key 的迭代器。

其实,在日常刷题做题中,我们的常用操作就是新建一个set或者mao容器,然后根据题目规则存储信息(set太棒用于去重排序,map用来记录两种数据之间的关联)在使用map时,我们也不会特地使用insert来进行插入,一般会直接在容器名后面[]键值,自动进行插入或者修改操作。

多说没用,实践才是最好的老师,接下来就让我带领大家做几道题应用一下map与set吧。

二 、实战运用

1、随机链表的复制

(题目链接:https://leetcode.cn/problems/copy-list-with-random-pointer/description/)

在没有学习map与set之前,我们是怎么解决这道题的呢?

对于这道题来讲,难点在于怎么深拷贝random 所指向的节点因为我们没有与之对应的映射关系。

以例1为例,当我们拷贝了一个新链表之后,新的13节点的random指针所指向的 7,我们是没有存储的信息的,也就是找不到新链表上的7的地址。为了解决这个问题,我们将新链表插入在老链表之间,比如老1,13之间夹着一个新7.通过让next指向新拷贝的结点,让二者产生联系。在分开链表之间,只需要通过pcur->next->random=pcur->random->next就能得到解决。

class Solution 
{
public:
    Node* copyRandomList(Node* head) 
    {
        if (head == nullptr)
        {
            return nullptr;
        }

        Node* cur = head;
        while (cur)//先复制节点并连接点原本链表中
        {
            Node* pcur = new Node(cur->val);
            pcur->next = cur->next;
            cur->next = pcur;
            cur = pcur->next;
        }

        
        cur = head;
        while (cur != nullptr)
        {
            if (cur->random != nullptr)
            {
                cur->next->random = cur->random->next;
            }
            else
            {
                cur->next->random = nullptr;
            }
            cur = cur->next->next;
        }

        Node* phead = head->next, * prew = head;
        cur = phead;

        while (cur)
        {
            prew->next = cur->next;
            prew = prew->next;
            if (prew == nullptr)
            {
                cur->next = nullptr;
                cur = cur->next;
            }
            else
            {
                cur->next = prew->next;
                cur = cur->next;
            }
        }
        return phead;
    }
};

但是在有了map之后,就不用这么麻烦。只需要新建一个map,key为老节点,val为新的对应节点,就可以轻松完成代码。

class Solution 
{
public:
    Node* copyRandomList(Node* head)
     {
        map<Node*,Node*>Map;
        Node*cur=head,*copyhead=nullptr,*copytail=nullptr;
        while(cur)//cur遍历原链表进行拷贝并且把原链表与拷贝链表一一插入进map
        {
            if(copytail==nullptr)
            {
                copyhead=copytail=new Node(cur->val);
            }
            else
            {
                copytail->next=new Node(cur->val);
                copytail=copytail->next;
            }
            Map[cur]=copytail;
            cur=cur->next;
        }

        cur=head;
        Node* copy=copyhead;
        while(cur)
        {
            if(cur->random==nullptr)
            {
                copy->random=nullptr;
            }
            else
            {
                copy->random=Map[cur->random];//直接从map表中找到相应的节点地址
            }
            cur=cur->next;
            copy=copy->next;
        }
        return copyhead;
    }
};

2、环形链表2

(题目链接:https://leetcode.cn/problems/linked-list-cycle-ii/solutions/441131/huan-xing-lian-biao-ii-by-leetcode-solution/)

对于该题,在学习set与map之前我们通常用快慢指针法解决,由于数学原理与图形关系,倘若有环,快慢指针必定相遇 ,因此可以反向求出第一个入环节点的位置。

ListNode* hasCycle(struct ListNode* head)
{
    if (head == NULL || head->next == NULL)//当传过来的链表是空链表,或者传来的只有一个节点,且指向为空,说明不是环
    {
        return NULL;//返回false
    }
    ListNode* pcur = head, * ptail = head;
    while (ptail->next && ptail->next->next)//ptail->next与ptail->next->next其中有一个为假,就说明该指针有尽头,不是环
    {
        ptail = ptail->next->next;//快慢指针,如果相遇了,说明必有环,不相遇则一定有尽头
        pcur = pcur->next;
        if (ptail == pcur)
        {
            return ptail;//返回相遇点地址
        }
    }
    return NULL;
}

 struct ListNode* detectCycle(struct ListNode* head)
 {
     //如果有环,先找到相遇点
     ListNode* meet = hasCycle(head);
     if (meet == NULL)
     {
         return NULL;
     }
     //说明有环
     //快指针速度为慢指针两倍,快指针所走的路程为慢指针两倍,画出路程图可以列出关系式:当fast从相遇点出发,slow从head出发,速度相同,路程相同
     //会同时到达第一个相交节点
     ListNode* slow = head;

     while (slow != meet)
     {
         slow = slow->next;
         meet = meet->next;
     }
     return meet;
 }

但我们现在可以用set来记录出现过的链表节点,轻松解决问题。

class Solution 
{
public:
    ListNode *detectCycle(ListNode *head) 
    {
        
        set<ListNode *>Set;
        ListNode*ret=nullptr,*cur=head;
        while(cur)
        {
            bool Find=Set.insert(cur).second;
            if(Find==false)
            {
                ret=cur;
                break;
            }
            cur=cur->next;
        }
        return ret;
    }
};

二者代码复杂度一看便知。

3、前K个高频单词

(题目链接:https://leetcode.cn/problems/top-k-frequent-words/description/)

在我们看见第k大,前k个这样的关键字时,我们就想到了这是一个topk问题,自然也可以用优先级队列解决。但我们这里可以结合map的性质,与仿函数相结合解决问题。

我们可以用一个一个 map<string, int> Map,用于统计每个单词的出现次数。string 是单词,int 是该单词的出现频率。

随后使用 for (auto it : words) 遍历 words 数组,对于每个单词,将其频率在 Map 中加 1。这个循环完成后,Map 中存储了每个单词对应的出现频率。

接下来再缔造一个vector<pair<string, int>>,用来我们对pair进行排序,所以我们要添加仿函数来排序pair对象。

class Solution
{
public:
    struct Compare
    {
        bool operator()(const pair<string, int>& a, const pair<string, int>& b)
        {
            /*return a.second > b.second || (a.second == b.second && a.first < b.first);*/
            return a.second > b.second ;
        }
    };
    vector<string> topKFrequent(vector<string>& words, int k)
    {
        map<string, int>Map;
        vector<string>ret;
        for (auto it : words)
        {
            Map[it]++;
        }

        vector<pair<string, int>>arr(Map.begin(), Map.end());
     /*   sort(arr.begin(), arr.end(), Compare());*/
        stable_sort(arr.begin(), arr.end(), Compare());
        for (int i = 0; i < k; ++i)
        {
            ret.push_back(arr[i].first);
        }
        return ret;
    }
};

我这里的代码结合了map本身对key键值对就进行了字典序排序,所以我们后面才使用稳定的stable_sort,这样就算两个key的val相同,二者的字典序相对顺序也不会改变。。如果我们要使用sort,就需要在仿函数中加上对val值相同,比较字典序的判断条件。

4、单词识别

(题目链接;http://https://www.nowcoder.com/practice/http://https://www.nowcoder.com/practice/)

对与此题,我们也可以类似的用map来存储单词与出现频率的关系。

为了能够接受一句话,所以我们要用getline来接受而不是cin。

#include <iostream>
using namespace std;
#include<map>
#include<algorithm>
#include<string>
#include<vector>

struct Compare
{
    bool operator()(const pair<string,int>&a,const pair<string,int>&b)
    {
        return a.second>b.second;
    }
};

int main()
{
    string s;
    map<string,int>Map;
    getline(cin,s);

    char*ch;
    for(int i=0;i<s.size();++i)
    {
        string ss;
        while(s[i]!=' ')
        {
            if(s[i]=='.')
            {
                break;
            }

            ss+=tolower(s[i++]);
        }
        Map[ss]++;
    }


    vector<pair<string,int>>arr(Map.begin(),Map.end());
    stable_sort(arr.begin(),arr.end(),Compare());
    for(auto it:arr)
    {
        cout<<it.first<<":"<<it.second<<endl;
    }
    return 0;
}

三、总结

在本文中,我们通过多个实际例题,深入探讨了 mapset 容器的常见操作和实际应用。这些容器在处理数据的过程中能够提供强大的支持,尤其在涉及到元素的唯一性、快速查找和数据关联的场景下。

1. 基本操作与接口

我们首先回顾了 mapset 容器的基本操作,包括元素插入、查找、删除等常用接口。在日常的算法题解中,这些基本操作为我们提供了便捷的工具。例如,使用 map 记录元素之间的关联关系,利用 set 处理去重和排序问题。

2. 实战应用

我们通过几个经典的算法题,展示了如何在实际编程中运用 mapset 容器。通过这些案例,我们看到了这些容器在不同场景下的灵活性和高效性:

  • 随机链表的复制:利用 map 建立新旧节点之间的映射关系,简化了链表的深拷贝操作。
  • 环形链表检测:通过 set 记录已经访问过的节点,轻松找到链表中的环。
  • 前K个高频单词:结合 map 统计频率和仿函数排序,快速找到出现频率最高的单词。
  • 单词识别:使用 map 统计句子中每个单词的频率,并按出现次数进行排序,展示了 map 的实用性。
3. 稳定排序的应用

在排序过程中,我们强调了 stable_sort 的使用,这对于保持排序的稳定性尤为重要。在某些题目中,我们需要确保排序过程中,相同频率的单词按字典序排列,而 stable_sort 正是保证这种稳定性的关键。

4. 代码复杂度与性能

通过与传统方法的比较,我们可以看到使用 mapset 后,代码复杂度得到了简化,逻辑更为清晰。虽然这些容器的底层实现较为复杂,但它们提供的高效查找和插入性能,使得我们在解决实际问题时受益匪浅。

结语

学习 mapset 容器不仅仅是掌握其基本用法,更重要的是通过实践,理解它们在不同场景中的应用和优势。本文的示例和解析,希望能帮助读者更好地掌握这些工具,并在今后的算法和编程实践中更加得心应手。通过不断练习,读者可以熟练地将 mapset 应用于各种复杂的数据处理任务中,从而提升编程效率和代码质量。

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

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

相关文章

【hot100篇-python刷题记录】【不同路径】

R5-多维动态规划篇 多维动态规划的核心在于建立多维状态记录表。 本题中&#xff0c;建立dp二维数组表&#xff08;初始化为1&#xff09; dp[i][j]dp[i-1][j]dp[i][j-1] 注意&#xff0c;需要判断是否存在&#xff0c;因为二维数组有边界 第一种处理需要判断边界 第二种&…

go的defer机制

defer的底层机制 为栈操作&#xff0c;栈是一个先进后出的数据结构 func main() {fmt.Println("reciprocal")for i : 0; i < 10; i {defer fmt.Println(i)} }运行结果 reciprocal 9 8 7 6 5 4 3 2 1 0defer拷贝机制 以下已经发生压栈发生值拷贝数据不再会发生变…

【Python机器学习系列】一文教你绘制多分类任务的ROC曲线-宏平均ROC曲线(案例+源码)

这是我的第345篇原创文章。 一、引言 ROC曲线是用于评估二分类模型性能的工具&#xff0c;它展示了模型在不同阈值下的真阳性率与假阳性率之间的关系&#xff0c;但是标准的ROC并不能运用于多分类任务种&#xff0c;于是扩展出了宏平均ROC曲线。 宏平均ROC曲线是多分类问题中…

工业控制常用“对象“数据类型汇总(数据结构篇)

合理巧妙的数据结构会大大简化项目的编程工作量,所以任何项目前期第一步应该是设计巧妙的数据结构、封装对象属性。这样会使我们的编程快捷和高效。这篇博客作为数据类型汇总,会不间断更新。 1、普通电机轴对象 2、普通电机轴对象(详细结构变量) TYPE "udtMotorAxis&q…

机器学习的入门笔记(第十五周)

本周观看了B站up主霹雳吧啦Wz的图像处理的课程&#xff0c; 课程链接&#xff1a;霹雳吧啦Wz的个人空间-霹雳吧啦Wz个人主页-哔哩哔哩视频 下面是本周的所看的课程总结。 利用GoogLeNet进行图像分类 GoogLeNet是由 Google 提出的卷积神经网络架构&#xff0c;于 2014 年在 …

没有用的小技巧之---接入网线,有内网没有外网,但是可以登录微信

打开控制面板&#xff0c;找到网络和Internet 选择Internet选项 点击连接&#xff0c;选择局域网设置 取消勾选代理服务器

JetBrains CLion 2024.2 (macOS, Linux, Windows) - C 和 C++ 跨平台 IDE

JetBrains CLion 2024.2 (macOS, Linux, Windows) - C 和 C 跨平台 IDE JetBrains 跨平台开发者工具 请访问原文链接&#xff1a;https://sysin.org/blog/jetbrains-clion/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org Jet…

实战勤务指挥系统解决方案

4. 总体设计方案 方案围绕业务需求、接口需求和安全需求进行设计&#xff0c;包括语音集成、视频图像集成和第三方系统集成&#xff0c;以实现多系统联动和资源共享。 5. 系统特色 系统特色包括高度融合的指挥应用模式、简化的指挥流程、高效的管理机制&#xff0c;以及基于…

《Windows PE》2.1 初识PE文件

Windows PE文件&#xff08;Portable Executable file&#xff09;是一种可执行文件格式&#xff0c;用于Windows操作系统中的可执行程序、动态链接库&#xff08;DLL&#xff09;和驱动程序等。它是一种规范化的文件格式&#xff0c;定义了文件的结构和组织方式&#xff0c;以…

go设计模式———抽象工厂模式

抽象工厂模式概念 抽象工厂模式是一种设计模式&#xff0c;它允许创建一系列相关的对象&#xff0c;而无需指定具体的类。具体来说&#xff0c;抽象工厂定义了用于创建不同产品的接口&#xff0c;但实际的创建工作则由具体的工厂类完成。每个具体工厂负责创建一组相关的产品&am…

谷歌账号停用后申诉了,也收到了谷歌的邮件,如何判断谷歌申诉是否成功,成功了怎么办?被拒绝谷歌账号就废了吗?

似乎是谷歌分工机制的更新&#xff0c;最近谷歌账号“被停用”的情况貌似多了起来&#xff0c;许多朋友在谷歌账号提示活动异常&#xff0c;要输入手机号码恢复账号的时候&#xff0c;无论是否立刻恢复&#xff0c;很快好像就迎来了“您的账号已停用”的结果。或者有一些朋友许…

多元统计分析——基于R语言的单车使用情况可视化分析

注&#xff1a;基于R语言的单车使用情况可视化分析为实验记录&#xff0c;存在不足&#xff0c;自行改进。 一、提出问题&#xff08;要解决或分析的问题&#xff09; 1 、用户对共享单车的使用习惯&#xff0c;环境对共享单车运营带来的影响&#xff1f; 2 、共享单车的租赁…

【北京仁爱堂】痉挛性斜颈的健康指导

痉挛性斜颈是一种肌肉紧张异常症&#xff0c;仅限于颈部肌肉的肌张力障碍。当患者患有痉挛性斜颈&#xff0c;会表现为颈部肌肉间歇性或持续不规则的收缩&#xff0c;因此患者的头颈部会出现扭曲、歪斜、姿势异常等症状&#xff0c;多发于30-40岁左右中年人 一、 痉挛性斜颈的5…

mac和windows上安装nvm管理node版本

NVM 是 node version manager 的缩写&#xff0c;它是一个用来管理电脑上 node 版本的命令行工具&#xff0c;在日常前端开发中是一个跟 node 一样会经常用到的工具&#xff0c;可以很方便的让我们快速切换不同的node版本。 mac 上安装 nvm 1、下载安装 nvm 下载安装可以直…

【机器学习】逻辑回归原理(极大似然估计,逻辑函数Sigmod函数模型详解!!!)

目录 &#x1f354; 逻辑回归应用场景 &#x1f354; 极大似然估计 2.1 为什么要有极大似然估计&#xff1f; 2.2 极大似然估计步骤 2.3 极大似然估计的例子 &#x1f354; Sigmod函数模型 3.1 逻辑斯特函数的由来 3.2 Sigmod函数绘图 3.3 进一步探究-加入线性回归 3…

【爬虫】 使用AI编写B站爬虫代码

记录一次&#xff0c;自己不写一行代码&#xff0c;所有的代码全由AI编写的过程。 本次使用的AI工具为&#xff1a;Claude 其他AI工具同理。 首先&#xff0c;观察哔哩哔哩网页的结构&#xff0c;定位到了包含视频信息的关键元素。右键检查或打开F12&#xff0c;找到最左侧的这…

2024前端面试题-js篇

1.js有哪些数据类型 基础数据类型&#xff1a;string,number,boolean&#xff0c;null&#xff0c;undefined&#xff0c;bigInt&#xff0c;symbol 引用数据类型&#xff1a;Object 2.js检测数据类型的方式 typeof&#xff1a;其中数组、对象、null都会被判断为object&…

基于WebSocket打造的一款SSH客户端

引用&#xff1a;Java打造一款SSH客户端&#xff0c;而且已开源_java ssh客户端-CSDN博客 由于原作者是放在Github上&#xff0c;不方便下载&#xff0c;所以下载下来&#xff0c;转存到码云上&#xff0c;地址&#xff1a;https://gitee.com/lfw1024/web-ssh 为了满足一些小白…

计算机毕业设计选题推荐-股票数据可视化分析与预测-Python爬虫

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

Windows10拿到shell后远程登录

一、准备工作 kali机&#xff1a;192.168.19.130 win10&#xff1a;192.168.19.133 主机&#xff1a;192.168.1.73&#xff08;自己操作可以在kali上&#xff0c;我这是因为反弹的shell在主机上&#xff09; 二、开启远程登录 1.win10上关闭实时保护&#xff0c;并且运行了…