C++ STL⚓︎
- C++ STL
- 数组 (array)
- 向量 (vector)
- 对 (pair)
- 字符串 (string)
- 链表 (list)
- 队列
- 栈 (stack)
- 双端队列 (deque)
- 集合和映射
- 哈希表
- 位集 (bitset)
C++的 Standard Template Library(STL) 包含以下三个部分:
- Container(容器): 用来存储数据;
- Iterator(迭代器): 用来访问数据;
- Algorithm(算法): 对STL相关操作的算法。
通用操作:
size(): 返回元素个数empty(): 返回是否为空
数组 (array)⚓︎
语法:std::array<T, N> arr;
array是一个封装了 固定 大小数组的容器,编译期间需要知道数组大小!array的“值传递”确实是值传递(回忆:传统的非STL的数组是指针传递)。
array访问元素的方式:
at(): 例如arr.at(4)指的是arr[4],如果下标越界会抛出out of bound exception;[]: 下标越界的行为与at()不同;front(): 返回第一个元素;back(): 返回最后一个元素;data(): 返回arr的指针。
测试代码:array
向量 (vector)⚓︎
语法:std::vector<T> vec;
vector( Sequence Container )是 变长数组,基本思想是倍增,有时被称为Dynamic Array或Array List。vector可以看作集合了“链表+数组”优点的容器。
std::vector是一种序列容器,可封装动态大小的数组。元素是连续存储的,这意味着不仅可以通过迭代器访问元素,还可以使用指向元素的常规指针的偏移量访问元素。这意味着指向vector元素的指针可以传递给任何期望指向数组元素指针的函数。
vector的存储会自动处理,并根据需要进行扩展。vector通常比静态数组占用更多空间,因为会分配更多内存来处理未来的增长。这样,每次插入一个元素时,vector都不需要重新分配,而只需在额外内存用完时重新分配。使用capacity()函数可以查询分配的内存总量。多余的内存可以通过调用shrink_too_fit()返回给系统。就性能而言,重新分配通常是代价高昂的操作。如果事先知道元素的数量,可以使用reserve()函数来消除重新分配。
vector常见操作的开销如下:
- 随机存取:常量 \(O(1)\)
- 在末尾插入或移除元素:摊销时间为常数(Constant Amortized Time) \(O(1)\)
- 插入或移除元素:与向量末端的距离成线性关系 \(O(n)\)
vector访问元素的方式:
at():[]: 时间复杂度为 \(O(1)\)front():back():data():
注:系统为某一个程序分配空间时,所需的时间与空间大小基本无关,而与申请次数有关,因此变长数组要尽量减小申请空间的次数(可以浪费空间)。对长度为 \(n\) 的vector,开辟空间的次数为 \(\log n\) ,而额外copy的次数为 \(O(1)\)。
vector的常见操作:
front(),back():push_back():pop_back():insert():emplace():emplace_back():resize():swap():erase():clear(): 清空reserve(): 将vector的容量(vector在不需要重新分配的情况下可容纳的元素总数)增加到大于或等于新设定的值。如果新设定大于当前的capacity(),就会分配新的存储空间,否则函数不会执行任何操作;begin(),end(): 注意end()代表最后一个数的下一个数的迭代器
vector支持随机寻址,支持比较运算。
区分:
size(): 返回当前元素个数;capacity(): 返回当前分配的存储空间可容纳的元素个数。
对 (pair)⚓︎
std::pair是一个类模板,它提供了一种将两个异构对象存储为一个单元的方法。一个pair是包含两个元素的std::tuple的一种特殊情况。
pair: 定义一个二元组,前后两个变量类型可以不一样;类似于有两个变量且实现了一些函数的结构体。
first: 第一个元素second: 第二个元素
pair支持比较运算,以first为第一个关键字,second为第二个关键字(字典顺序)。
测试代码:pair
字符串 (string)⚓︎
string: 字符串,substr(), c_str()
clear(): 清空substr():c_str():length(): 作用同size(),都是返回字符串长度
链表 (list)⚓︎
STL链表种类:
forward_list: 单向链表list: 双向链表
单向链表 (forward_list)⚓︎
std::forward_list是一个容器,支持从容器中的任何位置快速插入和移除元素。不支持快速随机存取。它以单链表的形式实现。与std::list相比,当不需要双向迭代时,该容器能提供更节省空间的存储空间。
在列表或多个列表中添加、删除和移动元素不会使当前引用列表中其他元素的迭代器失效。但是,当相应的元素从列表中删除(通过erase_after)时,指向该元素的迭代器或引用就会失效。
支持操作:operator =, assign, front, empty, max_size, clear, insert_after, emplace_after, reverse, sort, merge, splice_after, unique, remove, remove_if, resize等。
测试代码:forward_list
双向链表 (list)⚓︎
std::list是一种容器,支持从容器中的任何位置定时插入和移除元素。不支持快速随机存取。它通常以双链表的形式实现。与std::forward_list相比,该容器提供双向迭代功能,但空间效率较低。
在列表或多个列表中添加、删除和移动元素不会使迭代器或引用失效。只有删除相应元素时,迭代器才会失效。
支持操作:operator =, assign, front, back(forward_list无此操作), empty, size(forward_list无此操作), max_size, clear, insert, emplace, push_back(forward_list无此操作), pop_back(forward_list无此操作), push_front(forward_list无此操作), pop_front(forward_list无此操作), reverse, sort, merge, splice, unique, remove, remove_if, resize等。
测试代码:list
队列⚓︎
普通队列 (queue)⚓︎
std::queue类是一个容器适配器(container adaptor),它为程序员提供了队列的功能,特别是先进先出(FIFO)数据结构。该类模板充当底层容器的封装器,只提供一组特定的函数。queue将元素推到底层容器的后面,然后从前面取出。
queue允许在back进行push(insert),或者在front进行pop(remove)。
queue支持操作: push(), front(), back(), pop(), empty(), size()等。
push(): 向队尾插入一个元素front(): 返回队头元素back(): 返回队尾元素pop(): 弹出队头元素
注:queue没有clear()函数!
测试代码:quque
优先队列 (priority_queue)⚓︎
优先队列(堆)priority_queue是一种容器适配器(container adaptor),它以 对数 插入和提取为开销,提供 最大 (默认情况下)元素的 常数 时间查找。用户可以提供一个Compare来改变排序,例如,使用std::greater<T>会使 最小 的元素显示为top()。
使用priority_queue类似于在随机存取容器中管理heap,好处是不会意外地使heap失效。
注意Container指的是用于存储元素的底层容器类型,标准容器std::vector(包括std::vector<bool>)和std::deque可以满足这些要求。
priority_queue使用std::make_heap, std::push_heap, std::pop_heap函数作为底层实现。
priority_queue支持操作: push(), top(), pop()等。
注:默认为大根堆,如果想得到小根堆,可以插入原插入数的负数(h.push(-x)),或是直接定义小根堆
注:priority_queue也没有clear()函数!
push(): 插入一个元素top(): 返回堆顶元素pop(): 弹出堆顶元素
测试代码:priority_queue
栈 (stack)⚓︎
std::stack类是一个容器适配器(container adaptor),它为程序员提供了堆栈的功能,特别是后进先出(LIFO)数据结构。该类模板充当底层容器的封装器,只提供一组特定的函数。stack从底层容器的后部(即堆栈顶部)推入和弹出元素。默认情况下,如果没有为特定stack实例化指定容器(Container)类,就会使用标准容器std::deque作为底层容器实现(其他选项可能为std::vector或std::list)。
stack支持操作: push(), top(), pop(), empty(), size()等。
注:stack也没有clear()函数!
push(): 向栈顶插入一个元素top(): 返回栈顶元素pop(): 弹出栈顶元素
测试代码:stack
双端队列 (deque)⚓︎
deque: 双端队列,队头队尾均可插入和删除,支持随机访问或寻址;类似于加强版的vector,缺点是速度慢。
std::deque(双端队列,double-ended queue)是一种索引序列容器,允许在其开始和结束位置快速插入和删除。此外,在deque的任何一端插入和删除都不会使指向其余元素的指针或引用失效。与std::vector不同的是,deque的元素并 不是连续存储的:典型的实现方法是使用一系列单独分配的固定大小数组,并进行额外的簿记(bookkeeping),这意味着对deque的索引访问必须执行两次指针反引用,而vector的索引访问只需执行一次。
deque的存储空间会根据需要自动扩展和收缩。deque的扩展比std::vector的扩展成本 低,因为它不需要将现有元素复制到新的内存位置。但另一方面,deque通常具有较大的最小内存成本;一个只容纳一个元素的deque需要分配其整个内部数组(例如,在64位libstdc++上是对象大小的8倍;在64位libc++上是对象大小的16倍或4096字节,以较大者为准)。
对deque进行常见操作的开销如下:
- 随机存取:常数 \(O(1)\)
- 在结尾或开头插入或移除元素:常数 \(O(1)\)
- 插入或删除元素:线性 \(O(n)\)
常见操作:
clear(): 清空元素front():back():push_back():pop_back():push_front():pop_front():begin()\end():
测试代码:deque
集合和映射⚓︎
set, map, multiset, multimap 均是 Associative Container,基于平衡二叉树(红黑树)实现,本质是动态维护有序序列
通用操作:
clear():begin()\end(): 支持++和--操作(时间复杂度为 \(O(\log n)\) ),返回前驱和后继
集合 (set, multiset)⚓︎
std::set是一个关联容器,它包含一个由Key类型的唯一对象组成的 排序 集合。排序是通过键比较函数Compare完成的。搜索、移除和插入操作的复杂度为对数。set通常以红黑树的形式实现。我们可以在构建set对象时传入排序的规则。
std::multiset是一个关联容器,使用红黑树实现,它包含一个由Key类型对象组成的 排序 集合。与set不同,它允许 多个 具有相同值的键。排序使用键比较函数Compare完成。搜索、插入和删除操作的复杂度为对数。我们同样可以在构建multiset对象时传入排序的规则。
set / multiset支持:
insert(): 插入一个数,时间复杂度为 \(O(\log n)\)find(): 查找一个数count(): 返回某一个数的个数(set里仅有0和1两种情况)erase(): 若输入是一个数 \(x\) ,则删除所有 \(x\) ,时间复杂度为 \(O(k+\log n)\) ,其中 \(k\) 为 \(x\) 的个数;若输入是一个迭代器,则删除这个迭代器lower_bound(const Key& x)(重要): 返回大于等于 \(x\) 的最小的数的迭代器,若不存在返回endupper_bound(const Key& x)(重要): 返回大于 \(x\) 的最小的数的迭代器,若不存在返回end
注:emplace和insert的区分:
insert函数:- 使用方法:
insert函数用于向容器中添加元素,它可用于vector、list和map等各种容器; - 工作原理:使用
insert时,首先构造元素,然后将其复制或移动到容器中,这意味着元素必须有一个复制或移动构造函数; - 效率:与
emplace相比,insert的效率可能较低,因为它涉及复制或移动对象。 emplace函数:- 使用方法:在C++11中引入,
emplace函数(vector还有emplace_back)通过就地构建的方式向容器中添加新元素; - 工作原理:
emplace直接在容器中构造元素,这是通过将提供的参数传给元素的构造函数来实现的; - 效率:
emplace比insert更有效率,尤其是对于复杂对象,因为它省去了额外的复制或移动操作。它可以在容器中需要的位置构造对象。
测试代码:insert和emplace
映射 (map, multimap)⚓︎
std::map是一个已 排序 的关联容器,它包含具有 唯一键 的键值对。键值通过比较函数Compare排序。搜索、移除和插入操作的复杂度为对数。map通常以红黑树的形式实现。
std::multimap是一个关联容器,包含键值对的 排序 列表,同时允许 多个 条目具有相同的键值。排序是根据应用于键的比较函数Compare进行的。搜索、插入和删除操作的复杂度为对数。
注意:multimap没有像map那样的at()或[]的索引操作,可考虑使用equal_range()!
map / multimap支持:
insert(): 插入的数是一个pairerase(): 输入的参数是pair或迭代器find(const Key& key): 查找键值与key相同的元素。对于multimap而言,如果容器中有多个具有key的元素,可以返回其中任何一个;[](重要,仅map支持,multimap不支持): 像数组一样用map,时间复杂度为 \(O(\log n)\)lower_bound(const Key& key): 返回指向 不小于 (即大于或等于)key的第一个元素的迭代器;upper_bound(const Key& key): 返回指向 大于key的第一个元素的迭代器。
哈希表⚓︎
unordered_set, unordered_map, unordered_multiset, unordered_multimap: 哈希表
和上面类似(一一对应),好处是增删改查的时间复杂度为 \(O(1)\) ,但不支持lower_bound()和upper_bound(),也不支持迭代器的++和--(凡是和排序有关的操作都不支持)。
unordered_set⚓︎
std::unordered_set 是一种关联容器,包含一组类型为Key的唯一对象。搜索、插入和删除的平均时间复杂度为常数。
在内部,元素并没有按照特定顺序排序,而是组织到桶(buckets)中。元素被放入哪个桶完全取决于其值的哈希。这允许以 常数 时间复杂度快速访问单个元素,因为一旦计算出哈希,它就指向元素所在的确切桶。
由于修改可能会改变元素的哈希并损坏容器,因此unordered_set不允许修改容器元素 (即使是非const迭代器也不行)。
unordered_multiset⚓︎
std::unordered_multiset 是一种关联容器,包含可能非唯一的类型为Key的对象集合。搜索、插入和删除的平均时间复杂度为常数。
在内部,元素并没有按照特定顺序排序,而是组织到桶中。元素被放入哪个桶完全取决于其值的哈希。这允许快速访问单个元素,因为一旦计算出哈希,它就指向元素所在的确切桶。
此容器的迭代顺序不要求稳定(因此,例如,不能使用 std::equal 来比较两个 std::unordered_multiset),除非每组键相等的元素(使用 key_eq() 作为比较器时比较相等)在迭代顺序中形成一个连续的子范围,也可以通过 equal_range() 访问。
测试代码:哈希
unordered_map⚓︎
std::unordered_map 是一种关联容器,包含具有唯一键的键值对。元素的搜索、插入和删除的平均时间复杂度为 常数。
在内部,元素并没有按照特定顺序排序,而是组织到桶中。元素被放入哪个桶完全取决于其键的哈希。拥有相同哈希码的键出现在同一个桶中。这允许快速访问单个元素,因为一旦计算出哈希,它就指向元素所在的确切桶。
如果映射的键等价性谓词(key equality predicate)在传入这些键时返回真,则认为两个键是等价的。如果两个键是等价的,哈希函数必须为这两个键返回相同的值。
unordered_multimap⚓︎
std::unordered_multimap 是一种无序关联容器,支持等价键(一个 unordered_multimap 可以包含每个键值的多个副本),并将另一种类型的值与键相关联。unordered_multimap 类支持前向迭代器。搜索、插入和删除的平均时间复杂度为 常数。
在内部,元素并没有按照特定顺序排序,而是组织到桶中。元素被放入哪个桶完全取决于其键的哈希。这允许快速访问单个元素,因为一旦计算出哈希,它就指向元素所在的确切桶。
此容器的迭代顺序不要求稳定(因此,例如,不能使用 std::equal 来比较两个 std::unordered_multimaps),除非每组键相等的元素(使用 key_eq() 作为比较器时比较相等)在迭代顺序中形成一个连续的子范围,也可以通过 equal_range() 访问。
测试代码:哈希
位集 (bitset)⚓︎
bitset: 压位
支持~, &, |, ^, >>, <<, != 等操作,也支持[]操作符
count(): 返回有多少个1any(): 判断是否至少有一个1none(): 判断是否全为0set(): 把所有位置成1set(k, v): 将第k位变为vreset(): 把所有位变成0flip(): 等价于~flip(k): 把第k位取反