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

【C++】mapset使用与实战 OJ题

📚 博主的专栏

🐧 Linux   |   🖥️ C++   |   📊 数据结构

关联文章:【C++】搜索二叉树的实现以及应用-CSDN博客

目录

🌟 核心知识点概览

 set的介绍

set的使用

set的模板参数列表

1. 插入元素

2. 遍历与访问 

3. 创建与初始化

4. 查找与统计

oj: 

349. 两个数组的交集 - 力扣(LeetCode)

multiset

初始化与插入

批量插入验证

map的使用:

1. 构造初始化与插入

pair​编辑

  make_pair

​编辑

initializer_list

使用迭代器遍历:

范围for遍历: 

2. 安全查找

3. 统计次数:

 4.operator[]的简单实现与使用

oj

138. 随机链表的复制 - 力扣(LeetCode)

5.count --- 计数,可判断是否存在,返回值是存在的个数

multimap:

 equal_range

功能和用途

返回值

输出

进阶

1. 自定义排序规则

2. 范围查询

3. 高效删除

OJ练习

692. 前K个高频单词 - 力扣(LeetCode)

解决办法1:使用一个稳定的排序就能解决

实现原理

stable_sort与 sort 的区别

解决办法2:仿函数的返回值,次数大的在前面,次数相等的,字典序小的在前面


🌟 核心知识点概览

关联式容器 vs 序列式容器

  • 序列式容器vectorlist等,存储元素本身,无关联性。

  • 关联式容器setmap等,存储<key, value>键值对,数据关联性强,查询效率高(O(log n))。
    🔑 核心区别:底层数据结构(红黑树 vs 线性结构)。

键值对(Key-Value)

  • 表示一一对应关系,如字典中的英文单词与释义。

  • setkey模型,mapkey-value模型,底层均为红黑树

🌲 树形结构的关联式容器

1. Set

  • 特性

    • 元素唯一、有序、不可修改。

    • 插入自动去重,遍历输出有序序列。

    • 底层红黑树实现,查找时间复杂度为O(log n)

  • 操作示例

2. Map

  • 特性

    • 存储<key, value>键值对,key唯一。

    • 支持operator[]快速访问(插入或修改值)。

  • 操作示例

3. Multiset & Multimap

  • 特性

    • 允许重复键值,元素按排序规则存储。

    • multimapoperator[],需用equal_range遍历相同键值。

  • 示例

    multimap<string, int> mm;  
    mm.insert({"apple", 1});  
    mm.insert({"apple", 2}); // 允许重复  

⚙️ 进阶操作与技巧

  1. 自定义排序规则

  2. 范围查询与删除

  3. 统计高频元素(LeetCode 692)

    • 结合map统计词频,priority_queue取Top K。

    • 仿函数优化

🚀 实战应用场景

数组交集(LeetCode 349)

  • 双指针遍历有序集合,比较元素大小。

随机链表复制(LeetCode 138)

  • 使用map记录新旧节点映射关系。

1. 关联式容器

在初阶阶段,我们已经接触过STL中的部分容器,比如:vector、list、deque、forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身存储的数据与数据之间没什么关联

那什么是关联式容器?它与序列式容器有什么区别?

关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高。不仅仅是数据存储,一般还可以查找数据,存储的数据和数据之间有很强的关联性。

2. 键值对

用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息。比如:现在要建立一个英汉互译的字典,那该字典中必然有英文单词与其对应的中文含义,而且,英文单词与其中文含义是一一对应的关系,即通过该应该单词,在词典中就可以找到与其对应的中文含义。

实际上set就是key模型的搜索树,map就是key-value的搜索树,但是底层不是直接的搜索树,底层使用的是红黑树,时间复杂度是O(logn),10亿个值,找30次

对于搜索树的实现与应用可以看以下这篇博客:

3. 树形结构的关联式容器

根据应用场景的不桶,STL总共实现了两种不同结构的管理式容器:树型结构与哈希结构。树型结构的关联式容器主要有四种:map、set、multimap、multiset。这四种容器的共同点是:使用平衡搜索树(即红黑树)作为其底层结果,容器中的元素是一个有序的序列。下面一依次介绍每一个容器。

 set的介绍

cplusplus.com/reference/set/set/

翻译:

1. set是按照一定次序存储元素的容器

2. 在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。

set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。

3. 在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行排序。

4. set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对

子集进行直接迭代。

5. set在底层是用二叉搜索树(红黑树)实现的。

注意:

1. 与map/multimap不同,map/multimap中存储的是真正的键值对<key, value>,set中只放

value,但在底层实际存放的是由<value, value>构成的键值对。

2. set中插入元素时,只需要插入value即可,不需要构造键值对。

3. set中的元素不可以重复(因此可以使用set进行去重)。

4. 使用set的迭代器遍历set中的元素,可以得到有序序列

5. set中的元素默认按照小于来比较

6. set中查找某个元素,时间复杂度为:$log_2 n$

7. set中的元素不允许修改(为什么?)

8. set中的底层使用二叉搜索树(红黑树)来实现

set的使用

set的模板参数列表

T: set中存放元素的类型,实际在底层存储<value, value>的键值对。

Compare:set中元素默认按照小于来比较

Alloc:set中元素空间的管理方式,使用STL提供的空间配置器管理

1. 插入元素

set的主业是搜索副业是排序(中序排序),并且去重

#include<iostream>

#include<set>
using namespace std;
void test_set1()
{
	set<int> s1;

	s1.insert(1);
	s1.insert(5);
	s1.insert(0);
	s1.insert(11);
	s1.insert(6);
	s1.insert(9);
	s1.insert(3);
	s1.insert(20);
	s1.insert(1);
	s1.insert(8);
	//遍历:
	set<int>::iterator it = s1.begin();
	while(it != s1.end())
	{ 
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

int main()
{
	test_set1();
	return 0;
}

输出结果:

set的普通迭代器不允许修改:

2. 遍历与访问 

	vector<int> v = { 1,5,4,5456,45,885 };
	set<int> s2(v.begin(), v.end());
	for (auto e : s2)
	{
		cout << e << " ";
	}
	cout << endl;

运行结果:

1 4 5 45 885 5456

3. 创建与初始化

set支持{}初始化:

set<int> s1 = {3, 1, 4, 1, 5};  // 实际存储 {1, 3, 4, 5}
set<string> s2{"apple", "banana"};

4. 查找与统计

if (s1.find(3) != s1.end()) {
    cout << "Found 3" << endl;
}
cout << "Count of 3: " << s1.count(3) << endl;  // 输出1或0

oj: 

349. 两个数组的交集 - 力扣(LeetCode)

在set中两个数组已经排好了,使用迭代器比较

1.相等就是交集,it1++,it2++

2.不相等,小的++

3.有一个到尾,就结束

如果想要找差集?比对(同步)算法(几个设备之间的数据同步,我有你没有,你就同步)

1.相等就是交集,it1++,it2++

2.不相等,小的就是差集(后面不可能有比我小的),小的++

multiset

  • 允许存储重复元素

  • 相同元素按插入顺序相邻存储(排序规则依然有效)

  • 时间复杂度与set相同:插入/查找 O(log n)

初始化与插入
multiset<int> ms = {3, 1, 4, 1, 5};  // 存储 {1, 1, 3, 4, 5}
ms.insert(1);  // 插入第四个"1"
批量插入验证
vector<int> batch{2, 2, 2};
ms.insert(batch.begin(), batch.end());  // 插入三个"2"

multiset中,相等值可以在左边,也可以在右边,看具体实现,左边树默认,插入在右边,find默认查找的是中序的第一个(我找到这个x了,并且左树再没有x,那就是现在找到的这个x)


map的使用:

1. 构造初始化与插入

map<string, int> m1;
m1["apple"] = 5;  // 插入或修改
m1.insert({"banana", 3});  // 插入(若存在则不修改)
m1.emplace("orange", 8);  // 原地构造
pair
	map<string, string> dict;
	pair<string, string> kv1("sort", "排序");
	dict.insert(pair<string, string>("left", "左"));
  //pair<string, string> kv2{"sort", "排序"};
	dict.insert({ "hello", "c++" });//隐式类型转换,转pair
    dict.insert(kv1);
	dict.insert(kv2);
  make_pair

只会比较key,如果key值相同就不会再插入 

dict.insert(make_pair("left", "左"));
initializer_list

map<string, string> dict2 = { {"sort", "排序"},{"left", "左"},{"right", "右"} };
使用迭代器遍历:
//遍历
//map<string, string>::iterator it = dict.begin();
auto it = dict.begin();

while (it != dict.end())
{
	//pair不支持流插入
	//cout << *it << " ";
	//cout << (*it).first << ":" << (*it).second << " ";
	cout << it->first << ":" << it->second << " ";//这里省略了一个->

	++it;
}
cout << endl;

first(key不能修改,修改了改变了搜索树大小关系)不能修改,second(value)可以修改 

这里所使用的it -> first it->second的原理,可以看这篇博客:List

查找目录 :


范围for遍历: 

相当于把*it给了kv 最好加&,这样就没有拷贝,

不然全去走深拷贝,代价大,如果不需要修改,加const

	for (auto& kv : dict) //相当于把*it给了kv 最好加&,不然全去走深拷贝,代价大
	{
		cout << kv.first << ":" << kv.second << " ";
	}
	cout << endl;

2. 安全查找

if (auto it = m1.find("apple"); it != m1.end()) {
    cout << it->second << endl;  // 使用迭代器访问
}

3. 统计次数:

void test_map2()
{
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉","苹果","草莓", "苹果","草莓" };
	map<string, int> countMap;
	for (auto& e : arr)
	{
		auto it = countMap.find(e);
		if (it != countMap.end())
		{
			it->second++;//找到了,就计数++
		}
		else
		{
			//const pair<string, int>& val = { e, 1 }; //等价
			countMap.insert({ e, 1 });//首次出现,插入到countMap
		}
	}

	for (auto& kv : countMap)
	{
		cout << kv.first << ":" << kv.second << endl;
	}
	cout << endl;
}

按照Key(这里就是字符)来排序的:而不是按照val

 4.operator[]的简单实现与使用

operator[]的本质就是,给一个key,就返回key对应的value的引用

需要注意的是,如果已经存在key了,直接返回这个key的value的引用,如果没有这个key,就先插入这个key,value相当于一个缺省值。

void test_map2()
{
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉","苹果","草莓", "苹果","草莓" };
	map<string, int> countMap;
	for (auto& e : arr)
	{
		countMap[e]++;  //map最常用的就是[]

	}

	for (auto& kv : countMap)
	{
		cout << kv.first << ":" << kv.second << endl;
	}
	cout << endl;
}

运行结果:

常规的operator[]是通过下标的来随机访问,看这里与vector的对比

类似于这样写的:

示例:插入,插入 + 修改

 这种情况,就充当查找

 修改:

operator[]能够当vector来使用吗?

这两个的意义相同吗?

vector的v[5],返回的是下标为5的数据。                               

而map的m[5],1.如果没有key = 5,就会新插入一个,如果有,假设从key = 0,1,2,3,4,5都和左边是对应的,那么差不多,但是也有可能只有m[5],就直接返回key = 5所对应的val。

因此,Countmap[]++的能直接计数原理在于:

如果苹果不存在,遇到苹果的时候,充当的就是插入 + 修改,key就是苹果,而val++。

oj

138. 随机链表的复制 - 力扣(LeetCode)

解答:

5.count --- 计数,可判断是否存在,返回值是存在的个数

	string str;
	cin >> str;
	if (dict.count(str))
	{
		cout << "在" << endl;
	}
	else
	{
		cout << "不在" << endl;
	}

multimap:

multimap允许键值冗余,就不会存在插入失败的问题,返回新插入节点的迭代器,没有operator[],因为key存在多个。

想让上面的水果按出现的次数排序:

	for (auto& kv : countMap)
	{
		cout << kv.first << ":" << kv.second << endl;
	}
	cout << endl;

	//想让上面的水果按出现的次数排序:
	map<int, string> sortMap;
	for (auto& kv : countMap)
	{
		sortMap[kv.second] = kv.first;
	}
	cout << endl;

	for (auto& kv : sortMap)
	{
		cout << kv.first << ":" << kv.second << endl;
	}
	cout << endl;

这种方法是不正确的,这里使用的是operator[],因为草莓和香蕉出现的次数一样key值同样,插入香蕉的时候,草莓被修改成香蕉,如果直接使用插入insert会导致插入失败,因为存在相同的key值。

因此得使用mutimap: 并且插入sortMap.insert({ kv.second, kv.first });

 equal_range

想删除某个范围,想找出某个范围:

功能和用途

std::multimap::equal_range 会返回一个 std::pair,其中包含两个迭代器:

  • 第一个迭代器指向第一个键等于指定键的元素。

  • 第二个迭代器指向最后一个键等于指定键的元素的下一个位置。

通过这两个迭代器,可以遍历所有具有相同键的元素。

返回值

std::multimap::equal_range 返回一个 std::pair,其中:

  • pair.first 是一个迭代器,指向第一个键等于指定键的元素。

  • pair.second 是一个迭代器,指向最后一个键等于指定键的元素的下一个位置。

如果找不到任何键等于指定键的元素,pair.firstpair.second 都会等于 multimap.end()

以下是一个使用 std::multimap::equal_range 的示例:

#include <iostream>
#include <map>
#include <string>

int main() {
    // 创建一个 std::multimap
    std::multimap<std::string, int> studentGrades = {
        {"Alice", 85},
        {"Bob", 90},
        {"Alice", 92},
        {"Charlie", 78},
        {"Bob", 88}
    };

    // 查找所有键为 "Alice" 的元素
    auto range = studentGrades.equal_range("Alice");

    // 遍历范围内的所有元素
    for (auto it = range.first; it != range.second; ++it) {
        std::cout << it->first << ": " << it->second << std::endl;
    }

    return 0;
}
输出
Alice: 85
Alice: 92

进阶

1. 自定义排序规则

struct CaseInsensitiveCompare {
    bool operator()(const string& a, const string& b) const {
        return strcasecmp(a.c_str(), b.c_str()) < 0;
    }
};

set<string, CaseInsensitiveCompare> caseInsensitiveSet;

2. 范围查询

auto low = s.lower_bound(3);  // 第一个>=3的元素
auto high = s.upper_bound(8); // 第一个>8的元素
myset.erase(low, high)//[)左闭右开

3. 高效删除

// 删除所有偶数
for (auto it = s.begin(); it != s.end(); ) {
    if (*it % 2 == 0) {
        it = s.erase(it);  // C++11起返回下一元素的迭代器
    } else {
        ++it;
    }
}

OJ练习

692. 前K个高频单词 - 力扣(LeetCode)

统计次数+Top K(优先级队列)

 注意:此时的代码不能解决当有两个单词出现相同的次数,顺序会出错。

class Solution {
public:
    //写一个仿函数
    struct kvCom
    {
        bool operator()(const pair<string, int>& kv1, const pair<string, int>& kv2)
        {
            return kv1.second > kv2.second;
        }
    };


    vector<string> topKFrequent(vector<string>& words, int k) {
        //统计次数,此时单词已经按字典序排序
        map<string, int> countMap;
        for(auto& e : words)
        {
            countMap[e]++;
        }

        vector<pair<string, int>> v(countMap.begin(), countMap.end());
        //pair支持比较大小
        sort(v.begin(), v.end(), kvCom());
        //取前k个
        vector<string> vRet;
        for(size_t i = 0; i < k; ++i)
        {
            vRet.push_back(v[i].first);
        }
        return vRet;
    }
};

失败用例:

解决办法1:使用一个稳定的排序就能解决

如何理解稳定的排序?这是打印出的最后sort完的排序

1.首先map排完之后会得到一个按照原数组单词的比较后排序的一个序列:类似于这样

2.如果是稳定的,那么就会保证相同的值,相对顺序依旧不变

3.稳定的排序

stable_sort 是 C++ 标准库中提供的一种稳定排序算法,其核心特点是保持相等元素的原有相对顺序。

实现原理
  • 归并排序:多数实现(如 GCC 的 libstdc++)在内存充足时使用归并排序。

  • 自适应策略:小数据量可能使用插入排序(稳定且对缓存友好),大数据量切换为归并排序。

  • 内存不足时:可能退化为原地归并排序或其他稳定算法,但时间复杂度增加。

stable_sort与 sort 的区别
特性stable_sortsort
稳定性稳定(保持相等元素顺序)不稳定(可能改变顺序)
时间复杂度O(n log n) 或 O(n log² n)O(n log n)
空间复杂度O(n)O(log n)(递归栈)
适用迭代器仅随机访问迭代器仅随机访问迭代器

4.将原来的sort --> stable_sort

        stable_sort(v.begin(), v.end(), kvCom());

解决办法2:仿函数的返回值,次数大的在前面,次数相等的,字典序小的在前面

 struct kvCom
    {
        bool operator()(const pair<string, int>& kv1, const pair<string, int>& kv2)
        {
            return kv1.second > kv2.second;
        }
    };

原来是次数大的在前面,现在添加 次数相等的,字典序小的在前面

    //写一个仿函数
    struct kvCom
    {
        bool operator()(const pair<string, int>& kv1, const pair<string, int>& kv2)
        {
            //次数大的在前面,次数相等的,字典序小的在前面
            return kv1.second > kv2.second 
            || (kv1.second == kv2.second && kv1.first < kv2.first);
        }
    };

结语:

       随着这篇关于题目解析的博客接近尾声,我衷心希望我所分享的内容能为你带来一些启发和帮助。学习和理解的过程往往充满挑战,但正是这些挑战让我们不断成长和进步。我在准备这篇文章时,也深刻体会到了学习与分享的乐趣。

   

         在此,我要特别感谢每一位阅读到这里的你。是你的关注和支持,给予了我持续写作和分享的动力。我深知,无论我在某个领域有多少见解,都离不开大家的鼓励与指正。因此,如果你在阅读过程中有任何疑问、建议或是发现了文章中的不足之处,都欢迎你慷慨赐教。               

        你的每一条反馈都是我前进路上的宝贵财富。同时,我也非常期待能够得到你的点赞、收藏,关注这将是对我莫大的支持和鼓励。当然,我更期待的是能够持续为你带来有价值的内容,让我们在知识的道路上共同前行。

相关文章:

  • ABAP RANGE表 OPTION 运算符 SIGN
  • 无人机数据链技术及运行方式详解!
  • python生成并绘制各种类型声音噪声
  • xfreerdp 的使用
  • Spring的 init-method, @PostConstruct, InitializingBean 对比
  • 【鸿蒙5.0】两个数组,点击事件两个数组数据进行了双向数据交换,双向绑定的原理是什么?
  • JVM——模型分析、回收机制
  • 学透Spring Boot — 007. 加载外部配置
  • 【蓝桥杯14天冲刺课题单】Day 8
  • MQTT 服务器(emqx)搭建及使用(二)
  • 【原创】使用Golang和wails开发桌面程序初探
  • 基于CT成像的肿瘤图像分类:方法与实现
  • 多级限流防止 Redis 被打爆
  • Mysql-DQL
  • Docker学习--本地镜像管理相关命令--docker rmi 命令
  • bert自然语言处理框架
  • Senseglove:在虚拟现实训练中融入真实触感
  • day19 学习笔记
  • Electron崩溃问题排查指南
  • Redis 的缓存雪崩、击穿、穿透及其解决办法
  • 中国人保一季度业绩“分化”:财险净利增超92%,寿险增收不增利
  • 运动健康|不同能力跑者,跑步前后营养补给差别这么大?
  • 众信旅游:去年盈利1.06亿元,同比增长228.18%
  • 西班牙葡萄牙遭遇史上最严重停电:交通瘫了,通信崩了,民众疯抢物资
  • 北汽蓝谷一季度净亏损9.5亿元,拟定增募资不超60亿元
  • 十四届全国人大常委会第十五次会议继续审议民营经济促进法草案