c++中的STL
STL(Standard Template Library,标准模板库)是C++中的一个非常重要且强大的组成部分,它提供了丰富的模板类和函数,让我们可以高效、方便地处理数据结构和算法。下面我会以通俗易懂的方式,详细介绍STL的各个方面。
一、什么是STL?
**STL(Standard Template Library)**是C++标准库的一部分,主要包含三大方面:
- 容器(Containers)
- 算法(Algorithms)
- 迭代器(Iterators)
这些工具结合在一起,让你可以快速实现数据存储、操作和处理,不用自己从零写数据结构或算法。
二、STL的三大核心组成部分
1. 容器(Containers)
容器就像不同形状的“容器箱”,用来存放各种类型的数据。不同的容器有不同的用途和特性。
常用的容器分类:
- 序列容器:元素有序排列,可以根据位置访问
vector
(动态数组)list
(双向链表)deque
(双端队列)array
(数组封装)
- 关联容器:存放“键值对” 可以快速查找
set
(集合,不重复元素)map
(映射,键值对)multiset
(相同元素可多次)multimap
- 无序容器(哈希容器):提供更快的查找
unordered_set
unordered_map
2. 算法(Algorithms)
STL提供了大量的算法,比如排序、查找、复制、遍历等:
sort()
find()
lower_bound()
copy()
merge()
reverse()
- 等等
它们通常与迭代器一起使用,实现对容器中元素的操作。
3. 迭代器(Iterators)
迭代器就像指针,用于遍历容器中的元素。例如:
begin()
获取容器的起点end()
获取终点后面一个位置(用来界定遍历范围)
迭代器的作用是抽象出不同容器的遍历方式,让算法可以统一操作各种容器。
三、STL的详细介绍
3.1 容器细节
容器类型 | 描述 | 典型场景 | 访问方式 |
---|---|---|---|
vector | 动态数组,存放连续元素 | 需要频繁随机访问,增加/删除末尾 | 索引访问 [i] |
list | 双向链表 | 频繁插入/删除操作,位置不定 | 迭代器操作 |
deque | 双端队列,头尾都可以快速插入删除 | 需要两端快速操作 | 索引和迭代器操作 |
array | 固定大小数组(类似C数组) | 大小固定不变时使用 | 索引访问 |
set | 不重复元素的集合 | 存放唯一元素,实现集合操作 | 迭代器遍历 |
multiset | 允许重复元素的集合 | 存放多份相同元素 | |
map | 键值对(关联数组) | 快速查找、存储关联数据 | 通过键访问 |
multimap | 支持重复键的映射 | 一个键对应多个值 | |
unordered_set , unordered_map | 基于哈希的容器,操作更快 | 大数据场景,追求速度 |
3.2 常用容器简介
vector(动态数组)
#include <vector>
std::vector<int> v;
v.push_back(10); //在末尾添加元素
v[0]; // 索引访问
v.size(); // 获取元素个数
list(链表)
#include <list>
std::list<int> lst;
lst.push_back(20);
auto it = lst.begin();
set(集合)
#include <set>
std::set<int> s;
s.insert(5);
auto it = s.find(5);
if (it != s.end()) { /* 找到元素 */ }
3.3 容器的适用场景总结
- 如果需要频繁随机访问元素,用
vector
- 如果需要频繁插入删除,用
list
或deque
- 需要有序元素或去重,用
set
- 需要快速查找,用
unordered_map
四、STL算法详解
STL中的算法是模板函数,操作容器或区间。
以下是一些基础算法:
#include <algorithm> //包含算法
std::vector<int> v = {3, 1, 4, 1, 5};
std::sort(v.begin(), v.end()); // 排序
auto it = std::find(v.begin(), v.end(), 4); // 查找
std::reverse(v.begin(), v.end()); // 反转
常用算法介绍
sort()
:排序find()
:查找binary_search()
:二分查找(容器已排序)copy()
:复制区间merge()
:合并两个已排序的区间for_each()
:对范围内元素应用函数remove()
:移除元素(实际不改变容器,只是在区间操作,结合erase)
示例:排序和查找
#include <algorithm>
#include <vector>
#include <iostream>int main() {std::vector<int> nums = {4, 2, 7, 1, 3};std::sort(nums.begin(), nums.end()); // 排序if (std::binary_search(nums.begin(), nums.end(), 3))std::cout << "存在3\n";
}
五、迭代器详解
迭代器像指针一样,提供统一的访问接口。
常用迭代器类别:
- 输入迭代器(只读,前向)
- 输出迭代器(只写)
- 前向迭代器(多次遍历,支持
++
) - 双向迭代器(前后都可以遍历)
- 随机访问迭代器(支持索引访问,比如vector的
begin()
、end()
)
示例:
#include <vector>
#include <algorithm>
#include <iostream>int main() {std::vector<int> v = {1, 2, 3};for (auto it = v.begin(); it != v.end(); ++it) {std::cout << *it << " ";}
}
六、STL的实用技巧和注意事项
- 模板的灵活性:STL模板让算法和容器适用于各种类型。
- 头文件:常用如
<vector>
、<algorithm>
、<map>
、<set>
等。 - 避免越界:尽量用
at()
或检查size()
。 - 了解容器的复杂度:比如,
vector
插入末尾是O(1)
,中间插入是O(n)
。
七、总结
- STL让C++变得更强大:提供了许多强大的工具,减少自己造轮子。
- 设计原则:容器存数据,算法操作数据,迭代器连接二者。
- 学习建议:
- 重点掌握
vector
、list
、map
、set
的使用。 - 熟悉常用算法的用法。
- 结合具体场景,选择合适的容器。
- 重点掌握
八、结语
STL确实是C++的“神器”,掌握好它可以大大提高编程效率和代码质量。建议你通过多写多练,理解每个容器和算法背后的设计思想,慢慢就会觉得它们变得非常自然。
深入理解map
、unordered_map
等容器的底层实现,有助于你更好地选择数据结构,以及理解它们的性能特点。让我用通俗易懂的方式一一讲解,特别是它们的底层原理。
一、概览:map
和unordered_map
的区别
特性 | map | unordered_map |
---|---|---|
存储结构 | 平衡二叉搜索树(通常为红黑树) | 哈希表 |
元素有序 | 有序(根据键的大小排序) | 无序 |
操作复杂度(查找插入) | O(log n) | 平均为O(1) ,最坏O(n) (冲突严重时) |
存储空间 | 稍大一些 | 更节省空间(哈希冲突导致额外空间) |
二、map
的底层实现:平衡二叉搜索树(红黑树)
1. 什么是红黑树?
- 红黑树是一种自平衡的二叉搜索树,保证树的高度在
O(log n)
范围内。 - 树的每个节点有颜色(红或黑)。
- 特性:红黑树的插入、删除会调整颜色和旋转,确保树保持平衡。
2. map
的存储
- 所有元素(键值对)存放在红黑树的节点上。
- 节点按键大小排序(从小到大)。
3. 访问和操作
- 插入:类似二分搜索树,按键值插入,然后调整颜色和旋转色彩保持平衡。
- 查找:从根节点开始,与目标键比较,沿左或右子树深入,直到找到或到达叶子。
- 删除:找到目标节点后调整树的平衡。
4. 性能
- 查找、插入、删除:
O(log n)
,因为树的高度为log n
。
三、unordered_map
的底层实现:哈希表
1. 什么是哈希表?
- 哈希表通过哈希函数将键映射到数组的某个位置(桶/桶数组)。
- 支持平均
O(1)
的查找、插入和删除。
2. 核心原理
(a)哈希函数
- 将键(如整数、字符串)通过算法转化为一个“哈希值”。
- 这个哈希值经过“取模”操作,得到存放位置(索引)。
(b)桶(Bucket)
- 数组中的每个位置叫“桶”。
- 每个桶可以存放多个元素(解决哈希冲突的方法之一是链表法)。
(c)冲突处理
- 链表法(链式法):每个桶维护一个链表或其他结构,多个元素可能映射到同一桶。
- 开放地址法(较少用):当发生哈希冲突时,沿探查(线性或二次探查)找到下一个空桶。
3. 具体流程
- 插入:计算哈希值→取模→存放到对应桶的链表中(或开放地址法找到位置)。
- 查找:计算哈希值→找到对应桶→在链表中逐个比较键,找到目标。
4. 性能
- 平均复杂度:
- 查找、插入、删除:
O(1)
(理想情况下)
- 查找、插入、删除:
- 最坏复杂度:
- 当哈希函数碰撞严重,所有元素都在同一个桶,变成链表或链表变长,效率退化到
O(n)
。
- 当哈希函数碰撞严重,所有元素都在同一个桶,变成链表或链表变长,效率退化到
5. 负载因子(load factor)
- 衡量哈希表的密集程度。
- 负载因子 = 元素数 / 桶数,若太大,会触发扩容(增加桶数,用再哈希重新存放所有元素)。
四、map
和unordered_map
的底层区别总结
特性 | map | unordered_map |
---|---|---|
底层结构 | 红黑树 | 哈希表 |
有序性 | 有序(按键排序) | 无序(存储顺序不定) |
操作时间复杂度 | O(log n) | 平均O(1) ,最坏O(n) |
适用场景 | 需要排序、范围查询 | 追求速度、对顺序无要求 |
内存占用 | 比较大(树结构) | 较小(哈希冲突带来的空间) |
关键字类型支持 | 支持复杂类型(需要定义比较函数) | 支持自定义哈希函数(定义方法) |
五、通俗比喻总结
map
:就像字典,每个词(键)对应一个定义(值),字典按字母顺序排好,查找快,插入删除相对耗时(因为要保持排序),树结构就像一棵平衡树。unordered_map
:像一个乱序的抽屉,把标签(键)投到不同的抽屉(桶),几乎可以即时找到物品,如果碰巧碰到同一个抽屉里的东西(哈希冲突),就像一堆杂物堆在一起,需要逐个翻找。
六、底层实现的简单示意(伪代码)
1. map
(红黑树)简要模拟
复制代码
插入(key, value):如果树空->创建根节点否则:在树中搜索插入位置(类似二分搜索)插入后,调整颜色和旋转以保持平衡(红黑性质)
2. unordered_map
(哈希表)简要模拟
复制代码
插入(key, value):hash_value = hash_function(key)index = hash_value % 桶数在桶(链表或其他)中查找是否存在key存在:更新值不存在:插入新元素到链表头或尾
扩容机制:当元素数超过负载因子*桶数,扩大桶数组,重新哈希所有元素
七、总结
map
:保证有序,底层为平衡二叉树(红黑树),操作为O(log n)
unordered_map
:无序存储,底层为哈希表,操作平均O(1)
(极端情况下变为O(n)
)- 设计选择:看是否需要排序、对性能的要求,以及存储空间。