Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >【C++指南】你真的了解map和set吗?【下】

【C++指南】你真的了解map和set吗?【下】

作者头像
egoist祈
发布于 2025-03-25 05:07:31
发布于 2025-03-25 05:07:31
3200
代码可运行
举报
文章被收录于专栏:egoistegoist
运行总次数:0
代码可运行

map系列的使用

Key就是map底层关键字的类型,T是map底层value的类型,set默认要求Key⽀持大小⽐较。

map底层存储数据的内存是从空间配置器申请的。⼀般情况下,我们都不需要传后两个模版参数。map底层是⽤红⿊树实 现,增删查改效率是 O(logN) 迭代器遍历是⾛的中序,所以是按key有序顺序遍历的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
template < class Key,class T,class Compare = less<Key>,
           class Alloc = allocator<pair<const Key,T> >>

了解pair

map系列中并未像之前在实现二叉搜索树中在结构体中存储Key/Value,而是把它们封装在pair的结构体当中。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
typedef pair<const Key, T> value_type;
template <class T1, class T2>
struct pair
{
    typedef T1 first_type;
    typedef T2 second_type;

    T1 first;
    T2 second;
    pair(): first(T1()), second(T2())
    {}

    pair(const T1& a, const T2& b): first(a), second(b)
    {}

    template<class U, class V>
    pair (const pair<U,V>& pr): first(pr.first), second(pr.second)
    {}
};

构造及迭代器

迭代器区间构造

map (InputIterator first, InputIterator last)

拷贝构造

map (const map& x)

initializer list构造

map (initializer_list<value_type> il)

双向迭代器

a bidirectional iterator to const pair<Key,T>

增删查

插入某值

pair<iterator,bool> insert (const pair<Key,T>& val)

插入迭代器区间

template <class InputIterator> void insert (InputIterator first, InputIterator last);

initializer_list列表插入

void insert (initializer_list<value_type> il)

删除某位置

iterator erase (const_iterator position)

删除某值

size_t erase (const Key& k)

删除迭代器区间

iterator erase (const_iterator first, const_iterator last)

查找某值(返回迭代器)

iterator find (const Key& k);

可以看到,stl库设计尽量保持接口一致,如上插入接口中,返回类型依旧是pair<iterator,bool>。

在下面一段程序中,我们通过map一系列接口的设计完成了对arr数组中的字符串的计数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int main()
{
	string arr[] = { "苹果","香蕉","菠萝","苹果","香蕉" ,"苹果" ,"菠萝","苹果" };
	map<string, int> countmap;
	for (auto& ch : arr)
	{
		auto it = countmap.find(ch);
		if (it == countmap.end())
		{
			countmap.insert({ ch,1 });
		}
		else
		{
			it->second++;
		}
	}
	for (auto& ch : countmap)
	{
		cout << ch.first << " " << ch.second << endl;
	}
}

数据修改

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
我们清楚无论在map还是set系列都是不允许修改Key值的,因此这里的数据修改指的是修改Value值。可以这样想,在现实生活中,一个英文单词可以对应多个中文意思。

mapped_type& operator[] (const key_type& k)

这里的mapped_type实际就是Value值。

map的 [ ] 看起来高大上,实际上底层就是一个套了insert的壳,是如何实现的呢?

小编这里简化了主要的代码实现。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
V& operator[](const K& key)
{
	pair<iterator, bool> ret = insert({ key, V() });
	return ret.first->second;
    //  *(ret.first).second//*引用指针,再指向值
}

开始看return返回值时可能会一头雾水,ret是pair的对象,那么ret.first访问的应该是pair中的Key值,后面的second又是什么意思呢?

这里其实是有两层含义的,首先ret是一个对象,ret.first访问的是迭代器iterator,进而ret.first->second访问pair中的Value值,实际是ret.first.operator->()->second访问指针指向的second的值。

[ ]的用法
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	//[]的插入
	ret["left"];

	//[]的插入+修改
	ret["right"] = "右边";

	//[]的修改
	ret["left"] = "左边";

	//[]的查找
	cout << ret["left"] << endl;

[ ] 统计次数

在上文中实现了一个对出现单词的频率的计数,那是否可以用map中的[]接口的设计进行完善呢?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
map<string, int> countmap;

for (auto& ch : arr)
{
    countmap[ch]++;
}

是的你没看错,只需要使用[]就能完成单词的计数,那它是如何达到我们的目的的呢?

  1. 开始,map中没有存储数据,此时[]插入会成功,更新迭代器位置,返回Value值并进行++,此时Value为1;
  2. 下一个数据如果在map中出现过,会插入失败,不更新迭代器位置,但是依旧会返回Value并进行++;
  3. 综上两种情况,每出现一个单词都会对对应单词进行计数。

multimap

multimap版本是支持冗余的,那么insert/find/count/erase都围绕着⽀持关键值key冗余有所差异。

需要注意的是multimap并不支持 [ ] 接口,因为Key是支持冗余的,[] 操作符的设计初衷是返回一个唯一值,若键存在,它会返回该键对应的值 。如果多个相同的Key可能存在不同的Value会导致 [ ] 接口语义模糊不清。


博主通过以下习题让大火感谢下map系列的用处。

随机链表的复制

之前在实现随机链表的复制时,我们需要将每个结点都进行拷贝,通过旧链表进行random的链接,最后还要断开新旧链表,实现的步骤是非常麻烦的。

但学了map容器后,此题可以通过map中的映射关系,完成对随机链表的复制。

  1. 通过map存储新旧结点并实现映射关系,方便之后处理random。
  2. 通过[]接口设计完成对拷贝随机链表中random的链接。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Node* copyRandomList(Node* head) {
        Node* cur=head;
        map<Node*,Node*> nodemap;//存在映射关系
        Node* phead=nullptr,*ptail=nullptr;
        while(cur)
        {
            if(ptail==nullptr)
            {
                phead=ptail=new Node(cur->val);
            }
            else
            {
                ptail->next=new Node(cur->val);
                ptail=ptail->next;
            }
            nodemap[cur]=ptail;
            cur=cur->next;
        }

        //完成主逻辑
        cur=head;
        Node* ret=phead;
        while(cur)
        {
            if(cur->random==nullptr)
            {
                ret->random=nullptr;
            }
            else
            {
                ret->random=nodemap[cur->random];
            }
            ret=ret->next;
            cur=cur->next;
        }
        return phead;
    }

前K个高频单词(重点)

按此题要求会有以下情况:

  1. 如何对每个单词计数?
  2. 需要返回k个出现次数最多的单词,需要进行排序?用什么排序?
  3. 单词频率相同情况下,如何处理?

解决方法:

  1. 有map[]的接口可以实现对每个单词进行计数;
  2. 使用multimap实现;用稳定排序(归并排序);使用快排;用堆排序,那应该建大根堆还是小根堆?
  3. 在使用快排时会出现一些问题,需要自己实现仿函数控制,在下面小编会谈及。

对单词频率进行计数后,想再通过map来对前k个高频单词记录在vector中,但是会出现一个问题:可能不同单词出现的频率是一样的,由于map不支持冗余,会导致之后出现频率相同的单词不被记录。因此,在这里使用multimap,first存的是次数,second存字符串

multimap实现

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        //记录次数
        map<string,int> countmap;
        for(auto& ch:words)
            countmap[ch]++;

        //使用multimap来完成
        multimap<int,string,greater<int>> mtmap;//完成降序
        for(auto& ch:countmap)
        {
            mtmap.insert({ch.second,ch.first});
        }

        vector<string> v;
        auto it=mtmap.begin();
        while(k--)
        {
            v.push_back(it->second);
            it++;
        }
        return v;

此题对应TopK问题,我们一般采用排序来处理问题。

要求的是返回前k个频率高的单词,而频率count存在map中的second,因此在使用排序时需要自己手动实现仿函数比较second。

快排(错误示例)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        bool operator()(const pair<string,int>& v1,const pair<string,int>& v2)
        {
            return v1.second>v2.second;
        }

        //记录次数
        map<string,int> countmap;
        for(auto& ch:words)
            countmap[ch]++;

        vector<pair<string,int>> v(countmap.begin(),countmap.end());

        vector<string> ret;
        sort(v.begin(),v.end(),vcompare()); //需要手动实现仿函数

        for(int i=0;i<k;i++)
        {
            ret.push_back(v[i].first);
        }
        return ret;

但是提交后会报错,这是为什么?

原因在于快排是一个不稳定的排序,何为不稳定?

稳定性:在待排序的序列中经过排序后,若相对排序保持不变,则称这种算法是稳定的;反之,相对顺序改变是不稳定的。

排序⽅法

平均情况

最好情况

最坏情况

辅助空间

稳定性

冒泡排序

O(n^ 2 )

O(n)

O(n^ 2 )

O(1)

稳定

直接选择排序

O(n^ 2 )

O(n 2 )

O(n^ 2 )

O(1)

不稳定

直接插⼊排序

O(n^ 2 )

O(n)

O(n^ 2 )

O(1)

稳定

希尔排序

O(nlog n) ~ O(n^ 2 )

O(n^ 1.3 )

O(n^ 2 )

O(1)

不稳定

堆排序

O(nlog n)

O(nlog n)

O(nlog n)

O(1)

不稳定

归并排序

O(nlog n)

O(nlog n)

O(nlog n)

O(n)

稳定

快速排序

O(nlog n)

O(nlog n)

O(n^ 2 )

O(log n) ~ O(n)

不稳定

在std库中提供了稳定的排序(底层是归并排序)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
stable_sort(v.begin(),v.end(),vcompare());

如果非要使用快排呢?可以通过调整仿函数实现目的

我们需要解决的问题便是当单词出现频率相同时,如何按字典序排序。那么在仿函数中控制字典序排序的逻辑就能达到目的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
bool operator()(const pair<string,int>& v1,const pair<string,int>& v2)
{
    return (v1.second>v2.second||(v1.second==v2.second&&v1.first<v2.first));
}

在TopK问题中,我们一般采用堆排序来解决问题。那何时使用大根堆,何时使用小根堆呢?

  1. 在面临数据量较小情况下,如果是取前k个降序元素时,一般使用大根堆;
  2. 在处理海量数据时,如果是取前k个降序元素,一般使用小根堆。

需要注意的是,stl模板库中priority_queue的默认传的是less仿函数,理应是升序,但这里比较特殊 --> 排的是降序(槽点),因此仿函数的逻辑得反着来。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        bool operator()(const pair<string,int>& v1,const pair<string,int>& v2)
        {
            //次数相等的,字典序在前面
            return (v1.second<v2.second||(v1.second==v2.second&&v1.first>v2.first));
        }

        map<string,int> countmap;
        for(auto& ch:words)
            countmap[ch]++;

        // 将map中的<单词,次数>放到priority_queue中,仿函数控制⼤堆,次数相同按照字典
        priority_queue<pair<string, int>, vector<pair<string, int>>, vcompare> 
            p(countmap.begin(), countmap.end());
        vector<string> strV;   
        for(int i = 0; i < k; ++i)
        {
            strV.push_back(p.top().first);
            p.pop();
        }
        return strV;
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-03-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【C++】map和set的介绍及使用
map和 set 是 C++ STL(标准模板库)中的两种非常重要的容器,它们基于一种叫做平衡二叉搜索树(通常是红黑树)的数据结构来实现。在 C++ 中,map 是一个键值对容器,set 只存储唯一的键,而这两个容器都通过二叉树的结构来保持数据的有序性和高效的查找、插入、删除操作。
用户11375356
2024/11/22
800
【C++】map和set的介绍及使用
C++:map和set的使用
在学习map和set之前,我们接触到的容器有:vector、list、stack、queue、priority_queue、array,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。
小陈在拼命
2024/04/20
1360
C++:map和set的使用
【C++】map和set在OJ中的应用
如果大家看过我之前初阶数据结构的博客的话会发现这道题我们其实是讲过的,不过当时我们使用C语言搞的,说实话C语言实现起来还是挺麻烦的。 大家可以看一下之前这篇文章:
YIN_尹
2024/01/23
1580
【C++】map和set在OJ中的应用
C++ STL 中的 map:高效管理键值对的有序容器
map的声明如下,Key就是map底层关键字的类型,T是map底层value的类型,set默认要求Key支持小于比较,如果不支持或者需要的话可以⾃⾏实现仿函数传给第⼆个模版参数,map底层存储数据的内存是从空间配置器申请的。⼀般情况下,我们都不需要传后两个模版参数。map底层是⽤红⿊树实现,增删查改效率是 O(logN) ,迭代器遍历是⾛的中序,所以是按key有序顺序遍历的。
用户11286421
2024/11/21
1140
【C++】关联式容器——map和set的使用
用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息。
平凡的人1
2023/10/15
2870
【C++】关联式容器——map和set的使用
(清晰易懂版)(multi)map和set--C++
前面我们已经接触过STL中的部分容器如:string、vector、list、deque、array、forward_list等,这些容器统称为序列式容器,因为逻辑结构为线性序列的数据结构,两个位置存储的值之间一般没有紧密的关联关系,比如交换一下,他依旧是序列式容器。顺序容器中的元素是按他们在容器中的存储位置来顺序保存和访问的。
小志biubiu
2025/02/27
760
【C++】初探 map 与 set
关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高!
叫我龙翔
2024/05/26
680
【C++】初探 map 与 set
【C++】map、set、multimap、multiset的介绍和使用
1. 之前所学的vector,list,deque等容器都是序列式容器,因为他们的底层数据结构都是线性的,并且数据结构中存储的都是元素数据本身,也就是单一的变量。 而下面所学的set、map、multimap、multiset等容器都是关联式容器,他们内部存储的不再是单一的元素数据,存储的而是<key,value>的键值对,由于每个键值对之间都有关联,所以其结构天生就具有优势,在数据检索时效率要比序列式容器高的多。
举杯邀明月
2023/04/12
7370
【C++】map、set、multimap、multiset的介绍和使用
深度解析C++中的map的使用
map的声明如下,Key就是map底层关键字的类型,T是map底层value的类型,set默认要求Key支持小于比较,如果不支持或者需要的话可以自行实现仿函数传给第二个模版参数,map底层存储数据的内存是从空间配置器申请的。一般情况下,我们都不需要传后两个模版参数。map底层是用红黑树实现,增删查改效率是O(logN),迭代器遍历是走的中序,所以是按key有序顺序遍历的。
凯子坚持C
2025/01/04
550
【C++修炼之路】18.map和set
T: set中存放元素的类型,实际在底层存储<value, value>的键值对。 **Compare:**set中元素默认按照小于来比较 **Alloc:**set中元素空间的管理方式,使用STL提供的空间配置器管理
每天都要进步呀
2023/03/28
7370
【C++修炼之路】18.map和set
c++ map和set_STLset和map的区别
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
全栈程序员站长
2022/10/02
4220
c++ map和set_STLset和map的区别
【C++】详解 set && multiset && map && multiset 的使用
​ 我们已经接触过 STL 中的部分容器,比如:vector、list、deque、forward_list 等,这些容器统称为 序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。至于 stack 和 queue,他们其实不能算是容器,而应该是**容器适配器**,是用 deque 封装的。
利刃大大
2025/02/16
610
【C++】详解 set && multiset && map && multiset 的使用
【C++】map和set的使用
  vector、list、dequeforward_list(C++11)等,这些容器统称为 序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。那什么是关联式容器?它与序列式容器有什么区别?
用户11029129
2024/08/14
680
【C++】map和set的使用
C++:set和map的使用
一般来说,像string、vector、list、deque、forward_list等容器,这些容器的底层逻辑机构为线性序列的数据结构,所以这些容器也叫做序列式容器,序列式容器两个位置存储的值之间一般没有紧密的关联关系,如若将其交换,依旧是序列式容器。序列式容器中的元素是按他们在容器中的存储位置保存和访问的。
HZzzzzLu
2024/11/26
1260
C++:set和map的使用
【C++】树型结构关联式容器:map/multimap/set/multisetの使用指南(27)
YY的秘密代码小屋
2024/01/23
2140
【C++】树型结构关联式容器:map/multimap/set/multisetの使用指南(27)
【C++】unordered_set和unordered_map的封装(哈希)
秦jh
2024/08/20
1210
【C++】unordered_set和unordered_map的封装(哈希)
移情别恋c++ ദ്ദി˶ー̀֊ー́ ) ——13.map&&set
关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是结构的 键值对, 在数据检索时比序列式容器效率更高。
hope kc
2024/09/23
690
移情别恋c++ ദ്ദി˶ー̀֊ー́ ) ——13.map&&set
【C++】开散列实现unordered_map与unordered_set的封装
本文主要介绍unordered_map与unordered_set的封装,此次封装主要用上文所说到的开散列,通过开散列的一些改造来实现unordered_map与unordered_set的封装
平凡的人1
2023/10/15
1920
【C++】开散列实现unordered_map与unordered_set的封装
【C++】map和set的封装
set 参数只有 key,但是map除了key还有value。我们还是需要KV模型的红黑树的:
平凡的人1
2023/10/15
1660
【C++】map和set的封装
C++【set 和 map 学习及使用】
set 和 map 是 STL 中的容器之一,不同于普通容器,它俩的查找速度极快,常用来存储各种经常被检索的数据,因为这俩容器的底层是平衡二叉搜索树中的红黑树。除此之外,还可以借助其特殊的性质,解决部分难题
北 海
2023/07/01
3600
C++【set 和 map 学习及使用】
相关推荐
【C++】map和set的介绍及使用
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验