有问题望指正,谢谢.
虽然算法竞赛不是比的谁STL用的好,但是用的好有时候能帮大忙,哈哈.
下面总结一下STL的使用,这一篇写一些基础的应用,然后还会再有一篇进阶的使用.
这是c++一个参考手册网站,很好的网站:cppreference
在算法竞赛中能用到的东西主要是容器和一些算法.
常用的容器有:
- vector 不定长数组
- stack 栈
- queue 队列
- set 集合
- map 键值对集合
- priority_queue 优先级队列或者说是堆
还有个string不算容器,但很常用
常用的算法:
- sort —排序
- next_permutation —求一个序列的下一个排列
- lower_bound —二分查找,返回序列中第一个比某值大于或等于的迭代器
- upper_bound —二分查找,返回序列中第一个比某值大于的迭代器
- unique —序列去重
- swap —交换
- reverse —倒转序列
下面开始正题
首先是容器需要引进头文件,和容器名同名的头文件
1.vector不定长数组
这个是根据你添加元素动态调整大小的序列容器,大小按照指数增加.通常不需要担心这个引起的时间效率的下降,极端情况下该用数组用数组.用法基本和数组一致,但稍有不同.
这个是C++ Primer上写的几种初始化方法,参考一下.
常用还是直接初始化,就是第一种.下面代码演示:
#include <iostream>
#include <vector> //!!!
using namespace std;
typedef long long ll;
int main(){
vector<int> v; //初始化一个空的vector 里面装int
for(int i=0;i<100;i++){//往vector放了0-99一百个元素-
v.push_back(i);//在尾部追加元素i
}
cout<<"size:"<<v.size()<<endl;//现在有的元素个数
cout<<"capacity:"<<v.capacity()<<endl;//容量
vector<int> v1={1,2,3,4,5};//initializer_list初始化 这个需要c++11支持
cout<<"v1 size:"<<v1.size()<<endl;//现在有的元素个数
cout<<"v1 capacity:"<<v1.capacity()<<endl;//容量 可见这样就不像动态添加那样指数增加容量
v.clear();//v清空了
cout<<"v is empty? "<<v.empty()<<endl;//看v是否为空
cout<<v1[0]<<endl;//vector可以下标访问 重载了[]
//这两种有何区别?
vector<int> matrix[1000];//第一维度是定的也就是你最多matrix[999][这个h可能很大]
vector<vector<int>> matrix1;//这个两个维度都不定
//下面是分别在第0-999行分别放1-1000个元素 这是两种的区别
for(int i=0;i<1000;i++){
for(int j=0;j<=i;j++){
matrix[i].push_back(j);
}
}
for(int i=0;i<1000;i++){
vector<int> tmp;
//matrix1.push_back({}); 这样也可,就不用matrix1.push_back(tmp);了,这样就是放了一个空的 就等于加了一行
for(int j=0;j<=i;j++){
tmp.push_back(j);
}
matrix1.push_back(tmp);
}
return 0;
}
运行结果:
size:100
capacity:128
v1 size:5
v1 capacity:5
v is empty? 1
1
总之,vector只要记住如何增加元素,看容量和可以下标访问就行了.就当个数组用. vector的遍历还是下标访问最好了.
2.stack&queue 栈和队列
这两个差不多,一起说. 看代码就会了.
#include <iostream>
#include <stack>//!!
#include <queue>//!!
using namespace std;
typedef long long ll;
int main(){
stack<int> s;//初始化
s.push(1);//压栈
s.push(2);
s.pop();//弹栈
cout<<"s.top "<<s.top()<<endl;//栈顶元素
cout<<"s.size "<<s.size()<<endl;//栈元素数
cout<<"s.empty "<<s.empty()<<endl;//栈是否为空
queue<int> q;//初始化
q.push(1);//入队
q.push(2);
q.pop();//出队
cout<<"q.front "<<q.front()<<endl;//队首
cout<<"q.back "<<q.back()<<endl;//队尾
cout<<"q.size "<<q.size()<<endl;//队列元素数
cout<<"q.empty "<<q.empty()<<endl;//队列是否为空
return 0;
}
结果:
s.top 1
s.size 1
s.empty 0
q.front 2
q.back 2
q.size 1
q.empty 0
3.set 集合
这是一个非线性容器set默认是带自排序的,底层实现是红黑树,是个树形结构,我们要充分利用这个性质,这个很有用!!!当然STL也提供了哈希集合unordered_set,这个需要c++11,这个在有些时候比自排序set快.下面看代码.
首先通俗解释一下啥是迭代器( i t e r a t o r iterator iterator):可以理解为一个指针,可以访问集合的元素,看名字其实他的作用是遍历迭代容器用的. (在STL里面begin是容器首,end是容器最后一个元素的后一个,通常需要begin!=end来作为结束的条件.)
set不像vector那样方便,没有重载下标运算符,遍历需要用迭代器,但是很有用.
下面看代码:
#include <iostream>
#include <set>
using namespace std;
typedef long long ll;
int main(){
set<int> s; //初始化
s.insert(1);//添加元素
s.insert(2);
s.insert(3);
for(set<int>::iterator it=s.begin();it!=s.end();it++){//迭代器遍历
cout<<*it<<endl;//解引用
}
for(auto it=s.begin();it!=s.end();it++){//auto写更简单,但需要c++11
cout<<*it<<endl;
}
for(int i : s){//范围for
cout<<i<<endl;
}
cout<<s.size()<<endl;//返回s中元素数
auto pos1=s.find(1);//查找值 下面的都是返回迭代器
auto pos2=s.lower_bound(4);//查找第一个比参数大于等于的
auto pos3=s.upper_bound(2);//查找第一个比参数大于的
cout<<*pos1<<endl;
cout<<(pos2==s.end()? "no":"yes")<<endl;//这个找不到的 返回end
cout<<*pos3<<endl;
s.erase(1); //删1
//一个用迭代器删除例子删除奇数 一定这样用!
for(set<int>::iterator it=s.begin();it!=s.end();){
if(*it%2==1){
it=s.erase(it);
}
else{
it++;
}
}
s.clear();//清空set
return 0;
}
4.map键值对集合
这个和set底层结构一样,都是会自动排序,但是按照键排序,我通常都当其为一个数组用,可以理解map是一个“特殊下标类型集合”.很灵活,很有用.
#include <iostream>
#include <map>
using namespace std;
typedef long long ll;
int main(){
map<int,string> mp;//尖括号里面分别填key value的类型
mp[1]="I";//支持下标访问 下标就是key
mp[2]="II";
mp[3]="III";
mp[4]="VI";
cout<<mp.size()<<endl;//存储的键值对个数
cout<<mp.empty()<<endl;//map是否为空
for(map<int,string>::iterator it=mp.begin();it!=mp.end();it++){//四种遍历方式
cout<<"key: "<<it->first<<" value: "<<it->second<<endl;//这种要用-> first成员是key second是value
}
for(auto it=mp.begin();it!=mp.end();it++){
cout<<"key: "<<it->first<<" value: "<<it->second<<endl;
}
for(pair<int,string> it : mp){
cout<<"key: "<<it.first<<" value: "<<it.second<<endl;
}
for(auto it : mp){
cout<<"key: "<<it.first<<" value: "<<it.second<<endl;
}
auto pos1=mp.find(1);//按key查找
auto pos2=mp.lower_bound(2);//按key找比其第一个大于等于的
auto pos3=mp.upper_bound(5);//按key找比其第一个大于的
cout<<pos1->first<<" "<<pos1->second<<endl;
cout<<pos2->first<<" "<<pos2->second<<endl;
cout<<(pos3==mp.end()? "no":"yes")<<endl;//找不到就反回end
mp.erase(pos1);//用迭代器删除
mp.erase(2);//按值删除
mp.clear();//清空
map<int,map<int,int>> mp2d;//二维map 就像二维数组 多维同理
mp2d[1][1]=1;//同样下标访问ok
mp2d[1][-1]=1;
return 0;
}
5.string字符串
字符串很有用,但是比字符数组的效率低,而且输入不能直接用scanf输入,必须cin,但是仍然在很多时候string能带来很多方便.
#include <iostream>
#include <string>
using namespace std;
typedef long long ll;
int main(){
string a;//定义空串
cin>>a;//输入
getline(cin, a);//读一行 类似gets
string b="bbb";//可以这样初始化
a.push_back('a');//在尾部添加字符
a+="aa";//重载了加号 += 都可以直接和字符或者字符串字面量或字符串相加
a+='a';
a="a"+a;
a='a'+a;
b=a;//把a赋给b
cout<<a.length()<<endl;//size/length 都是长度
cout<<a.size()<<endl;
cout<<(a==b)<<endl;//直接可以判断相等
cout<<(a>b)<<endl;//直接可以比大小 按照字典序
for(int i=0;i<a.length();i++){//单字符访问 可以下标
cout<<a[i]<<endl;
}
a="abcde";
int pos1=a.find('d');//找字符 第一个出现的
int pos2=a.find("bc");//找子串
int pos3=a.find("fgh");//这个找不到 返回string::npos 这个常量=-1
cout<<(pos3==string::npos)<<endl;
return 0;
}
6.priority_queue 优先队列
优先队列就是一种队首总是最大最小值的特殊队列,有些题要用到,STL给实现好了.
下面是我以前写的.直接复制上了.
这是常用方法:
方法名 | 作用 |
---|---|
push | 入队 |
pop | 出队 |
top | 队首 |
size | 队中元素数 |
empty | 队是否为空 |
如果题目涉及的只有一个值,不用结构体的话,可以直接用下面两个。
还有这种写法:这样也是队头大
#include <iostream>
#include <queue>
using namespace std;
typedef long long ll;
int main(){
priority_queue<int> q;
q.push(1);
q.push(2);
q.push(3);
while(q.size()){
cout<<q.top()<<endl;
q.pop();
}
return 0;
}
这种写法最方便的,
涉及到结构体的就有些复杂
下面我以一个例子为例:
这里定义了一个结构体,有两个成员,我们的比较规则是先比a,a相等的话再比b,要特别注意我写的重载函数,这个和写结构体排序的比较函数是一样的,但是大于小于号是反着的,这个一定注意。从运行结果也可看出。
现在定义优先队列就变成直接把结构体放到尖括号就行了,因为它重载了运算符,就按照默认的比较了。写的时候一定重载小于号!!!
到这里所有容器就介绍完了,下面说几个常用的算法.
这一块我写一段代码演示.
#include <iostream>
#include <vector>
#include <algorithm>//!!!
using namespace std;
typedef long long ll;
struct node{
int a,b;
};
bool cmp_node(node l,node r){//这个是自定义结构的比较函数
if(l.a==r.a){ //a相等的话比b
return l.b<r.b;
}
return l.a<r.a;
}
bool cmp(int l,int r){//自定义反序的比较函数
return l>r;
}
int main(){
//sort
vector<int> v={5,4,3,2,1};
sort(v.begin(),v.end());//排vector的就填begin和end 默认升序
sort(v.begin(),v.end(),cmp);//这个自定义的 降序
int arr[10]={5,4,3,2,1};
sort(arr,arr+5);//排数组的话begin就是数组首指针也就是数组名 end是最后一个元素的后一个 也就是数组名+几个元素
sort(arr,arr+5,cmp);//arr arr+1 arr+2 arr+3 arr+4 arr+5
//0 1 2 3 4 5
//begin end
//这里排几个就加几 如果你b下标不是从0开始就加上第一个元素的下标就行了
node arrnode[5]={{3,1},{3,2},{2,2},{2,1},{1,1}};
sort(arrnode,arrnode+5,cmp_node);//结构体排序得加自定义排序函数
//next_permutation
string str="1234";
do{//这里为何用do while?
//因为这个函数是求z序列的下一个排列返回是否有下一个排列并且把序列变成下一个排列 while的话就会落下第一个排列
cout<<str<<endl;
}while(next_permutation(str.begin(), str.end()));
//当然数组也可以
//lower_bound&upper_bound
//因为是二分法所以前提是序列有序
//lower_bound —二分查找,返回序列中第一个比某值大于或等于的迭代器
//upper_bound —二分查找,返回序列中第一个比某值大于的迭代器
//一开始你或许会疑惑为啥是这样设计 用用就会发现这个很好用
vector<int> v1={1,2,4,5};
auto pos1=lower_bound(v1.begin(), v1.end(), 2);//找不到就返回end
auto pos2=upper_bound(v1.begin(), v1.end(), 3);
cout<<*pos1<<endl;//2
cout<<*pos2<<endl;//4
int arr1[100]={1,2,4,5};
//数组的迭代器是啥呢?
//-指针
int *pos3=lower_bound(arr1,arr1+4, 2);
auto pos4=upper_bound(arr1,arr1+4, 2);//当然也可
cout<<*pos3<<endl;
cout<<*pos4<<endl;
//unique 序列去重 这个用的少 但要知道
//作用 把重复的元素挪到后面 返回去重完最后一个元素的后一个也就是新序列的end
//前提也得序列有序
vector<int> v2={1,1,2,3,3,3,4,5};
auto posend=unique(v2.begin(), v2.end());
vector<int> v3(v2.begin(),posend);//v3: 1 2 3 4 5
//当然你可以循环放进去
//swap 这个常用的
int a=2,b=3;
swap(a, b);
//reverse 把序列反过来
reverse(v3.begin(), v3.end());
//v3: 5 4 3 2 1
return 0;
}
看完你会发现所有的“范围”都是前闭后开的. 这一点很重要!!!