第21课-C++[set和map学习和使用]

🌼引言

C++ 标准模板库(STL)中的 set map 是两种非常实用的关联式容器。它们具备快速查找、有序存储的特点,因而在很多需要高效数据管理的场景中被广泛应用。本文将深入讲解 setmap 的用法,并通过实际例子分析如何将它们应用到编程问题中。

一、关联式容器的基础知识

1.1 什么是关联式容器?

在 STL 中,容器分为两大类:序列式容器关联式容器

  • 序列式容器:包括 vectordequelist 等,它们的特点是按元素插入的顺序存储数据,并提供快速的随机访问能力。然而,在这些容器中查找某个特定值的效率较低(线性时间复杂度 O(n)),不适合频繁查找的情况。
  • 关联式容器:包括 setmapmultisetmultimap 等,它们采用键值对(key-value)的形式存储数据,可以通过键快速找到相应的值。这类容器的查找、插入和删除操作的时间复杂度较低(O(log n)),且存储的元素自动按键排序。

关联式容器的特点如下:

  • 高效查找setmap 可以在 O(log n) 的时间内查找、插入或删除数据。
  • 自动排序:存储的元素按键自动排序,默认是升序,可以通过自定义比较函数实现降序排序。
  • 键值对存储:对于 mapmultimap,每个元素都以键值对的形式存储,即每个键对应一个值。

1.2 键值对概念

mapmultimap 是基于键值对(key-value pair)结构的关联式容器。键值对是一种用于表示一一对应关系的数据结构。在键值对中,key 表示键,value 表示值。通过 key 可以快速定位到 value,这种设计非常适合用于数据查找和映射。

1.在 C++ 中,标准库提供了 pair 结构用于实现键值对。例如

pair<string, int> p("Alice", 90); 
cout << "Name: " << p.first << ", Score: " << p.second << endl;

 pair 中的 first 表示 键值,second 则表示 实值,在给 关联式容器 中插入数据时,可以构建 pair 对象。

2.利用函数实现键值对:

make_pair 是一个方便的工具函数,用于创建 pair 对象。通过它,你可以简洁地创建键值对、返回多个值以及处理成对的数据。在 STL 容器(如 mapmultimap)中使用 make_pair,不仅使代码简洁,而且提高了代码的可读性

#include <iostream>
#include <vector>
#include <algorithm>
#include <utility>

int main() {
    std::vector<std::pair<int, std::string>> items;

    // 使用 make_pair 添加元素
    items.push_back(std::make_pair(3, "Apple"));
    items.push_back(std::make_pair(1, "Banana"));
    items.push_back(std::make_pair(2, "Cherry"));

    // 按第一个元素排序
    std::sort(items.begin(), items.end());

    for (const auto& item : items) {
        std::cout << item.second << " ";
    }
    std::cout << std::endl;

    return 0;
}

1.3、树型结构的关联式容器

所以在 C++ 标准中,共提供了四种 树型结构的关联式容器

set

multiset

map

multimap

关于 哈希结构的关联式容器 将在 哈希表 中学习

树型结构与哈希结构的关联式容器功能都是一模一样的,不过 哈希结构查找比树型结构快得多 -> O(1)

注: STL 中选择的树型结构为 红黑树 RB-Tree 树型结构中的元素 中序遍历 后有序,而哈希结构中的元素无序

二、set 容器详解

set 是 C++ STL 中的一个集合容器,用于存储不重复的元素,且所有元素是有序存储的。它适合需要唯一性和快速查找的场景。

2.1 set 的特点  

排序+去重

  • 唯一性set 中的每个元素都是唯一的,不能重复,重复元素不插入
  • 自动排序set 会自动对元素进行排序,默认情况下按升序排列。
  • 无键值对set 只存储键值,数据即键值,不存在 key-value 对。

2.2 set 的常用操作

操作作用
insert插入一个元素
erase删除一个元素
find查找一个元素,返回其迭代器
count统计元素个数,set 中最多为 1
empty判断 set 是否为空
size返回 set 中元素的个数
clear清空 set 中的所有元素
beginend返回迭代器,支持遍历

 代码演示:

#include <iostream>
#include <vector>
#include <set>
using namespace std;

int main()
{
	vector<int> arr = { 7,3,6,9,3,1,6,2 };
	set<int> s1(arr.begin(), arr.end());

	//迭代器遍历
	cout << "迭代器遍历结果: ";
	set<int>::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	//判空、求大小
	cout << "===================" << endl;
	cout << "empty(): " << s1.empty() << endl;
	cout << "size(): " << s1.size() << endl;
	cout << "max_size(): " << s1.max_size() << endl;

	//插入元素
	cout << "===================" << endl;
	cout << "insert(5): ";
	s1.insert(5);
	for (auto e : s1) cout << e << " ";
	cout << endl;

	//删除元素
	cout << "===================" << endl;
	cout << "erase(6): ";
	s1.erase(6);
	for (auto e : s1) cout << e << " ";
	cout << endl;

	//交换、查找、清理
	cout << "===================" << endl;
	set<int> s2(arr.begin() + 5, arr.end());
	s1.swap(s2);
	cout << "s1: ";
	for (auto e : s1) cout << e << " ";
	cout << endl;

	cout << "s2: ";
	for (auto e : s2) cout << e << " ";
	cout << endl;

	cout << "s1.find(9): ";
	cout << (s1.find(9) != s1.end()) << endl;

	cout << "s2.clear(): " << endl;
	s2.clear();

	cout << "s1: ";
	for (auto e : s1) cout << e << " ";
	cout << endl;

	cout << "s2: ";
	for (auto e : s2) cout << e << " ";
	cout << endl;

	return 0;
}

至于 count 也可以用来查找元素是否存在,对于 set 来说,键值 key 就是 实值 value,并且因为不允许冗余,所以只有一个 键值,count 统计 键值 数量就相当于 查找 。

 

#include <iostream>
#include <vector>
#include <set>
using namespace std;


int main()
{
	vector<int> arr = { 7,3,6,9,3,1,6,2 };
	set<int> s1(arr.begin(), arr.end());

	for (int i = 0; i < 10; i++)
	{
		if (s1.count(i))
			cout << i << " 在 set 中" << endl;
		else
			cout << i << " 不在 set 中" << endl;
	}

	return 0;
}

2.3 set 的基本用法

示例:创建和插入数据

以下是一个创建 set 的例子,并展示如何插入和删除数据。

#include <iostream>
#include <set>
using namespace std;

int main() {
    set<int> s;  // 创建一个空的 set

    // 插入元素
    s.insert(10);
    s.insert(20);
    s.insert(10);  // 重复插入 10,不会被插入

    // 输出 set 中的元素
    for (int e : s) {
        cout << e << " ";
    }
    cout << endl;

    return 0;
}

 

示例:查找元素

find 可以用于查找元素是否存在于 set 中。如果存在,返回该元素的迭代器;否则,返回 end 迭代器。

int main() {
    set<int> s = {1, 2, 3, 4, 5};

    // 查找元素 3
    auto it = s.find(3);
    if (it != s.end()) {
        cout << "Element 3 found." << endl;
    } else {
        cout << "Element 3 not found." << endl;
    }

    return 0;
}

set的特点:

2.4 multiset 容器

multisetset 的一个变体multiset 和 set 的操作没什么区别,一模一样,允许存储重复的元素。multiset 的操作与 set 基本相同,只是它允许插入重复元素。示例如下:

#include <iostream>
#include <set>
using namespace std;

int main() {
    multiset<int> ms;
    ms.insert(5);
    ms.insert(5);
    ms.insert(3);

    for (int e : ms) {
        cout << e << " ";
    }
    cout << endl;

    return 0;
}

 

 值得一提的是,当在 multiset 中查找冗余的数据时,返回的是 中序遍历中,第一次出现的元素。

#include <iostream>
#include <vector>
#include <set>
using namespace std;

int main()
{
	vector<int> arr = { 3,5,3,4,5,9,2,3 };
	multiset<int> ms1(arr.begin(), arr.end());

	auto it = ms1.begin();
	while (it != ms1.end())
	{
		cout << *it << " | " << &*(it) << endl;
		++it;
	}

	cout << "================" << endl;

	cout << "ms1.find(3): " << &*(ms1.find(3)) << endl;

	return 0;
}

 

所以,multiset 才是真正的排序,set 则是去重 + 排序

统计 键值 数 count 在 multiset 中可以发挥真正效果。

#include <iostream>
#include <vector>
#include <set>
using namespace std;


int main()
{
	vector<int> arr = { 3,5,3,4,5,9,2,3 };
	multiset<int> ms1(arr.begin(), arr.end());

	for (int i = 0; i < 10; i++)
		cout << i << "在 multiset 中的数量: " << ms1.count(i) << endl;

	return 0;
}


三、map 容器详解

map 是 C++ STL 中的一种键值对关联式容器,每个元素包含一个键和值。map 中的键是唯一的,不能重复;每个键都映射到一个对应的值。map 在很多场景中被用作哈希表来存储键值对数据。

3.1 map 的特点

  • 唯一键map 中的每个键是唯一的。
  • 键值对存储map 存储的是键值对,即 key-value
  • 按键自动排序map 会自动对键进行排序,默认按升序排列。
  • 可以通过键访问值:可以通过 operator[] 运算符直接访问键对应的值。

3.2 map 的常用操作

操作作用
insert插入一个键值对
erase删除指定键的键值对
find查找指定键,返回其迭代器
count统计键的数量(map 中最多为 1)
operator[]根据键访问值,如果键不存在则自动插入新键
empty判断 map 是否为空
size返回 map 中键值对的个数
clear清空 map 中的所有键值对

map 的定义和常用操作

map 位于 <map> 头文件中,定义方式如下

#include <map>
#include <iostream>
using namespace std;

int main() {
    map<string, int> age; // 创建一个 map,其中键是 string,值是 int

    // 插入键值对
    age["Alice"] = 25;
    age["Bob"] = 30;

    // 查找键值对
    if (age.find("Alice") != age.end()) {
        cout << "Alice's age: " << age["Alice"] << endl;
    }

    // 遍历 map
    for (const auto& entry : age) {
        cout << entry.first << ": " << entry.second << endl;
    }

    return 0;
}

map 的基本用法示例

1. 插入和访问键值对

#include <map>
#include <iostream>
using namespace std;

int main() {
    map<string, int> scores;

    // 插入键值对
    scores.insert(make_pair("Alice", 90));
    scores.insert(make_pair("Bob", 85));
    
    // 也可以通过 operator[] 插入和访问
    scores["Charlie"] = 95;

    // 访问值
    cout << "Alice's score: " << scores["Alice"] << endl;

    return 0;
}

2. 查找和删除键值对

#include <map>
#include <iostream>
using namespace std;

int main() {
    map<string, int> scores = {{"Alice", 90}, {"Bob", 85}, {"Charlie", 95}};

    // 查找键 "Bob"
    auto it = scores.find("Bob");
    if (it != scores.end()) {
        cout << "Found Bob's score: " << it->second << endl;
    }

    // 删除键 "Alice"
    scores.erase("Alice");

    // 输出所有键值对
    for (const auto& entry : scores) {
        cout << entry.first << ": " << entry.second << endl;
    }

    return 0;
}

3.3 map 的特点与性能

  1. 自动排序map 中的元素按键自动排序,因此每次插入键值对后,map 都会根据键进行重新排序。
  2. 唯一性map 中不允许重复键,如果试图插入相同键的元素,将会被忽略。
  3. 高效查找map 的查找、插入和删除的时间复杂度为 O(log n),适合需要快速查找的场景。
  4. 默认初始化:使用 operator[] 时,如果键不存在,则会创建一个默认值的键值对。

注意事项

  • map[key] 如果访问的键不存在,则会自动创建该键,并给其赋一个默认值(如 int 的默认值是 0)。使用 find 可以避免这种不必要的插入。
  • map 中的数据是按键有序的,因此 map 不支持像 unordered_map 那样的 O(1) 查找

map 的应用场景

  1. 词频统计:可以使用 map 来统计字符串中每个字符的出现频率。
  2. 缓存机制:可以通过 map 来实现简单的缓存机制,用于存储经常使用的键值对。
  3. 字典实现map 是存储字典(如配置文件、键值对映射等)的一种常用数据结构。

总计:

总结

map 是一个非常强大的数据结构,可以存储唯一的键值对并按键排序。它在很多场景中都非常实用,尤其适合用于需要快速查找和唯一键的情况。在使用时需要注意 map[key] 会默认初始化键值对的问题,可以通过 find 函数来避免不必要的插入。

 四、前K个高频单词

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

题目分析:题目很短,就是在一个字符串数组中,找出前 k 个出现频率最高的单词

注意: 如果出现次数相同,则按字典序排序。(相同次数小写排前面)。

解法:map + 快排

利用 map 建立 <string, int> 的映射关系,在按照字典序排序的同时统计出每个单词的出现频率,再通过快排依照数量进行二次排序,选择前 k 个高频单词即可

因为基础版快排 不稳定,可能会导致频率相同的单词顺序出问题,即违背题目要求:如果出现频率相同,则按字典序排序

所以这里需要使用 稳定版快排 stable_sort,如果频率相同,保持原有顺序

//map + stable_sort
class Solution {
public:
    struct Compare
    {
        bool operator()(const pair<string, int>& kv1, const pair<string, int>& kv2) const
        {
            return kv1.second > kv2.second;
        }
    };

    vector<string> topKFrequent(vector<string>& words, int k) {
        //统计每个单词出现的频率,同时先按照字典序排序
        map<string, int> table;
        for(auto e : words)
            table[e]++;

        //将处理好的数据存入数组中
        vector<pair<string, int>> vTable(table.begin(), table.end());

        //按照出现频率进行二次排序
        stable_sort(vTable.begin(), vTable.end(), Compare());

        //取出前 k 个高频单词
        vector<string> vs;
        for(int i = 0; i < k; i++)
            vs.push_back(vTable[i].first);
        
        return vs;
    }
};

LCR 154. 复杂链表的复制 - 力扣(LeetCode)

五、补充:交集与差集

交集,指两个数组中相同的元素所构成的集合

求交集的步骤如下:

  1. 先将两个数组 排序 + 去重
  2. 遍历两个数组
  3. 如果不相等,小的 ++
  4. 相等就是交集,记录下来
  5. 其中一方走完,所有交集就查找完了

排序 + 去重,这就不就是 set 吗?

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

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
      set<int> set1(nums1.begin(),nums1.end());
       set<int> set2(nums2.begin(),nums2.end());

       
       vector<int> num1(set1.begin(),set1.end());
       vector<int> num2(set2.begin(),set2.end());
        int i=0,j=0;
        vector<int> v;

        while(i<num1.size()&&j<num2.size())
        {
            if(num1[i]==num2[j])
            {
                v.push_back(num1[i]);
            }
            else if(num1[i]<num2[j])
            {
                i++;
            }
            else{
                j++;
            }

        }
        return v;
    }
};

 总结:

容器特点适用场景
set唯一元素,有序存储去重,查找唯一元素
multiset允许重复元素,有序存储频率统计,处理重复数据
map键值对存储,唯一键,有序频率统计,字典查询
multimap键值对存储,允许重复键,有序频率统计,管理多值的数据

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

视频流媒体播放器EasyPlayer.js RTSP播放器视频颜色变灰色/渲染发绿的原因分析

EasyPlayer.js RTSP播放器属于一款高效、精炼、稳定且免费的流媒体播放器&#xff0c;可支持多种流媒体协议播放&#xff0c;无须安装任何插件&#xff0c;起播快、延迟低、兼容性强&#xff0c;使用非常便捷。 EasyPlayer.js播放器不仅支持H.264与H.265视频编码格式&#xff0…

(一)- DRM架构

一&#xff0c;DRM简介 linux内核中包含两类图形显示设备驱动框架&#xff1a; FB设备&#xff1a;Framebuffer图形显示框架; DRM&#xff1a;直接渲染管理器&#xff08;Direct Rendering Manager&#xff09;&#xff0c;是linux目前主流的图形显示框架&#xff1b; 1&am…

远程控制步骤

当远在千里之外的朋友想求助你帮他找到他电脑上的文件、或者是给他安装软件时。但是你给他说了他又找不到&#xff0c;那么这时你就可以通过控制对方的电脑去做一系列的操作。 如何远程控制对方的电脑非常关键。 方法一&#xff08;Windows自带远程桌面功能&#xff09;&#…

InternVL 多模态模型部署微调实践 | 书生大模型

文章目录 多模态大模型简介基本介绍例子常见设计模式BLIP 2Q-Former 模块细节应用案例&#xff1a;MiniGPT - 4Q-Former 的缺点 LLaVALLaVA - 1.5 - HDLLaVA - Next InternVL2 介绍架构设计Intern VitPixel ShuffleDynamic High - ResolutionMultitask output 训练方法 环境配置…

javaScript交互补充(元素的三大系列)

1、元素的三大系列 1.1、offset系列 1.1.1、offset初相识 使用offset系列相关属性可以动态的得到该元素的位置&#xff08;偏移&#xff09;、大小等 获得元素距离带有定位祖先元素的位置获得元素自身的大小&#xff08;宽度高度&#xff09;注意&#xff1a;返回的数值都不…

AI大模型(二):AI编程实践

一、软件安装 1. 安装 Visual Studio Code VSCode官方下载&#xff1a;Visual Studio Code - Code Editing. Redefined 根据自己的电脑系统选择相应的版本下载 安装完成&#xff01; 2. 安装Tongyi Lingma 打开VSCode&#xff0c;点击左侧菜单栏【extensions】&#xff0c;…

linux c 语言回调函数学习

动机 最近在看 IO多路复用&#xff0c;包括 select() poll () epoll() 的原理以及libevent&#xff0c; 对里面提及的回调机制 比较头大&#xff0c;特写此文用例记录学习笔记。 什么是回调函数 网上看到的最多的一句话便是&#xff1a;回调函数 就是 函数指针的一种用法&am…

Python 正则表达式的一些介绍和使用方法说明(数字、字母和数字、电子邮件地址、网址、电话号码(简单)、IPv4 )

## 正则表达式的概念和用途 正则表达式&#xff08;Regular Expression&#xff0c;简称Regex&#xff09;是对字符串操作的一种逻辑公式&#xff0c;由一些事先定义好的特定字符以及这些特定字符的组合所构成。这些特定字符及其组合被用来描述在搜索文本时要匹配的一个或多个…

DreamClear:字节跳动开源了高性能图像修复技术,中科院加持,商业免费使用

哇&#xff0c;字节跳动开源了DreamClear项目&#xff0c;采用的是Apache-2.0开源协议&#xff0c;可以商用&#xff0c;并且用户可以自由地使用、复制、修改和分发该软件&#xff0c;甚至可以用于私有项目中。这对于开发者和企业来说是个好消息&#xff0c;因为它们可以利用这…

Flutter:android studio无法运行到模拟机的问题

提示如下错误信息&#xff1a; Entrypoint is not a Dart filenot applicable for the "main.dart" configurat点击运行按钮提示让填写以下信息 或者出现无法选择模拟机的情况 发下下列问题&#xff1a; 无法运行的项目默认根目录地址&#xff1a; 可以正常运行…

FromData格式提交接口时入参被转成JSON格式问题

本地上传文件后通过事件提交文件&#xff0c;一般先通过前端组件生成文本流&#xff0c;在通过接口提交文本流&#xff0c;提交文本流一般使用FormData的入参形式传入&#xff0c;接口请求头也默认"Content-Type": “multipart/form-data”&#xff0c;但是某些场景统…

<AI 学习> 下载 Stable Diffusions via Windows OS

注意&#xff1a; 不能使用 网络路径 不再支持 HTTPS 登录&#xff0c;需要 Token 1. 获得合法的授权 Stability AI License — Stability AI 上面的链接打开&#xff0c;去申请 许可 2. 拥有 HuggingFace 账号 注册&#xff1a;https://huggingface.co/ 3. 配置 Tok…

MySQL缓存使用率超过80%的解决方法

MySQL缓存使用率超过80%的解决方法 一、识别缓存使用率过高的问题1.1 使用SHOW GLOBAL STATUS命令监控1.2 监控其他相关指标二、分析缓存使用率过高的原因2.1 数据量增长2.2 查询模式变化2.3 配置不当三、解决缓存使用率过高的方法3.1 调整Buffer Pool大小3.1.1 计算合理的Buff…

39.安卓逆向-壳-smali语法3(方法)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;图灵Python学院 本人写的内容纯属胡编乱造&#xff0c;全都是合成造假&#xff0c;仅仅只是为了娱乐&#xff0c;请不要盲目相信。 工…

《FreeRTOS任务基础知识以及任务创建相关函数》

目录 1.FreeRTOS多任务系统与传统单片机单任务系统的区别 2.FreeRTOS中的任务&#xff08;Task&#xff09;介绍 2.1 任务特性 2.2 FreeRTOS中的任务状态 2.3 FreeRTOS中的任务优先级 2.4 在任务函数中退出 2.5 任务控制块和任务堆栈 2.5.1 任务控制块 2.5.2 任务堆栈…

RHCE的学习(18)

第二章 变量和引用 深入认识变量 在程序设计语言中&#xff0c;变量是一个非常重要的概念。也是初学者在进行Shell程序设计之前必须掌握的一个非常基础的概念。只有理解变量的使用方法&#xff0c;才能设计出良好的程序。本节将介绍Shell中变量的相关知识。 什么是变量 顾名思义…

AG32 FPGA部分简单开发

环境 Quartus 13.0&#xff08;Quartus 不能使用Lite 版本&#xff0c;需要使用Full 版本&#xff09;AGM SDKSupra&#xff08;快捷方式在SDK目录下&#xff0c;具体路径为AgRV_pio\packages\tool-agrv_logic\bin&#xff09; FPGA编程 在AG32芯片中&#xff0c;拥有异构双…

__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ is not explicitly defined

VUE_PROD_HYDRATION_MISMATCH_DETAILS 未明确定义。您正在运行 Vue 的 esm-bundler 构建&#xff0c;它期望这些编译时功能标志通过捆绑器配置全局注入&#xff0c;以便在生产捆绑包中获得更好的tree-shaking优化。 Vue.js应用程序正在使用ESM&#xff08;ECMAScript模块&#…

Spring——事务

事务 JdbcTemplate 简介 Spring框架对JDBC进行封装&#xff0c;使用JdbcTemplate方便实现对数据库操作 准备工作 ①搭建子模块 搭建子模块&#xff1a;spring-jdbc-tx ②加入依赖 <dependencies><!--spring jdbc Spring 持久化层支持jar包--><dependenc…

集合类源码浅析のJDK1.8ConcurrentHashMap(下篇)

文章目录 前言一、分段扩容1、addCount2、transfer3、helpTransfer 二、查询二、删除总结 前言 主要记录ConcurrentHashMap&#xff08;笔记中简称CHM&#xff09;的查询&#xff0c;删除&#xff0c;以及扩容方法的关键源码分析。 一、分段扩容 1、addCount 扩容的逻辑主要在…