C++ 关联式容器map 与 set 的原理与实践操作

 更新时间:2025年12月01日 09:29:40   作者:思成不止于此  
本文将详细介绍关联式容器中最常用的map和set,包括它们的底层实现、核心特性、使用方法及实际应用,本文结合实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧

        在 C++ 中,容器是存放数据的重要数据结构,分为序列式容器和关联式容器。序列式容器(如 vector、list、deque)按线性顺序存储元素,元素的位置与值无关;而关联式容器则通过键(key)建立元素间的关联,实现高效的查找、插入和删除操作。本文将详细介绍关联式容器中最常用的 map 和 set,包括它们的底层实现、核心特性、使用方法及实际应用。

一、关联式容器的核心概念

1. 容器分类与特点

        关联式容器的核心是 “关联关系”,即通过键(key)快速定位元素,而无需像序列式容器那样遍历整个容器。其特点如下:

  • 元素按特定规则排序(有序容器)或无序存储(无序容器);
  • 插入位置由元素的键决定,而非用户指定;
  • 查找效率极高,平均时间复杂度为 O(logN)(有序容器)或 O(1)(无序容器)。

2. 底层实现

        有序容器(set、map 等)的底层通常采用 平衡二叉搜索树(红黑树) 实现,其特性为:

  • 左子树所有节点的值 < 根节点的值;
  • 右子树所有节点的值 > 根节点的值;
  • 树的高度保持平衡,确保查找、插入、删除操作的时间复杂度为 O(logN)。

        无序容器(unordered_set、unordered_map 等)的底层采用 哈希表 实现,通过哈希函数将键映射到存储位置,平均时间复杂度为 O(1),但最坏情况下可能退化为 O(N)。

3. 搜索模型

        关联式容器分为两种搜索模型:

  • K 模型:仅存储键(key),如 set,核心功能是判断元素是否存在;
  • KV 模型:存储键值对(key-value),如 map,核心功能是通过键查找对应的值。

二、set 的原理与使用

1. set 的核心特性

        set 是 有序、不重复 的 K 模型容器,底层为红黑树。其核心特性:

  • 自动排序:插入元素后,容器会按键的升序(默认)排列;
  • 自动去重:插入重复元素时,操作会失败,容器中仅保留一个实例;
  • 不可修改元素:set 中的元素是 const 类型,修改元素会破坏红黑树的结构,需通过 “删除旧元素 + 插入新元素” 实现。

2. set 的常用操作

(1)插入操作

        set 不支持 push_back/push_front,需使用 insert() 插入元素:

#include <set>
using namespace std;
set<int> s;
s.insert(3);   
s.insert(1);   
s.insert(3);   // 重复插入,操作失败

        插入后,set 中的元素会自动排序为 {1, 3}

(2)遍历操作

set 支持迭代器遍历和范围 for 遍历:

void test(){
    set<int> s;
    s.insert(3);
    s.insert(4);
    s.insert(1);
    s.insert(2);
    s.insert(3);
    s.insert(7);
    //排序+去重
    set<int>::iterator it = s.begin();
    while (it != s.end())
    {
	    cout << *it << " ";
	    it++;
    }
    cout << endl;
    for (auto e : s)
    {
    	cout << e << " ";
    }
    cout << endl;
}

(3)删除操作

        set 支持两种删除方式:

  • 通过迭代器删除(需先通过 find() 查找元素);
  • 直接通过值删除。
// 方式 1:通过迭代器删除
set<int>::iterator pos = s.find(7);	//log(N)
//set<int>::iterator pos = find(s.begin(), s.end(), 4); //OP(N)
if (pos != s.end())
{
	s.erase(pos);
}
// 方式 2:直接通过值删除
s.erase(1);  // 删除元素 1,若不存在则无操作

(4)查找操作

        set 的查找功能是其核心,提供两种方式:

  • 成员函数 find():利用红黑树特性,时间复杂度 O(logN);
  • 算法 std::find():线性遍历,时间复杂度 O(N)。

示例对比

#include <algorithm>  // 包含 std::find
// 成员函数 find()
set<int>::iterator pos1 = s.find(3);  // 高效查找
// 算法 find()
set<int>::iterator pos2 = find(s.begin(), s.end(), 3);  // 低效遍历

使用建议:优先使用 set 的成员函数 find() 以获得最佳性能。

3. set 的实际应用

        set 的核心优势是 快速存在性检查 和 高效去重排序,适用于以下场景:

  • 存储学号、身份证号等唯一标识,快速验证是否存在;
  • 对输入数据去重并排序,如统计考试成绩的不重复分数;
  • 实现集合运算(交集、并集、差集)。

示例:验证学号是否存在

student_ids.insert("001");
student_ids.insert("002");
student_ids.insert("003");
string id = "002";
if (student_ids.find(id) != student_ids.end()) {
    cout << "学号 " << id << " 存在" << endl;
} else {
    cout << "学号 " << id << " 不存在" << endl;
}

三、map 的原理与使用

1. map 的核心特性

        map 是 有序、键唯一 的 KV 模型容器,底层为红黑树。其核心特性:

  • 存储键值对(key-value),键(key)唯一,值(value)可重复;
  • 按键自动排序(默认升序);
  • 通过键快速查找对应的值,时间复杂度 O(logN);
  • 支持通过键修改值,但键不可修改(否则会破坏红黑树结构)。

2. map 的常用操作

(1)pair 类型介绍

        map 中的元素是 pair<const key_type, value_type> 类型,pair 是一个模板结构体,包含两个成员:

  • first:键(key),不可修改;
  • second:值(value),可修改。

创建 pair 的方式:

// 方式 1:显式指定模板参数
pair<int, string> p1(1, "张三");
// 方式 2:使用 make_pair(自动推导类型)
pair<int, string> p2 = make_pair(2, "李四");

(2)插入操作

        map 通过 insert() 插入 pair 类型元素:

#include <map>
using namespace std;
map<int, string> student_info;
// 方式 1:插入 pair 对象
student_info.insert(pair<int, string>(1, "张三"));
// 方式 2:使用 make_pair(推荐,更简洁)
student_info.insert(make_pair(2, "李四"));
// 方式 3:C++11 统一初始化
student_info.insert({3, "王五"});

        插入后,map 会按键的升序排列:{1:张三, 2:李四, 3:王五}

(3)遍历操作

        map 支持迭代器遍历和范围 for 遍历,通过 it->first 访问键,it->second 访问值:

// 迭代器遍历
map<int, string>::iterator it = student_info.begin();
while (it != student_info.end()) {
    cout << "学号:" << it->first << ",姓名:" << it->second << endl;
    ++it;
}
// 范围 for 遍历
for (auto e : student_info) {
    cout << "学号:" << e.first << ",姓名:" << e.second << endl;
}

(4)查找与修改操作

        通过键查找值有两种方式:

  • 成员函数 find():返回指向该键值对的迭代器;
  • 下标运算符 []:直接通过键访问值(若键不存在,会自动插入一个默认构造的键值对)。
// 方式 1:find() 查找(推荐,避免误插入)
map<int, string>::iterator pos = student_info.find(2);
if (pos != student_info.end()) {
    cout << "找到:" << pos->second << endl;  // 输出:李四
    pos->second = "李小四";  // 修改值
}
// 方式 2:下标访问(注意:键不存在时会自动插入)
string name = student_info[3];  // 访问键 3 的值,存在则返回 "王五"
student_info[4] = "赵六";       // 键 4 不存在,插入 {4:赵六}

(5)删除操作

        map 的删除方式与 set 类似,支持迭代器删除和键删除:

// 方式 1:通过迭代器删除
map<int, string>::iterator pos = student_info.find(2);
if (pos != student_info.end()) {
    student_info.erase(pos);
}
// 方式 2:通过键删除
student_info.erase(3);  // 删除键 3 对应的键值对

3. map 的实际应用

        map 的核心优势是 通过键快速查找值,适用于以下场景:

  • 存储键值对映射关系,如字典(单词 - 翻译)、学号 - 成绩;
  • 统计元素出现次数,如统计字符串中每个单词的出现次数;
  • 实现缓存机制(键为缓存 key,值为缓存数据)。

示例:统计字符串出现次数

string str[] = { "西瓜","圣女果", "圣女果", "西瓜", "西瓜", "香蕉", "葡萄", "葡萄", "菠萝", "西瓜", "桃子", "西瓜",  "栗子", "水蜜桃", "西瓜", "葡萄" };
map<string, int> countMap;
for (auto e : str)
{
	map<string, int>::iterator pos = countMap.find(e);
	if (pos == countMap.end())
		countMap.insert(make_pair(e, 1));
	else
		pos->second++;	
}
for (auto e : countMap)
{
	cout << e.first<<":"<<e.second<<endl;
}

四、map 与 set 的区别与联系

1. 相同点

  • 底层均为红黑树(有序容器),操作时间复杂度均为 O(logN);
  • 均支持自动排序和去重(set 去重键,map 去重键);
  • 均不支持直接修改元素(set 元素不可修改,map 键不可修改)。

2. 不同点

特性setmap
存储类型仅键(K 模型)键值对(KV 模型)
核心功能快速存在性检查快速键值映射查找
元素访问仅访问键访问键和值
修改方式不可修改,需删插可修改值,键不可改

五、使用注意事项

  1. 元素不可修改:set 的元素和 map 的键均为 const 类型,修改会破坏红黑树结构,需通过 “删插” 实现;
  2. 迭代器有效性:插入元素时,红黑树可能重新平衡,迭代器不会失效;删除元素时,仅被删除元素的迭代器失效,其他迭代器有效;
  3. 比较规则:默认按键的升序排序,若需自定义排序,可在定义容器时指定比较函数(如 set<int, greater<int>> 按降序排序);
  4. 效率选择
    • 需有序存储且高效查找时,使用 set/map;
    • 无需排序且追求极致查找效率时,使用 unordered_set/unordered_map(哈希表实现);
    • 仅需线性存储时,使用 vector/list 等序列式容器。

六、总结

        map 和 set 是 C++ 中最常用的关联式容器,其核心优势在于 高效的查找、插入和删除操作,底层依赖红黑树实现有序存储和去重。set 专注于 “键的存在性检查”,map 专注于 “键值对的映射查找”,二者在实际开发中应用广泛,如数据去重、统计计数、字典映射等场景。

        掌握 map 和 set 的使用,需理解其底层实现原理和核心特性,根据实际需求选择合适的容器,以优化程序性能。

到此这篇关于C++ 关联式容器map 与 set 的原理与实践操作的文章就介绍到这了,更多相关c++ map和set原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C++ POSIX API超详细分析

    C++ POSIX API超详细分析

    这篇文章主要介绍了C++ POSIXAPI的使用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2022-11-11
  • C++ 实现双向链表的实例

    C++ 实现双向链表的实例

    这篇文章主要介绍了C++ 实现双向链表的实例的相关资料,需要的朋友可以参考下
    2017-07-07
  • Pthread 并发编程线程自底向上深入解析

    Pthread 并发编程线程自底向上深入解析

    这篇文章主要为大家介绍了Pthread 并发编程线程自底向上深入解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-11-11
  • C++快速调用DeepSeek API的完整指南

    C++快速调用DeepSeek API的完整指南

    最近,DeepSeek的API引起了我的兴趣,它提供了强大的对话生成能力,可以用于多种应用场景,虽然DeepSeek官方提供了详细的API文档,但遗憾的是,目前没有专门针对C++的调用示例,所以,本文给大家实现一个C++版本的调用示例,需要的朋友可以参考下
    2025-03-03
  • C++ Primer 第一部分基本语言

    C++ Primer 第一部分基本语言

    这篇文章主要介绍了C++ Primer 第一部分基本语言的相关资料,需要的朋友可以参考下
    2014-02-02
  • 可能是全网最详细的Qt连接MySQL数据库教程

    可能是全网最详细的Qt连接MySQL数据库教程

    QT众所周知是一个开源的,以C++为底层的可视化工具库,下面这篇文章主要给大家介绍了关于最详细的Qt连接MySQL数据库教程的相关资料,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2023-04-04
  • Qt中线程常用通信方式介绍

    Qt中线程常用通信方式介绍

    Qt中,线程通信无处不在,最核心的特性信号槽就是一种线程间通信,这篇文章主要为大家介绍了几种常用的方式,需要的小伙伴可以参考一下
    2025-01-01
  • C语言中的奇技淫巧

    C语言中的奇技淫巧

    学习C语言的过程中,总会遇到很多令人眼前一亮的代码,尤其是你写了几十行的代码,别人只用了简单几行的递归就实现的功能。下面我就总结几个C语言中 比较新手向的代码。让你有一种woc!还能这么写的想法
    2018-08-08
  • VC List Control控件如何删除选中的记录实例详解

    VC List Control控件如何删除选中的记录实例详解

    这篇文章主要介绍了VC List Control控件如何删除选中的记录实例详解的相关资料,需要的朋友可以参考下
    2017-06-06
  • C++中delete函数的具体使用

    C++中delete函数的具体使用

    本文主要介绍了C++中delete函数的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-03-03

最新评论