1 STL 线性数据结构容器
没有选 C++ 的课,自学 C++ 是为了打 CSP 更方便。C++ 提供的一些模版和方法可以极大地减少我的代码量和思维量,做题时更加高效。
数组类容器 vector¶
vector 的创建¶
需要包含<vector>头文件,创建方式例如
vector进行pop_back()是未定义行为,应先检查是否为空。
vector 的访问¶
vector支持下标访问,即可以采用和数组类似的方法进行访问。同时 vector 还支持迭代器访问。通过begin()方法可以获取指向其首部(第一个元素)的迭代器,通过end()方法获取指向其尾部(最后一个元素的下一个)的迭代器。
front(),back()和begin(),end()的区别
对于下面的声明:
vec.front()和vec.back()返回的是vec中元素的引用,类型为int;vec.begin()和vec.end()返回的是迭代器,类型为std::vector<int>::iterator。迭代器有点像指针,实际上
*(vec.begin())的结果和vec.front()的结果是一样的。
逆序迭代器和常迭代器
begin()和end()返回的是正序迭代器,与之对应地有逆序迭代器rbegin()和rend()。一张图形象展示:
若vec为std::vector<int>类型,那么逆序迭代器都是std::vector<int>::reverse_iterator类型。
实际上与这四个方法对应,还有四个产生只读迭代器(常迭代器)的方法,即cbegin(),cend(),crbegin()和crend()。他们所产生迭代器的类型为std::vector<int>::const_interator或者std::vector<int>::const_reverse_iterator。
常迭代器的特点是,不可通过它们修改vector中对象的值。
所以我们可以这样访问一个vector:
vector 的插入、删除和反转¶
插入用insert()函数。vector中有很多重载的insert()函数,介绍可能常用的两种:
erase()函数。vector中有很多重载的erase()函数,介绍可能常用的两种:
删除满足特定条件的元素
删除元素时,大多不能提前知道要删除元素的位置在哪里,可能需要先遍历一下。比如要删除所有值为 $3$ 的元素:
it遍历到 $3$ 这个元素时,it被擦除变成野指针,不能使用了。此时it++继续遍历会造成问题。可以利用erase()本身的返回值:
反转可能很少用到,可以通过vec.reverse(vec.begin(),vec.end())实现。
栈容器 stack¶
栈自己手写其实并不难,但既然有现成的模版,也没必要动这个脑子,对吧。
需要包含<stack>头文件,常用的方法有
- 对于空的
stack进行pop()或者top()都是未定义行为,应先检查是否为空。 stack容器不提供随机访问方法,栈容器本身的特点就是只能访问、修改栈顶元素。
队列容器¶
普通队列容器 queue¶
队列自己写起来就稍微有些麻烦了,耗时且不能保证总是写对。使用容器的常数代价大,但是工作量小。
需要包含<queue>头文件,常用的方法有
优先队列¶
大顶堆¶
优先队列是非常重要的一个工具,其本质是包装好的堆,在实现优化的 Dijkstra 算法中有巨大意义。
创建一个优先队列需要包含头文件<queue>。下面的代码默认创建的是大顶堆:
stack类似的方法,即
自定义优先级¶
通过这样的方式可以构造一个自定义优先级的优先队列:
int类型,第二个模版参数指出了优先队列的底层容器,第三个参数compare是一个通过仿函数自定义优先级的结构体。比如可以使用下面的结构体构造小根堆:
a和b,仿函数返回true意味着a的优先级低于b。有时候我们觉得专门创建一个这样的结构体重载运算符
()不是优雅,也有解决办法。