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
。