STL笔记
在c++中,提供了许多实用的数据结构,称作STL,在考试时拿来用即可,非常方便,下面讲讲常见的STL。
Vector
vector
,又称动态数组,每次往里面丢一个数会申请一定空间,而不是在一开始全部申请,如果对空间大小把握不明确,可以使用 vector
替代数组,常见的应用有邻接表等。
支持随机访问,如可以直接 a[10]
,表示动态数组的第11个位置(下标默认从0开始)。
定义STL:vector<int> s;
(定义一个一维动态数组,整形)。
基本操作(按照使用频率从上往下排序):
-
push_back(x)
:在数组的尾部插入一个元素x
,时复 \(O(1)\). 注意下标从 \(0\) 开始。 -
size()
:返回当前动态数组的长度。 -
clear()
:清空动态数组。 -
begin(),end()
- 功能:分别返回头,尾指针
- 应用:排序,二分查找,去重等。
-
erase
-
erase(a,b)
:删除[a,b)
中的元素,其中a,b
是两个迭代器。 -
erase(a)
:删除位置a
上的元素,其中a
是迭代器。 -
时间复杂度:\(O(n)\)
-
写离散化时,可以先
unique
再erase
,最后二分查找即可。int find(int x) { //这样写,从1开始编号 return lower_bound(s.begin(), s.end(), x) - s.begin() + 1; } sort(s.begin(), s.end()); it = unique(s.begin(), s.end()); s.erase(it, s.end());
-
Queue
又称队列,实现的操作较少,但在广搜时还是很好用的。
常见操作:
push(x)
:往队尾插入x
元素,\(O(1)\).front()
:取出队首元素。pop()
:弹出队尾元素。size()
:返回大小(长度)。empty()
:判断队列是否为空。
注意,queue
没有清空操作,所以清空时,需要:while(q.size()) q.pop();
Deque
双端队列,但是支持随机访问。可以说,包含了所有vector
和queue
的操作,还有其他一些操作,下面讲讲额外的操作。
push_front(x)
:往队头插入数字x
。pop_front()
:删除队头元素。push_back(x)
:往队尾插入数字x
。pop_back()
:删除队尾元素。
上述操作均为 \(O(1)\) ,不过在常数上稍微劣于vector
(只用push_back
)。
说完线性表STL,下面说说有关高级数据结构的STL。
Set
Set,顾名思义“集合”,内部采取红黑树实现,是一种有序数据结构,其中不会出现重复的元素。默认从小到大排序。
基本操作:
-
insert(x)
:插入元素x
。 -
erase(x)
- 如果
erase
里面加的是元素而不是迭代器,就会删除所有值为x
的元素,不过这在set
中影响不大,毕竟set
每个数值的元素只会出现一次。 - 如果
erase
里面加的是迭代器,那么就删除对应位置上的元素即可。
- 如果
-
find(x)
:查找元素 \(x\),找到了返回迭代器,找不到返回.end()
。 -
lower_bound(x)
:二分查找大于等于元素x
的第一个数,返回迭代器,如果找不到,返回s.end()
。 -
upper_bound(x)
:找到第一个大于x
的数,剩下同上。 -
s.rbegin()
:反向迭代器。其实就是返回末尾元素的迭代器,一定要和s.end()
区分开,s.end()
是返回末尾元素的后一个位置,并不是末尾元素。 -
s.count(x)
:查找元素x
出现了多少次,在set
中最多出现一次,所以用这个函数检验是否存在元素也是可行的。 -
s.clear()
:清空集合。 -
访问。
set
的访问其实比较蛋疼,不支持随机访问,只能用迭代器一个个走。像这样:set<int>::iterator it; for(it = s.begin(); it != s.end(); it++) *it; // *it 就是当前元素
不过现在支持的
c++11
标准可以这样做:for(auto x : s) x; //x就是当前元素
是不是简洁很多。。。
Multiset
和Set类似,不过这个是可重集。
Map
也是相当实用的STL,甚至我最开始接触的STL就是map。
map一般是当成桶用,即如果要记录一些信息,用map就可以处理值域过大的问题。
这样定义:map<数据类型1,数据类型2> s;
数据类型1表示数组括号里面的类型,即 s[数据类型1]
,数据类型2就表示储存的值。
函数也不多,主要用的:
clear()
:清空。count()
:数数。
map的用途比较广泛,比如可以和字符串哈希一起用,以省去写哈希表的代码。但是要注意,不能直接写 map<string,int>s
,然后直接将字符串丢进map里面,这样还是会超时,需要先将字符串算成 unsigned long long
的哈希值,在丢进 map<ULL,int> s
的map里面。
如果记忆化搜索的状态比较复杂,可以考虑用 map
进行储存。
unordered_map
\(c++11\) 标准下的哈希表,一般来讲效率高于 map
.
Priority_queue
优先队列,内部用堆实现,可以进行查询最值,插入的功能,虽然功能少于set,但是常数很小,比较优秀。
常见操作:
push(x)
:往堆里插入元素x
.pop()
:弹出堆顶元素。top()
:返回堆顶元素。
由于堆不支持随机删除,所以可以使用两个堆,一个储存原来的数(记作 heap1
),一个储存要删除的数(记作 heap2
),如果 heap1
的堆顶和 heap2
的堆顶元素一致,说明当前元素是要删除的元素,两个堆都弹出即可。这样删除的复杂度是 \(O(\log n)\) 的。
还有一种做法,将要删除的数放进桶里,然后每次判断堆顶元素是否需要删除,并做相关标记。但这种做法对值域的要求较为严苛,这里不在赘述。