大纲
一、基础STL 二、STL的迭代器遍历 三、for(auto it:S)遍历STL 四、STL函数
一、基础STL
目录:
array 普通数组
vector 边长数组
string 字符串
sort 快速排序
auto 任意类型
queue 队列
deque 双端队列
set 集合
stack 栈
map 哈希,映射
priority_queue 优先队列
pair 整合类型
lower_bound 二分下界
upper_bound 二分上界
-------------------------
0.科普知识&无用知识:STL的数组
萌新学的数组就是一个:
type a[MAX_LEN] ;
但其实除了这种数组,STL还有一种数组(但是跟这个没啥区别,耗时还大...):
定义方式:
array < type , MAX_LEN > a ;
但是它有一些功能函数,这是比较香的,不过都可以用数组模拟,所以如果数组能轻松解决的东西还是尽量不要用STL的数组。
说了这么多全是废话...
1.边长数组:vector
vector和普通数组的区别是vector不需要定义数组长度,数组需要提前定义数组长度,相比vector更浪费空间。
vector的定义方式:
vector < type > 名字 ;
vector的基本操作:
vector < int > v ; 定义类型为int的vector数组
v[index] v的第index个元素
v.size () v的长度
v.push_back (val) ; 将值val加入数组v
v.erase (v.begin () + index - 1) ;
删除第index个元素
v.insert (v.begin () + index , val) ;
在index的位置插入值val
unique (v.begin () , v.end ()) ;
将v去重。
遍历:
for (it = v.begin (); it != v.end (); it ++)
int x = * it ;
vector代码实现:
vector < int > v ;
for (int i = 1; i <= 10; i ++)
v.push_back (i) ;
for (int i = 0; i < v.size (); i ++)
cout << v[i] << ' ' ;
v.erase (v.begin ()) ;
for (int i = 0; i < v.size (); i ++)
cout << v[i] << ' ' ;
v.insert (i , 1) ;
for (int i = 0; i < v.size (); i ++)
cout << v[i] << ' ' ;
2.字符串:string
string和字符数组的区别与vector类似,也是不需要定义数组的长度。
string的定义方式:
string 名字 ;
string的基本操作:
string str ;
cin >> str ; 读入字符串str
int len = str.size () ; 获取字符串的长度
cout << str ; 输出字符串
str[index] 字符串的第index个字符
str.erase (index , len) ; 从第index个字符开始,删除它以及后面的len个字符
str.insert (index , insert_str) ; 在index的位置插入字符串insert_str
str.pop_back () 删除末尾字符
str.back () 末尾字符
str += st 末尾加上字符串st
string实现代码:
string str = "Minecraft_Dream" ;
str.erase (9 , 1) ;
cout << str << endl ;
str.pop_back () ;
cout << str << endl ;
str += "m" ;
cout << str ;
3.排序:sort
sort是STL内置的排序,是快速排序的函数,时间复杂度:O(nlogn) 。
sort (a + 1 , a + 1 + n) ;
排序a数组。
sort (a + l , a + 1 + r) ;
排序区间[l, r]
快排没有稳定性,stable_sort是稳定的快排,用法同sort。
stable_sort (a + 1 , a + 1 + n) ;
stable_sort (a + l , a + 1 + r) ;
sort实现代码:
int a[N] , n ;
int main () {
cin >> n ;
for (int i = 1; i <= n; i ++)
cin >> a[i] ;
sort (a + 1 , a + n + 1) ;
for (int i = 1; i <= n; i ++)
cout << a[i] << ' ' ;
}
4.任意类型定义:auto
auto支持任意类型的信息,一半用来简化代码,用法:
就比如定义了一个结构体:
struct Edge {
int a , b , w ;
} edges ;
接下来要存储edges,可以:
方法1:
Edge e = edges ;
方法2:
auto e = edges ;
auto还可以是迭代器类型的:
遍历map:
方法1:
map < int , int > :: iterator it ;
for (it = mp.begin (); it != mp.end (); it ++) {
int x = it.first , y = it.second ;
}
方法2:
for (auto it : mp) {
int x = it.first , y = it.second ;
}
auto实现代码:
struct Edge {
int a , b ;
} egde ;
int main () {
cin >> egde.a >> egde.b ;
auto e = edge ;
cout << e.a << ' ' << a.b ;
}
5.队列
队列是数据结构,是先进先出的数据结构,出队时把队头pop掉,入队时在队尾入队。
实现队列:
方法1:
使用数组实现。
int hh = 0 , tt = -1 , q[N] ;
q[++ tt] = val ; 进队
hh ++ ; 出队
方法2:
STL自带队列实现。
定义方式:queue < type > q ;
queue < int > q ;
只不过STL的队列没有队头和队尾的位置,只存值,不过题目不一般也不需要你的队头队尾位置。
基本操作:
q.size () 队列的长度
q.push (val) ; 将val入队
q.pop () ; 将队首元素出队
q.back () 队尾元素
q.front () 队头元素
q.empty () 判断队列是否为空
queue实现代码:
queue < int > q ;
int n , x ;
cin >> n ;
while (n -- ) {
cin >> x ;
q.push (x) ;
}
while (! q.empty ()) {
cout << q.front () << ' ' ;
q.pop () ;
}
6.双端队列
双端队列是队列的升级版,与队列的区别是可以在两端入队和出队。
实现双端队列:
方法1:
数组实现。
int hh = 0 , tt = -1 , q[N] ;
q[++ tt] = val ; 队尾入队
tt -- ; 队尾出队
q[++ hh] = val ; 队头入队
hh ++ ; 队头出队
方法2:
STL自带双端队列实现:
定义方式:deque < type > dq ;
基本操作:
dq.size () 队列长度
dq.push_back (val) ; 队尾入队
dq.pop_back () ; 队尾出队
dq.push_front () ; 队头入队
dq.pop_front () 队头出队
dq.empty () 判断双端队列是否为空
deque实现代码:
deque < int > dq ;
int n , x , f ;
cin >> n ;
while (n -- ) {
cin >> f >> x ;
if (f == 1) {
dq.push_front (x) ;
} else {
dq.push_back (x) ;
}
}
while (! dq.empty ()) {
cout << dq.front () << ' ' ;
dq.pop_front () ;
}
7.集合
集合set是一个关联容器,它的功能很花,很方便,但是常数可能会大一些。
优点:
功能完善,方便。
缺点:
①耗时大
②无法通过下标访问元素
接下来介绍一下set的基本功能
定义方式:
set < type > s ;
基本操作:
s.size () 集合的长度
s.clear () ; 清空集合
s.empty () 判断集合是否为空
s.erase () 删除元素
s.insert () 插入元素
s.lower_bound () 二分下界
s.upper_bound () 二分上界
s.count () 统计元素在集合中出现次数
swap () 交换集合
set的遍历可以用:来遍历:
for (int x : s) {
cout << x << ' ' ;
}
set实现代码:
set < int > s ;
for (int i = 1; i <= 10; i ++)
s.insert (i) ;
for (int x : s) {
cout << x << ' ' ;
}
8.栈
栈也是数据结构,栈是先进后出的数据结构,栈只有一个栈顶。
栈的实现:
方法1:
数组模拟。
int tt = 0 , stk[N] ;
stk[++ tt] = val ; 元素入栈
tt -- ; 栈顶出栈
stk[tt] 栈顶元素
方法2:
STL自带栈。
定义方法:stack < int > stk ;
基本操作:
stk.push (val) 元素入栈
stk.pop () 栈顶出栈
stk.top () 栈顶元素
stk.size () 栈内元素个数
注意:stack也不能通过下表访问元素,一般使用不停记录栈顶,接下来栈顶出栈的方法访问元素。
stack实现代码:
stack < int > stk ;
void input_stack () {
for (int i = 1; i <= 10; i ++)
stk.push (i) ;
}
void output_stack () {
while (! stk.empty ()) {
cout << stk.top () << ' ' ;
stk.pop () ;
}
}
int main () {
input_stack () ;
output_stack () ;
input_stack () ;
stk.pop () ;
output_stack () ;
}
9.映射表
映射表(map)本质上是散列的hash,与桶数组相似,解决计数一类的问题。
定义方式:map < key_type , value_type > hash_ ;
可以通过一个key找到相应的value,解决了桶数组只能解决整数计数的问题。
常见作用:
①字符串查找+统计
用于统计字符串出现次数以及存储。
map < string , int > hash_ ;
string str ;
int m ;
cin >> m ;
while (m -- ) {
char q ;
cin >> q >> str ;
if (q == 'Q') {
cout << _hash[str] << endl ;
} else {
_hash[str] ++ ;
}
}
②大桶数组
假设整数范围很大(比如1e9),使用普通的数组是不能实现整数计数的,但是可以使用map将整数映射成出现次就可以了。
map < int , int > hash_ ;
int n , x , q ;
cin >> n >> q;
while (n -- ) {
cin >> x ;
hash_[x] ++ ;
}
while (q -- ) {
cin >> x ;
cout << hash_[x] << endl ;
}
常见问题:如果在映射表中没有查询的值,那么怎么办呢?
解决方案1:全局初始化
把映射表定义在main函数外面,可以自动初始化0,所以这个时候就不会有不输出的情况了。
解决方案2:调用函数find
介绍一个map的函数:find
hash_.find (x) 是查找x所对应的迭代器(这个可以不用明白),如果x没有被映射过,那么hash_.find (x) 的返回值就是hash_.end ()。
就是最后一个迭代器+1(这个也不用明白,搞清楚返回值是hash_.end ()就可以了)
所以检验x的映射情况就可这样写:
map < int , int > hash_ ;
.......
int x ;
cin >> x ;
if (hash_.find (x) != hash_.end ())
cout << "映射过" ;
else
cout << "没有映射过" ;
map经常可以代替二分查找,方便极了(总之STL万岁!)
10.链表
链表可以用O(1)的时间复杂度实现删除和插入,实现方法:
定义方式:list < int > l ;
l.front() 是第一个元素
l.back() 是最后一个元素
l.insert() 是插入元素
l.erase() 是删除元素
l.pop_back() 删除最后一个元素
l.pop_front() 删除第一个元素
l.push_back() 在链表的末尾添加一个元素
l.push_front() 在链表的头部添加一个元素
l.size() 链表长度
11.优先队列
优先队列本质是一个堆维护的。
定义类型:
priority_queue < type , Container , Functional > heap ;
type是类型,Container是实现它的容器(如vector啥的), Functional是它的类型
常用写法:
维护升序序列:
priority_queue < int , vector < int > , greater < int > > up_heap ;
维护降序序列:
priority_queue < int , vector < int > , less < int > > down_heap ;
总之优先队列是维护上升和下降的工具罢了,基本操作基本同于queue:
heap.top () 队头元素
heap.pop () 出队
heap.size () 获取队列长度
heap.empty () 判断队列是否为空
优先队列可以算是一个工具,实时维护上升或下降,像一些贪心的题,动态的修改,如果每一次都排序就会超时。
这种题就可以用优先队列,因为维护的时间复杂度时log的,不会超时了。
priority_queue例题:合并果子
就是维护一个升序的序列(贪心)。
实现代码:
priority_queue < int , vector < int > , greater < int > > up_heap ;
int n , x , res ;
int a , b ;
void get_top (int & x) {
x = up_heap.top () ;
up_heap.pop () ;
}
void push_val (int x) {
up_heap.push (x) ;
}
int main () {
cin >> n ;
while (n -- ) {
cin >> x ;
up_heap.push (x) ;
}
while (up_heap.size () > 1) {
get_top (a) ;
get_top (b) ;
res += a + b ;
push_val (a + b) ;
} cout << res ;
}
12.整合类型
pair可以整合两个类型:
pair < type1 , type2 > p ;
比如:
pair < int , char > p ;
访问int就是p.first
访问char就是p.second
如果需要赋值可以这样赋值:
假设要赋值int为1,char为'a':
p = {1 , 'a'} ;
即可
pair还可以当数组,pair只是个工具,没有什么特别的作用。
13.二分下界lower_bound
lower_bound (x)定义为:
第一个>=x的位置。
调用STL函数的方法就是:
int p = lower_bound (a + 1 , a + 1 + n , x) - a - 1 ;
14.二分上界upper_bound
upper_bound (x)定义为:
最后一个>=x的位置。
调用STL函数的方法就是:
int p = upper_bound (a + 1 , a + 1 + n , x) - a - 2 ;
注意这里需要多-1,系统函数配置的原因
二、迭代器遍历STL
首先介绍一下迭代器,迭代器是一个指向STL值元素的指针,定义类方式:
如vector:vector < int > :: iterator it ;
如list:list < int > :: iterator it ;
切换为下一个迭代器可以直接++,第一个迭代器有系统的函数:.begin ()和.end ()
注意.end ()不是最后一个迭代器,是最后一个迭代器的后一个迭代器。
所以最开始如果是要正序遍历容器的话,就可以这样遍历:
vector < int > :: iterator it ;
for (it = v.begin (); it != v.end (); it ++)
但是如果需要倒叙遍历容器的话,也可以是实现:
.rbegin ()是倒叙的.begin (),.rend ()是倒叙的.end ()
注意:这个iterator就不对了,是:
reverse_iterator
所以倒叙遍历可以这样:
vector < int > :: reverse_iterator it ;
for (it = v.rbegin (); it != v.rend (); it ++)
最后说一下怎样通过当前遍历的it访问元素:
但是像那种map,是有多个type的容器(就如map),访问还是一样的,就是访问元素的时候变了一下:
it.first:Key 映射的东西key
it.second:Value 映射出来的值value
*it即可访问。
基本访问:
vector:
vector < int > :: iterator it ;
for (it = v.begin (); it != v.end (); it ++)
vector < int > :: reverse_iterator rit ;
for (rit = v.rbegin (); rit != v.rend (); rit ++)
map:
map < int , int > :: iterator it ;
for (it = hash_.begin (); it != hash_.end (); it ++)
map < int , int > :: reverse_iterator rit ;
for (rit = hash_.rbegin (); rit != hash_.rend (); rit ++)
set:
set < int > :: iterator it ;
for (it = s.begin (); it != s.end (); it ++)
set < int > :: reverse_iterator rit ;
for (rit = s.rbegin (); rit != s.rend (); rit ++)
list:
list < int > :: iterator it ;
for (it = s.begin (); it != s.end (); it ++)
list < int > :: reverse_iterator rit ;
for (rit = s.rbegin (); rit != s.rend (); rit ++)
后面的一样。
总之总结一下就是:
定义类型 + :: iterator it ;
for (it = 名字.begin (); it != 名字.end (); it ++)
定义类型 + :: reverse_iterator rit ;
for (rit = 名字.rbegin (); rit != 名字.rend (); rit ++)
三、for(auto it:S)遍历STL
使用新版本与auto结合的用法:
for (auto item : 名字)
接比如遍历vector就可以这样:
vector < int > v ;
for (auto item : v) cout << item << ' ' ;
遍历map:
map < int , int > mp ;
for (auto item : mp) cout << item.first << ' ' << item.second << endl ;
遍历字符串也可使用:
string str ;
for (auto c : str) cout << c ;
这样就很方便的遍历了,就是对于那种不能用下标访问的容器,使用:是不能完成倒叙的,或者说把正序的出来的结果存下来,然后倒叙遍历。
四、函数
前面已经介绍了STL的三个函数:
sort()
lower_bound()
upper_bound()
现在再介绍几个常用函数:
(1).reverse
reverse可以反转数组、字符串、vector等:
string str ;
vector < int > v ;
int a[N] ;
反转str:
reverse (str.begin () , str.end ()) ;
反转vector:
reverse (v.begin () , v.end ()) ;
反转数组:
reverse (a + 1 , a + n + 1) ;
(2).binary_search
前面是上界和下界,二binary_search只是单纯的的二分查找x是否存在与数组(或vector)中,如果找到返回true,否则返回false:
bool as = binary_search (a + 1 , a + n + 1 , x) ;
bool vs = binary_search (v.begin () , v.end () , x) ;
这些都是二分查找,单次查找的时间复杂度是:O(log_2(n))
(3).find
find是一个字符串的函数:
string str , p ;
现在比如要在字符串str里面找一个字符串p的起始位置,就可以调用函数find,如果没有找到就会返回string::npos
实例(找字串p):
if (str.find (p) == string :: npos)
puts ("no") ;
else
cout << str.find (p) ;
(4).stoi
stoi可以将字符串转换成数字,如:
string num ;
cin >> num ;
int x = stoi (num) ;
cout << x ;
(5).stringstream
stringstream是转换的工具,可以将两个类型互相转换(如int->string,string->int,char->string,string->char),例:
stringstream有输入流和输出流(就像cout<<和cin>>):
string str ;
stringstream sins ;
int num ;
cin >> str ;
sins << str ; 将字符串进入工具stringstream
sins >> num ; 将当前工具里的转为int类型
cout << num ;
如果字符串中有空格,那么一次>>只能输入一段(就会被空格截断),所以可以while(sins>>x)不停的输出:
string str ;
getline (cin , str) ;
stringstream sins ;
int x ;
while (sins >> x) cout << x ;
(6).next_permutation
next_permutation是全排列的函数,是下一个全排列,可以这样解决全排列的问题:
int a[N] , n ;
int main () {
cin >> n ;
for (int i = 1; i <= n; i ++)
a[i] = i ;
do {
for (int i = 1; i <= n; i ++)
cout << a[i] << ' ' ;
puts (" ") ;
} while (next_permutation (a + 1 , a + 1 + n)) ;
}
(7).prev_permutation
prev_permutation也是全排列函数,是上一个全排列,也可以解决全排列的问题:
int a[N] , n ;
int main () {
cin >> n ;
for (int i = 1; i <= n; i ++)
a[i] = n - i + 1 ;
do {
for (int i = 1; i <= n; i ++)
cout << a[i] << ' ' ;
puts (" ") ;
} while (prev_permutation (a + 1 , a + 1 + n)) ;
}