深入理解C++关联式容器:set、multiset、map和multimap详解

序列式容器 与 关联式容器

我们知道:

  • C++ 中,我们将 vector、list、queue 这种底层为线性序列的数据结构叫做 序列式容器,其存储的就是元素本身
  • 关联式容器键-值对的形式存储数据。每个键在容器中必须是唯一的,而值则与相应的键关联。

键-值对

<key,value> 结构,在C++标准库中,std::pair 是一个模板类,其存储的就是键值对。其定义如下:

namespace std {template <class T1, class T2>struct pair {typedef T1 first_type;typedef T2 second_type;T1 first;T2 second;pair();pair(const T1& x, const T2& y);template<class U, class V> pair(const pair<U, V>& p);};
}

一般键是唯一且不可重复的标识符,用于查找和访问对应的值。值可以是任何数据类型,包括整数、字符串、对象等


树形结构的关联式容器

C++ 中,标准库提供了 std::map 、std::set 、std::multimap、std::multimap 四个基于红黑树实现的关联式容器。下面依次介绍其性质与使用:


std::set

Cplusplus 官网set文档

性质

上面的cpp文档详细的介绍了set这个容器,下面我们对其性质进行总结概括:

  1. 有序性set 中的元素按照其key值自动被排序。默认情况下使用元素的 < 操作符(小于)比较,也可以通过自定义比较函数来指定排序规则。插入 / 删除 新元素时,set 会自动确保元素的有序性,并且不会插入重复的元素。

  2. 唯一性:对于set,元素的值(value)即是键(key),而且每个value必须是唯一的。

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

  4. 底层实现:通常基于红黑树实现,因此插入、删除和查找操作的平均时间复杂度都是 O(log n)。

  5. 迭代器支持:set 提供了正向迭代器,可以用于遍历集合中的元素。

Attention:

  • set在创建时为std::set<value> myset;,只存放value,其底层上存放的是<value, value> 的键值对。实际实现中,只存储了键,而值部分为空。
  • 我们知道set中的元素不可直接修改,为什么?
    • 由于 std::set 是一种基于红黑树实现的关联容器,它要求元素在容器中保持有序性和唯一性。因此,直接修改元素可能会破坏红黑树的结构,导致容器不再符合要求。

使用

模板参数列表

下面是C++标准库中的 set 模板参数列表

template<class Key, // Key(键)类型class Compare = std::less<Key>, // 比较函数类型class Allocator = std::allocator<Key> // 分配器类型
> class set;

分别解释:

  • Keystd::set 中存储的元素的类型。set 中的元素按照键值自动被排序,并且每个元素在中都是唯一的
  • Compare可选的比较函数类型,用于指定元素的比较规则。默认情况下,使用 < 操作符进行比较。
  • 可选的分配器类型用于分配内存和管理容器中的元素

应用

数据去重std::set会自动对元素进行排序并移除重复的元素

std::vector<int> numbers = {4, 1, 2, 2, 3, 4};
std::set<int> uniqueNumbers(numbers.begin(), numbers.end());
// 现在uniqueNumbers中包含的是去重后的元素{1, 2, 3, 4}

快速查找std::set内部基于红黑树实现,因此查找操作非常高效,时间复杂度为O(log n)

std::set<std::string> names = {"Alice", "Bob", "Charlie", "David"};
if (names.find("Bob") != names.end())  // 查找Bob
{std::cout << "Bob 在set中" << std::endl;
}

迭代器的使用可以通过迭代器来访问集合中的元素

#include <iostream>
#include <set>int main() {std::set<int> mySet = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};// 使用迭代器遍历 set 中的所有元素for (auto it = mySet.begin(); it != mySet.end(); ++it) {std::cout << *it << " ";}std::cout << std::endl;// 反向迭代器for (auto rit = mySet.rbegin(); rit != mySet.rend(); ++rit) {std::cout << *rit << " ";}std::cout << std::endl;// 使用迭代器查找特定元素int target = 5;auto findIt = mySet.find(target);if (findIt != mySet.end()) {std::cout << target << "找到了" << std::endl;} else {std::cout << target << "没找到" << std::endl;}return 0;
}

std::map

cplusplus 官网文档

性质

上面的cpp文档详细的介绍了map这个容器,下面我们对其性质进行总结概括:

  1. 有序性map 中的元素按照key的大小顺序进行排序,默认情况下采用升序方式。这使得在 map 中进行范围遍历时,能够以键的顺序访问元素。

  2. 唯一键map 中的键是唯一的,如果插入一个已经存在的键,则会覆盖原有的键对应的值。

  3. 查找效率:由于 map 基于红黑树实现,对于元素的查找、插入和删除操作具有较高的效率,时间复杂度为 O(log n)。

  4. 键-值对映射:map 提供了键和值之间的映射关系,通过键可以快速查找对应的值,这使得 map 适合用于需要快速查找和检索值的场景。

Attention:

  • map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。

模板参数列表

template <class Key, class T, class Compare = std::less<Key>, class Allocator = std::allocator<std::pair<const Key, T>>>
class map;

这里是对每个模板参数的解释:

  • Key键的类型,用于唯一标识每个元素。

  • T值的类型,与键相关联的数据类型。

  • Compare可选参数,用于比较键的函数对象类型,默认情况下采用 std::less<Key>,表示采用 < 操作符进行键的比较。

  • Allocator可选参数,内存分配器的类型,默认情况下采用 std::allocator<std::pair<const Key, T>>,表示使用标准分配器

应用

我们下面用map进行一些应用:字典、计数器

字典

#include <iostream>
#include <map>
#include <string>int main() {// 创建字典std::map<std::string, std::string> dictionary;// 添加词条到字典dictionary["apple"] = "苹果";dictionary["banana"] = "香蕉";dictionary["cat"] = "猫";// 查找词条std::string word = "";getline(word);if (dictionary.find(word) != dictionary.end()) {std::cout << word << ": " << dictionary[word] << std::endl;} else {std::cout << "字典中未查到该词" << std::endl;}return 0;
}

上面的代码中,我们使用map<string, string> 来模拟字典,key,value分别对应一个单词的英文和中文含义。

计数器

#include <iostream>
#include <map>int main() {std::map<int, int> counter;// 统计数字出现的次数int numbers[] = {5, 3, 7, 5, 3, 9, 5};for (int num : numbers) {counter[num]++;}// 输出数字及其出现的次数for (const auto& pair : counter) {std::cout << pair.first << " occurs " << pair.second << " times" << std::endl;}return 0;
}

计数器通过将value值设为int类型,可以统计值为key的元素的出现个数。


std::multiset

cpluscplus官网文档

性质

multiset 的 模板参数列表 与 set 是相同的,但在性质上有所差别。

这里我们先看 多重集(Multiset)和集合(Set)之间 的区别

  1. 元素重复性multiset 允许元素重复出现,而 set 中每个元素都是唯一的。

  2. 插入操作:在 set 中,插入已经存在的元素会被忽略,因为元素不能重复;而在 multiset 中,可以插入重复的元素,每个元素将会被单独计数

  3. 查找操作:在 set 中,由于元素唯一,查找特定元素的速度较快;而在 multiset 中,由于元素可能重复,查找特定元素需要遍历多个相同元素

  4. 应用场景:set 适合用于需要保持元素唯一性的场景,比如存储不重复的关键字;而 multiset 则适用于需要统计元素重复次数的场景,比如统计数据集中各个元素的出现频率。

使用

当有以下情况时,我们会使用 multiset 而不是set:

  1. 需要存储重复元素的情况
  2. 不需要维护元素的唯一性
  3. 需要快速查找和删除重复元素

下面的代码展示了,multiset的使用:

int main() {std::multiset<int> numbers;// 插入元素numbers.insert(10);numbers.insert(20);numbers.insert(30);numbers.insert(20);  // 插入重复的元素// 输出元素std::cout << "该multiset包含: ";for (const auto& num : numbers) {std::cout << " " << num;}std::cout << std::endl;// 统计特定元素的重复次数int target = 20;int count = numbers.count(target);printf("值为%d的元素共有%d个\n", target, count);// 删除指定元素numbers.erase(target);// 再次输出元素printf("删除元素%d后,multiset共包含:", target);for (const auto& num : numbers) {std::cout << " " << num;}std::cout << std::endl;return 0;
}

代码输出结果:

在这里插入图片描述


std::multimap

cpluscplus官网文档

性质

这里我们主要关心 multimap 与 map 的区别(性质 及 操作):

  • 键的唯一性map中的键是唯一的,每个键只能对应一个值。而multimap允许多个键有相同的值,一个键可以对应多个值

  • 插入顺序: map按照键的大小顺序进行排序,并保持该顺序。multimap不对键进行排序,插入的顺序被保留

  • 查找操作在map中,由于键的唯一性,使用find()函数查找一个键时,如果找到了就返回该键对应的值,如果没找到则返回end()迭代器。而在multimap中,find()函数返回指向第一个匹配的键-值对的迭代器,如果没有匹配的键,则返回end()迭代器。

  • 删除操作: 在map中,使用erase()函数删除一个键时,如果该键存在,则删除该键对应的键-值对,并将其从容器中移除。而在multimap中,erase()函数会删除所有与给定键匹配的键-值对

总的来说,multimap 提供了一种允许重复键并自动排序的数据结构,适用于需要按照键进行检索且允许重复键的情况下使用。

应用

我们利用multimap 一个key可以对应多个value的特性,下面的示例使用 multimap 构建了一个学生表:

#include <iostream>
#include <map>
#include <string>int main() {std::multimap<std::string, std::string> studentCourses;// 添加学生和他们的课程studentCourses.insert(std::make_pair("Alice", "Math"));studentCourses.insert(std::make_pair("Alice", "Physics"));studentCourses.insert(std::make_pair("Bob", "Chemistry"));studentCourses.insert(std::make_pair("Bob", "Biology"));studentCourses.insert(std::make_pair("Bob", "Math"));// 输出每个学生选修的课程for (const auto& pair : studentCourses) {std::cout << pair.first << " takes " << pair.second << std::endl;}return 0;
}

输出如下:

在这里插入图片描述

结尾

上面介绍的关联式容器,在做OJ题时也会经常用上,在理解其使用场景后做题,可以很好的运用它们。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

毕业设计项目:基于java+springboot的共享单车信息网站

运行环境 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Ma…

保姆级Decimal.js的使用(如何解决js精度问题)

精度问题控制台图样 如果银行的业务你这样做&#xff0c;不知道要损失多少钱&#xff0c;这样是不行的&#xff0c;计算的不准确是需要背锅的&#xff0c;我们给后端去做吧&#xff0c;其实我们前端也是可以做的&#xff0c;引入Decimal.js 01.引入Decimal.js decimal.js是使用…

北邮22级信通院数电:Verilog-FPGA(9)第九周实验(2)实现下降沿触发的JK触发器(带异步复位和置位功能)

北邮22信通一枚~ 跟随课程进度更新北邮信通院数字系统设计的笔记、代码和文章 持续关注作者 迎接数电实验学习~ 获取更多文章&#xff0c;请访问专栏&#xff1a; 北邮22级信通院数电实验_青山如墨雨如画的博客-CSDN博客 JK.v module JK (input clk,input J,input K,input…

TCP编程及基础知识

一、端口号 为了区分一台主机接收到的数据包应该转交给哪个进程来进行处理&#xff0c;使用端口号来区分TCP端口号与UDP端口号独立端口用两个字节来表示 2byte&#xff08;65535个&#xff09; 众所周知端口&#xff1a;1~1023&#xff08;1~255之间为众所周知端口&#xff…

macOS使用conda初体会

最近在扫盲测序的一些知识 其中需要安装一些软件进行练习&#xff0c;如质控的fastqc&#xff0c;然后需要用conda来配置环境变量和安装软件。记录一下方便后续查阅学习 1.安装miniconda 由于我的电脑之前已经安装了brew&#xff0c;所以我就直接用brew安装了 brew install …

优秀智慧园区案例 - 珠海华发智慧园区,万字长文解析先进智慧园区建设方案经验

一、项目背景 珠海华发产业园运营管理有限公司&#xff08;简称“产业园公司”&#xff09;是2016年起连续五年跻身“中国企业500强”、国务院国企改革“双百企业”的珠海华发集团旗下的实体产业发展载体运营平台&#xff0c;依托“四园一基地”&#xff1a;中以国际产业园、信…

如何用postman+jmeter实现接口实例

一、接口基础 为什么要单独测试接口&#xff1f; 1. 程序是分开开发的&#xff0c;前端还没有开发&#xff0c;后端已经开发完了&#xff0c;可以提前进入测试 2. 接口直接返回的数据------越底层发现bug&#xff0c;修复成本是越低的 3. 接口测试能模拟功能测试不能测到的异…

【Java 进阶篇】JQuery DOM操作:通用属性操作的绝妙魔法

在前端的舞台上&#xff0c;JQuery犹如一位魔法师&#xff0c;为我们展现了操纵HTML元素的奇妙技巧。而在这个技巧的精妙组成中&#xff0c;通用属性操作是一门绝妙的魔法。在本篇博客中&#xff0c;我们将深入研究JQuery DOM操作中的通用属性操作&#xff0c;揭示这段魔法的神…

C++——gcc、clang和cmake以及make

文章目录 1. CMake和make1.1 生成内容区别1.2 CMakeLists.txt和Makefile内容比较2. clang, gcc和make2.1 基本概念2.2 改进历史(gcc,make,cmake,Ninja)1. CMake和make 1.1 生成内容区别 环境CMake生成最终生成WindowsXXX.slnVisual Studio(MSBuild)处理.sln生成.exe可执行二进…

【架构】后端项目经典分层架构介绍

文章目录 前言分层架构项目实践示例项目结构 其他知识 前言 开发后端项目时&#xff0c;我们最常见的一种架构模式就是分层架构 。 所谓的分层架构&#xff0c;就是把系统自上而下分为多个不同的层&#xff0c;每一层都有特定的功能和职责&#xff0c;且只和自己的直接上层与…

如何使用CORS和CSP保护前端应用程序安全

前端应用在提供无缝用户体验方面起着核心作用。在当今互联网的环境中&#xff0c;第三方集成和API的普及使得确保强大的安全性至关重要。安全漏洞可能导致数据盗窃、未经授权访问以及品牌声誉受损。本文将向您展示如何使用CORS和CSP为您的网页增加安全性。 嗨&#xff0c;大家好…

国际化:i18n

什么是国际化&#xff1f; 国际化也称作i18n&#xff0c;其来源是英文单词 internationalization的首末字符和n&#xff0c;18为中间的字符数。由于软件发行可能面向多个国家&#xff0c;对于不同国家的用户&#xff0c;软件显示不同语言的过程就是国际化。通常来讲&#xff0…