您现在的位置是:首页 > 编程 > 

全面解析 C++ STL 中的 set 和 map

2025-07-29 03:09:41
全面解析 C++ STL 中的 set 和 map C++ 标准模板库(STL)中的关联式容器以其强大的功能和高效性成为开发者解决复杂数据组织问题的重要工具。其中,set 和 map 是最常用的两类关联容器。本篇博客将从基本特性、底层实现、用法详解、高级案例以及性能优化等多个角度,详细解读它们的设计与使用。1. 什么是关联式容器关联式容器是一类根据关键字组织和管理数据的容器。与序列式容器(如 ve

全面解析 C++ STL 中的 set 和 map

C++ 标准模板库(STL)中的关联式容器以其强大的功能和高效性成为开发者解决复杂数据组织问题的重要工具。其中,setmap 是最常用的两类关联容器。本篇博客将从基本特性、底层实现、用法详解、高级案例以及性能优化等多个角度,详细解读它们的设计与使用。


1. 什么是关联式容器

关联式容器是一类根据关键字组织和管理数据的容器。与序列式容器(如 vectorlist)相比,关联式容器的主要区别如下:

特性

关联式容器(set/map)

序列式容器(vector/list)

数据存储顺序

按关键字排序

按插入顺序

数据访问复杂度

O(log⁡)O(\log )O(log)

O(1)O(1)O(1) 或 O()O()O()

是否支持随机访问

是否支持按索引访问

关联式容器分为有序和无序两类:

  1. 有序容器:如 setmap,基于平衡二叉树(红黑树)实现,数据按排序规则组织。
  2. 无序容器:如 unordered_setunordered_map,基于哈希表实现,提供更高效的查速度,但不保证元素顺序。
关联式容器的核心特性
  • 键值对:关联式容器通过关键字对元素进行组织,set 中的关键字即为数据本身,而 map 则以键值对形式存储数据。
  • 自动排序:有序容器会自动对数据进行排序(升序或自定义规则)。
  • 高效操作:插入、删除、查的平均时间复杂度为 O(log⁡)O(\log )O(log)(红黑树实现)。

2. set 容器详解

2.1 基本概念与特性

set 是一种集合数据结构,用于存储唯一且自动排序的元素。它的主要特点如下:

  • 数据唯一性:同一元素不能重复插入。
  • 自动排序:默认按升序排序,可通过自定义比较器更改排序规则。
  • 迭代器类型set 支持双向迭代器,不支持随机访问。
  • 底层实现:使用红黑树作为存储结构。
2.2 底层实现:红黑树
红黑树的特性

红黑树是一种平衡二叉搜索树,满足以下性质:

  1. 每个节点是红或黑。
  2. 根节点是黑。
  3. 每个叶子节点(nullptr 或 IL 节点)是黑。
  4. 如果一个节点是红,则其子节点必须是黑(即红节点不能相邻)。
  5. 从任意节点到其每个叶子节点的路径都包含相同数量的黑节点。
红黑树的操作
  • 插入:通过旋转和重新着,确保平衡性和红黑性质。
  • 删除:比插入更复杂,同样通过旋转和着维护树的性质。
  • 查:沿树遍历,时间复杂度为 O(log⁡)O(\log )O(log)。

setmap 中,红黑树用来高效实现元素的有序存储和快速查。


2. 构造函数

set 提供以下几种构造方式:

默认构造:创建一个空集合。

代码语言:javascript代码运行次数:0运行复制
set<int> s;

初始化列表构造:直接用 {} 初始化集合。

代码语言:javascript代码运行次数:0运行复制
set<int> s = {, 1, 4, 1, 5, 9};  // 重复元素自动去重

迭代器区间构造:从其他容器的元素构造集合。

代码语言:javascript代码运行次数:0运行复制
vector<int> v = {1, 2, , 4};
set<int> s(v.begin(), ());

自定义比较规则

代码语言:javascript代码运行次数:0运行复制
set<int, greater<int>> s = {, 1, 4};  // 按降序排序

2.4 常用操作与复杂度分析

操作

函数

复杂度

说明

插入

insert(value)

O(log⁡)O(\log )O(log)

插入元素,若已存在则插入失败

删除

erase(value)

O(log⁡)O(\log )O(log)

删除指定元素

find(value)

O(log⁡)O(\log )O(log)

返回迭代器,指向目标元素

统计

count(value)

O(log⁡)O(\log )O(log)

判断元素是否存在,结果为 0 或 1

遍历

begin(), end()

O()O()O()

正向迭代访问所有元素

下界/上界

lower_bound()/upper_bound()

O(log⁡)O(\log )O(log)

返回 >= / > 某值的第一个元素的迭代器

插入操作
代码语言:javascript代码运行次数:0运行复制
set<int> s;
auto res = s.insert(10);  // 插入 10
if (res.second) {
    cout << "插入成功" << endl;
} else {
    cout << "插入失败" << endl;
}
查操作
代码语言:javascript代码运行次数:0运行复制
if (s.find(20) != ()) {
    cout << "到元素 20" << endl;
}
删除操作
代码语言:javascript代码运行次数:0运行复制
(10);  // 删除值为 10 的元素
遍历
代码语言:javascript代码运行次数:0运行复制
for (int x : s) {
    cout << x << " ";  // 正向遍历
}
for (auto it = s.rbegin(); it != s.rend(); ++it) {
    cout << *it << " ";  // 反向遍历
}

2.5 特殊操作与技巧
(1) 自定义排序规则

set 默认按升序排序,使用仿函数或 std::greater 可修改排序规则:

代码语言:javascript代码运行次数:0运行复制
set<int, greater<int>> s = {, 1, 4};
(2) 范围删除

删除值在 [low, high) 范围内的所有元素:

代码语言:javascript代码运行次数:0运行复制
(s.lower_bound(10), s.upper_bound(50));
() 应用:求两个数组的交集
代码语言:javascript代码运行次数:0运行复制
vector<int> intersection(ct vector<int>& nums1, ct vector<int>& nums2) {
    set<int> s1(nums1.begin(), ());
    set<int> s2(nums2.begin(), ());
    vector<int> result;

    for (int x : s1) {
        if ((x)) result.push_back(x);
    }

    return result;
}

2.6 multiset 的区别与应用

multisetset 的区别在于:

  1. multiset 允许存储重复元素。
  2. 插入、删除和查操作的接口与 set 相同,但返回的结果会包含重复项。
代码语言:javascript代码运行次数:0运行复制
multiset<int> ms = {1, 2, 2, };
ms.insert(2);  // 再次插入 2

. map 容器详解

.1 基本概念与特性

map 是一个关联式容器,用于存储键值对(key-value)。与 set 相比,map 不仅存储键(key),还存储与每个键关联的值(value)。 map 的主要特点包括:

  • 键唯一性:每个键在 map 中都是唯一的。
  • 自动排序:默认按照键的升序排序,也可以通过自定义比较器来更改排序规则。
  • 底层实现:基于红黑树实现,操作复杂度为 O(log⁡)O(\log )O(log)。
  • 支持随机访问:与 set 不同,map 中存储的键值对支持通过键快速查对应的值。
代码语言:javascript代码运行次数:0运行复制
map<int, string> m;
m[1] = "apple";  // 插入键值对 (1, "apple")
m[2] = "banana"; // 插入键值对 (2, "banana")
m[] = "cherry"; // 插入键值对 (, "cherry")
内部存储结构

map 使用红黑树存储数据,保证了所有元素按键值自动排序。在 map 中,每个节点存储一个 pair<ct Key, T>,其中 ct Key 表示键,T 表示值。红黑树的特点确保了查、插入和删除操作的时间复杂度都为 O(log⁡)O(\log )O(log)。


.2 构造与初始化

map 提供了多种构造方法,以适应不同的使用场景:

默认构造:创建一个空 map

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

初始化列表构造:通过初始化列表直接创建 map

代码语言:javascript代码运行次数:0运行复制
map<int, string> m = {{1, "apple"}, {2, "banana"}, {, "cherry"}};

范围构造:从另一个容器(如 setvector 等)构造 map

代码语言:javascript代码运行次数:0运行复制
vector<pair<int, string>> v = {{1, "apple"}, {2, "banana"}};
map<int, string> m(v.begin(), ());

自定义比较器:通过传入自定义比较器,指定键的排序方式。

代码语言:javascript代码运行次数:0运行复制
map<int, string, greater<int>> m;  // 降序排序
m[2] = "banana";
m[1] = "apple";

. 常用操作与复杂度分析

操作

函数

复杂度

说明

插入

insert(pair)

O(log⁡)O(\log )O(log)

插入一个键值对,若已存在则插入失败

插入或修改

operator[]

O(log⁡)O(\log )O(log)

插入新元素或修改已有元素的值

find(key)

O(log⁡)O(\log )O(log)

查指定键,返回键值对的迭代器

统计

count(key)

O(log⁡)O(\log )O(log)

查指定键是否存在(map 中为 0 或 1)

删除

erase(key)

O(log⁡)O(\log )O(log)

删除指定键及其对应的值

遍历

begin(), end()

O()O()O()

正向遍历所有元素

下界/上界

lower_bound(key)/upper_bound(key)

O(log⁡)O(\log )O(log)

查大于等于某值或大于某值的第一个元素

插入与查操作

插入:可以通过 insert 方法插入新的键值对,也可以通过 operator[] 插入或修改键值对。

代码语言:javascript代码运行次数:0运行复制
map<int, string> m;
m.insert({1, "apple"});
m[2] = "banana";  // 插入或修改

查:find 方法返回一个迭代器,指向指定键的键值对,若未到则返回 end()

代码语言:javascript代码运行次数:0运行复制
auto it = m.find(1);
if (it != ()) {
    cout << "Found: " << it->second << endl;  // 输出 "apple"
}
删除操作

删除某个键值对:

代码语言:javascript代码运行次数:0运行复制
(1);  // 删除键为 1 的元素

.4 遍历与修改

map 提供了多种遍历方法:

范围 for

代码语言:javascript代码运行次数:0运行复制
for (ct auto& [key, value] : m) {
    cout << key << ": " << value << endl;
}

传统迭代器

代码语言:javascript代码运行次数:0运行复制
for (auto it = m.begin(); it != (); ++it) {
    cout << it->first << ": " << it->second << endl;
}
修改值

可以通过迭代器直接修改值,operator[] 也支持修改已有键的值:

代码语言:javascript代码运行次数:0运行复制
m[2] = "grape";  // 修改键为 2 的值为 "grape"
auto it = m.find(2);
if (it != ()) {
    it->second = "orange";  // 通过迭代器修改值
}

.5 特殊操作与进阶技巧
(1) 下界与上界

通过 lower_bound()upper_bound() 方法,可以获取某个键的下界和上界,常用于区间查。

  • lower_bound(key):返回第一个大于等于 key 的元素。
  • upper_bound(key):返回第一个大于 key 的元素。
代码语言:javascript代码运行次数:0运行复制
map<int, string> m = {{1, "apple"}, {2, "banana"}, {, "cherry"}};
auto lb = m.lower_bound(2);  // 返回键为 2 或大于 2 的第一个元素
cout << lb->second << endl;  // 输出 "banana"
(2) 自定义排序规则

如同 setmap 也可以通过自定义比较器来实现不同的排序规则。

代码语言:javascript代码运行次数:0运行复制
map<int, string, greater<int>> m = {{1, "apple"}, {, "cherry"}, {2, "banana"}};
for (ct auto& [key, value] : m) {
    cout << key << ": " << value << endl;
}  // 输出:: cherry 2: banana 1: apple
() 范围删除

删除某个键值范围内的元素,常用于清除一段区间:

代码语言:javascript代码运行次数:0运行复制
map<int, string> m = {{1, "apple"}, {2, "banana"}, {, "cherry"}};
(m.lower_bound(2), m.upper_bound());  // 删除键为 2 和  的元素

.6 multimap 的区别与应用

multimapmap 的扩展,允许相同的键有多个值(即支持键的冗余)。与 map 的区别在于,multimap 在插入重复键时不会丢失数据,而 map 会自动覆盖原有键。

代码语言:javascript代码运行次数:0运行复制
multimap<int, string> mm;
mm.insert({1, "apple"});
mm.insert({1, "banana"});
mm.insert({2, "cherry"});

for (ct auto& [key, value] : mm) {
    cout << key << ": " << value << endl;  // 输出:1: apple 1: banana 2: cherry
}

multimap 在某些场景下非常有用,例如存储学生成绩时,可能有多个学生取得相同的分数。


4. 高级案例:综合利用 setmap

4.1 查两个数组的交集
代码语言:javascript代码运行次数:0运行复制
vector<int> intersection(ct vector<int>& nums1, ct vector<int>& nums2) {
    set<int> s1(nums1.begin(), ());
    set<int> s2(nums2.begin(), ());
    vector<int> result;

    for (int x : s1) {
        if ((x)) result.push_back(x);
    }

    return result;
}
4.2 构建词频统计表
代码语言:javascript代码运行次数:0运行复制
map<string, int> wordCount(ct vector<string>& words) {
    map<string, int> wordMap;
    for (ct string& word : words) {
        wordMap[word]++;
    }
    return wordMap;
}
4. 高效查链表中的环
代码语言:javascript代码运行次数:0运行复制
bool hasCycle(Listode* head) {
    set<Listode*> visited;
    while (head != nullptr) {
        if (visited.find(head) != ()) {
            return true;  // 到环
        }
        visited.insert(head);
        head = head->next;
    }
    return false;
}

5. 性能优化与注意事项

5.1 使用 unordered_mapunordered_set

在很多查密集型的应用中,unordered_mapunordered_set 基于哈希表实现,提供常数时间复杂度 O(1)O(1)O(1) 的查和插入操作。它们的性能优势适用于不需要保持元素顺序的场景。

5.2 避免不必要的拷贝

当插入大量数据时,可以使用 emplace() 来避免不必要的对象拷贝。emplace() 可以直接构造元素,而无需创建临时对象。

代码语言:javascript代码运行次数:0运行复制
map<int, string> m;
(1, "apple");  // 不会发生拷贝
5. 避免频繁修改键

map 不支持修改键,修改键会导致数据结构破坏。因此,避免频繁修改键,而应使用新的键值对进行插入和删除。


6. 总结

通过本文的详细解析,我们全面了解了 C++ 中 setmap 容器的使用、底层实现以及高效操作技巧。掌握这些基本知识后,开发者可以灵活使用 setmap 来处理各种复杂的关联数据问题,从而提高程序的效率和可读性。

在实际开发中,选择合适的容器(如 mapunordered_mapsetunordered_set)可以帮助我们应对不同的数据处理需求,避免性能瓶颈。希望通过本文的学习,你能够深入掌握这些强大的容器,提升 C++ 编程技能。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。 原始发表:2025-01-1,如有侵权请联系 cloudcommunity@tencent 删除mapsetstlc++容器

#感谢您对电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格的认可,转载请说明来源于"电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格

本文地址:http://www.dnpztj.cn/biancheng/1183023.html

相关标签:无
上传时间: 2025-07-22 05:41:09
留言与评论(共有 19 条评论)
本站网友 同声翻译
11分钟前 发表
2
本站网友 套利者
20分钟前 发表
关联式容器的主要区别如下:特性关联式容器(set/map)序列式容器(vector/list)数据存储顺序按关键字排序按插入顺序数据访问复杂度O(log⁡)O(\log )O(log)O(1)O(1)O(1) 或 O()O()O()是否支持随机访问否是是否支持按索引访问否是关联式容器分为有序和无序两类:有序容器:如 set 和 map
本站网友 宿州租房信息
22分钟前 发表
提供更高效的查速度
本站网友 高温输送带
30分钟前 发表
operator[] 也支持修改已有键的值:代码语言:javascript代码运行次数:0运行复制m[2] = "grape"; // 修改键为 2 的值为 "grape" auto it = m.find(2); if (it != ()) { it->second = "orange"; // 通过迭代器修改值 }.5 特殊操作与进阶技巧(1) 下界与上界通过 lower_bound() 和 upper_bound() 方法
本站网友 百度通
6分钟前 发表
若未到则返回 end()
本站网友 蜂乃宝
16分钟前 发表
set 中的关键字即为数据本身
本站网友 张冰玉
8分钟前 发表
value]
本站网友 荔枝湾
12分钟前 发表
set 中的关键字即为数据本身
本站网友 李海生
22分钟前 发表
但返回的结果会包含重复项
本站网友 成都购房
22分钟前 发表
banana 2
本站网友 河南艾滋病
17分钟前 发表
代码语言:javascript代码运行次数:0运行复制auto it = m.find(1); if (it != ()) { cout << "Found
本站网友 豌豆的营养价值
7分钟前 发表
则其子节点必须是黑(即红节点不能相邻)
本站网友 汝阳二手房
26分钟前 发表
基于平衡二叉树(红黑树)实现
本站网友 王富信
10分钟前 发表
自动排序:默认按照键的升序排序
本站网友 vlsi
3分钟前 发表
掌握这些基本知识后
本站网友 一对一视频聊天
15分钟前 发表
" << value << endl; } // 输出:
本站网友 广东寰球广业工程有限公司
0秒前 发表
每个节点存储一个 pair<ct Key
本站网友 草一次
27分钟前 发表
而 map 会自动覆盖原有键