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

前言:

本文主要讲解的时对于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 应用于各种复杂的数据处理任务中,从而提升编程效率和代码质量。

  • 21
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
回答: 在C++中,map、unordered_mapset和unordered_set都是STL(标准模板库)中的容器。它们都用于存储一组数据,并提供了不同的功能和性能特点。 map是一个有序的关联容器,它使用红黑树实现,可以根据键值进行快速查找。map中的元素按照键值的大小进行排序,并且每个键值只能出现一次。\[1\]unordered_map是一个无序的关联容器,它使用哈希表实现,可以根据键值进行快速查找。unordered_map中的元素没有特定的顺序,并且每个键值只能出现一次。\[2\] set是一个有序的容器,它使用红黑树实现,可以存储不重复的元素。set中的元素按照值的大小进行排序,并且每个值只能出现一次。\[3\]unordered_set是一个无序的容器,它使用哈希表实现,可以存储不重复的元素。unordered_set中的元素没有特定的顺序,并且每个值只能出现一次。 在使用这些容器时,可以使用insert()函数插入元素,使用find()函数查找元素,使用erase()函数删除元素。此外,map和unordered_map还提供了count()函数来计算特定键值的出现次数。 总结来说,map和unordered_map适用于需要根据键值进行快速查找的场景,set和unordered_set适用于需要存储不重复元素的场景。具体选择哪个容器取决于你的需求和性能要求。 #### 引用[.reference_title] - *1* *3* [C++map,unordered_map,set和unordered_set的用法和区别](https://blog.csdn.net/bryant_zhang/article/details/111600209)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [初识C++之unordered_map与unordered_set](https://blog.csdn.net/Masquerena114514/article/details/129938734)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

渡我白衣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值