CPH-第四章 数据结构
C++ 标准库中提供了最重要、最常用的数据结构。针对具体问题选择合适的数据结构,往往比从头实现算法更能节省时间。
4.1 动态数组(Dynamic Arrays)
动态数组的大小可以在程序运行期间改变。C++ 中最常用的动态数组是 vector。
- 可以像普通数组一样通过
[]访问元素 - 支持
push_back、pop_back、size、back等操作 push_back的平均时间复杂度为 (O(1))
string 本质上也是一种动态数组,额外提供:
- 字符串拼接:
+ - 取子串:
substr - 查找:
find
4.2 集合结构(Set Structures)
- 集合用于维护一组元素,支持插入、查找和删除。C++ 提供了两种主要实现:
| 结构 | 底层实现 | 核心操作复杂度 | 特点 |
|---|---|---|---|
set |
平衡二叉搜索树 | \(O(\log n)\) | 元素有序,支持范围查询 |
unordered_set |
哈希表 | 平均 \(O(1)\) | 元素无序,但通常更快 |
set中的元素互不相同。如果需要存储重复元素,可以使用:multisetunordered_multiset
- 注意:
multiset中删除一个元素时,使用st.erase(st.find(x))。
4.3 映射结构(Map Structures)
-
map用于存储键值对,可以看作一种“带下标的集合”。- 基于平衡二叉搜索树
- 操作复杂度为 \(O(\log n)\)
- 键是有序的
-
map的典型用法包括:- 计数
- 建立对象之间的关联
- 判断某个键是否存在(
count)
4.4 迭代器与范围(Iterators and Ranges)
-
迭代器是“指向数据结构中元素”的变量。
begin()指向第一个元素end()指向最后一个元素之后的位置- 二者构成一个半开半闭区间:
[begin, end)
-
迭代器的意义:许多标准库算法都通过迭代器范围来操作数据,例如:
sortreverserandom_shuffle
-
因此这些算法不仅适用于
vector,也适用于普通数组(传入指针即可)。
4.5 其他结构(Other Structures)
bitset
- 每个元素只占 1 bit
- 比普通数组节省约 32 倍内存
- 天然支持位运算
- 适合状态压缩或优化布尔数组
deque
- 双端队列
- 在两端插入和删除元素都是 \(O(1)\)
vector只擅长尾端操作- 常数因子通常比
vector大
stack / queue
stack:后进先出(LIFO)queue:先进先出(FIFO)- 核心操作均为 \(O(1)\)
priority_queue
- 维护一个“最值”集合
- 插入和删除最值:\(O(\log n)\)
- 取最值:\(O(1)\)
- 默认是大根堆
- 可以通过
greater改为小根堆,priority_queue<int, vector<int>, greater<int>> q; - 与有序集合相比,常数因子更小
5. Policy-based data structures(GNU 扩展)
-
类型:
typedef tree<int, null_type, less<int>, rb_tree_tag, tree_order_statistics_node_update> indexed_set; -
indexed_set在set的基础上增加了按排名访问元素的能力:find_by_orderorder_of_key
-
复杂度仍为 \(O(\log n)\)。
4.6 与排序的比较(Comparison to Sorting)
问题:计算两个列表中公共元素的个数。
- 三种算法的思路与复杂度如下:
| 算法 | 核心操作 | 时间复杂度 |
|---|---|---|
| 算法 1 | 用 set 存储列表 A,遍历 B 查询 |
\(O(n \log n)\) |
| 算法 2 | 用 unordered_set 替代 set |
平均 \(O(n)\) |
| 算法 3 | 排序两个列表,然后双指针扫描 | \(O(n \log n)\) |
-
尽管算法 1 和算法 3 的渐进复杂度相同(均为 \(O(n \log n)\)),但实际运行时间相差悬殊:
-
在 \(n = 10^6\) 时,算法 1 用时 1.5 秒
-
算法 3 仅需 0.2 秒
-
原因在于常数因子:
- 排序是一个非常“紧凑”的过程
- 只需在开头做一次排序
- 后续扫描是线性的
- 基于平衡树的
set在每一步操作中都伴随指针跳转和内存分配,开销更大
-
-
核心启示:
- 渐进复杂度只是选型的第一层依据,常数因子和实际数据规模同样值得关注。
- 在某些场景下,“先排序,再处理”可能比维护一个动态数据结构更高效。
浙公网安备 33010602011771号