#include <vector>
size()
返回容器内元素的多少
empty()
返回一个bool类型,表明vector是否为空。二者的时间复杂度都是O(1)。
所有的STL容器都支持这两个方法,含义也相同,之后我们就不再重复给出。
clear函数把vector清空除了队列queue、priority_queue、stack其他STL容器都有clear函数
迭代器
迭代器就像STL容器的“指针”,可以用星号“*”操作符解除引用。
一个保存int的vector的迭代器声明方法为:
vector<int>::iterator it;
vector的迭代器是“随机访问迭代器”,可以把vector的迭代器与一个整数相加减,其行为和指针的移动类似。可以把vector的两个迭代器相减,其结果也和指针相减类似,得到两个迭代器对应下标之间的距离。
begin/end
begin函数返回指向vector中第一个元素的迭代器。例如a是一个非空的vector,则*a.begin()
与a[0]
的作用相同。
所有的容器都可以视作一个“前闭后开”([begin(),end())
)的结构,end函数返回vector的尾部,即第n个元素再往后的“边界”。*a.end()与a[n]都是越界访问,其中n=a.size()。
下面两份代码都遍历了vector<int>a
,并输出它的所有元素。
for (int i = 0; i < a.size(); i ++) cout << a[i] << endl;
//绝大部分都不会使用迭代器来访问vector
for (vector<int>::iterator it = a.begin(); it != a.end(); it ++) cout << *it << endl;
for (auto it = a.begin(); it != a.end(); it ++) cout << *it << endl;
for (int x : a) cout << x <<" ";
front/back 都是O(1)的时间复杂度
front函数返回vector的第一个元素,等价于*a.begin()
和 a[0]
。
back函数返回vector的最后一个元素,等价于*==a.end()
和 a[a.size() – 1]
。
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main(){
vector<int> a({1,2,3});
a.push_back(4);
for(auto x : a) cout << x <<" ";
cout<<endl;
a.pop_back();
for( auto x : a) cout << x << " ";
cout<<endl;
return 0;
}
//运行结果:
1 2 3 4
1 2 3
vector是基于倍增的思想
//比如说我这里定义了一个vector
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main(){
vector<int> a({1,2,3});
cout << a.capacity() << endl;
cout << a.size() << endl;
return 0;
}
//运行结果
3
3
//我们发现vector的容器的容量和元素的个数大小一样
//那么,如果我插入一个元素这两个数字会发生变化吗?
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main(){
vector<int> a({1,2,3});
a.push_back(4);
cout << a.capacity() << endl;
cout << a.size() << endl;
return 0;
}
//运行结果
6
4
//我们就很容易发现vector的容器的大小变成了6,但此时元素的个数变成4
//容器的大小为什么会变成6呢?因为原来的容器大小是3,利用容器大小的倍增思想,每次发现插入的元素大于容器的大小,那么容器就会倍增2,然后把原来的元素复制到新扩大的vector容器里,来达到扩大容器的目的,这样就可以放入更多的元素;如此以往,如果再加的话会变成12……
我们如何分析vector的效率呢?(vector的原理)
我们发现我们新开的vector初始的容量大小就是元素的大小,但是随着插入元素的增多,容量会倍增,并且将原有的小的容器的元素复制到扩大的新的数组上来;复制元素效率不就下来了吗?
假如说我们插入了n个元素,那么vector会复制多少次呢?(复制一个元素算复制一次)
在插入的数量达到 n 2 \frac{n}{2} 2n,会复制 n 2 \frac{n}{2} 2n,那么在此之前就是 n 4 \frac{n}{4} 4n,(复制 n 4 \frac{n}{4} 4n次),然后一次类推vector复制了n( 1 2 \frac{1}{2} 21+ 1 4 \frac{1}{4} 41+ 1 8 \frac{1}{8} 81+……)
1 2 \frac{1}{2} 21+ 1 4 \frac{1}{4} 41+ 1 8 \frac{1}{8} 81+……<1(可以用数学证明,这里时间紧迫就不去证明了)
最后复制的次数会小于n
#include <queue>
主要包括循环队列queue和优先队列priority_queue一个堆,可以实时返回所有数的最大值两个容器
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
int main(){
queue<int> q;//队列
priority_queue<int> a;//大根堆。每次弹出最大的值
priority_queue<int, vector<int>, greater <int>> b;//小根堆,每次弹出最小的值
//如果对结构体创建大根堆的话要重载小于号
struct Rec{
int a,b;
// //定义Rec结构体的大根堆要重载小于号
// bool operator < (const Rec& t) const{
// return a<t.a;
// }
//定义Rec结构体的小根堆要重载大于号
bool operator > (const Rec& t) const{
return a>t.a;
}
};
//priority_queue<Rec > d;//以Rec结构体为对象创建的d大根堆
priority_queue<Rec ,vector<Rec>,greater<Rec>> d;//以Rec结构体为对象创建的d小根堆
return 0;
}
普通队列用法:
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
int main(){
queue<int> q;//队列
//只能在队头插入,在队尾弹出
q.push(1);//在队头插入元素
q.push(2);
q.push(3);
//插入的队列的顺序为(队尾)1、2、3(队头)
q.pop();//弹出队尾元素
cout<<q.front()<<endl;//返回队头元素
cout << q.back() <<endl;//返回队尾元素
return 0;
}
//运行结果
2
3
优先队列用法(以大根堆为例):
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
int main(){
priority_queue<int > a;//大根堆
a.push(1);
a.push(3);
a.push(2);
cout<< a.top()<<endl;//取最大值
a.pop();
cout<< a.top()<<endl;
return 0;
}
//运行结果
3
2
queue和priority_queue都不能使用clear()函数,那么清空队列?
q=queue<int>();//重新定义一个队列覆盖原有的队列达到清空的目的
#Include <stack>
先进后出的原则—这样的一个数据结构
#include <iostream>
#include <algorithm>
#include <stack>
using namespace std;
int main(){
stack<int > stk;
stk.push(1);//插入一个元素
stk.push(2);
stk.push(3);
cout<< stk.top()<<endl;;//返回栈顶元素
stk.pop();//弹出栈顶元素
cout<< stk.top()<<endl;
return 0;
}
//运行结果
3
2
#include <deque>
双端队列deque是一个支持在两端高效插入或者删除的连续线性存储空间。它就像是vector和queue的结合。与vector相比,deque在头部增添元素只需要O(1)的时间;但是vector在尾部是O(1),但是在头部就是O(n);与queue相比,deque就像数组一样可以随机访问
#include <iostream>
#include <algorithm>
#include <deque>
using namespace std;
int main(){
deque<int> a;
a.push_front(1);//在队头插入元素
a.push_front(3);
a.push_front(5);
a.push_back(2);//在队尾插入元素
a.push_back(4);
a.push_back(6);
//这个时候的deque存取的是:5 3 1 2 4 6
// cout<< a.begin()<<" "<<a.end()<<endl;//迭代器的begin()和end()
cout<<a.front()<<" "<<a.back()<<endl;//返回deque的队头和队尾元素
for( int i=0;i<a.size();i++) cout<<a[i]<<" ";//可以使用[]访问deque的元素
cout<<endl;
a.pop_back();//弹出队尾元素
a.pop_front();//弹出队头元素
for( int i=0;i<a.size();i++) cout<<a[i]<<" ";
cout<<endl;
a.clear();//清空deque的元素
cout<< a.size() <<endl;
return 0;
}
//运行结果
5 6
5 3 1 2 4 6
3 1 2 4
0
#include <set>
头文件set主要包括set
和multiset
两个容器,分别是**“有序集合”和“有序多重集合”,(注意是有序的)即前者的元素不能重复,而后者可以包含若干个相等的元素**。set和multiset的内部实现是一棵红黑树,它们支持的函数基本相同。
红黑树不会QAQ,心态emo了
红黑树是一个有如下规则的二叉排序树
1.结点是红色或黑色。
2.根结点是黑色。
3.每个叶子结点都是黑色的空结点(NIL结点)。
4 每个红色结点的两个子结点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色结点)
5.从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点。
具体红黑树的内容可以看:漫画:什么是红黑树? - 掘金 (juejin.cn)
#include <iostream>
#include <algorithm>
#include <set>
using namespace std;
int main(){
set<int> a;//元素不能重复
multiset<int> b;//元素可以重复
//也可以定义struct
struct Rec{
int x,y;
//不过使用基于结构体的set集合的时候结构体内要重载小于号(因为set内部是有比较的)
bool operator < (const Rec& t) const{
return x<t.x;
}
};
//重载<或者>等我们就可以定义一个Rec类型的set集合
set<Rec> c;
return 0;
}
//size()、empty()、clear()这三个函数的使用和效果和vector一样
迭代器:
set和multiset的迭代器称为“双向访问迭代器”,不支持“随机访问”,支持星号(*)解除引用,仅支持”++”和–“两个与算术相关的操作。
设it是一个迭代器,例如set<int>::iterator it;
若把it++,则it会指向“下一个”元素。这里的“下一个”元素是指在元素从小到大排序的结果中,排在it下一名的元素。同理,若把it–,则it将会指向排在“上一个”的元素。(有序集合);时间复杂度是O(logn)
begin/end
返回集合的首、尾迭代器,时间复杂度均为O(1)。
s.begin() 是指向集合中最小元素的迭代器。
s.end() 是指向集合中最大元素的下一个位置的迭代器。换言之,就像vector一样,是一个“前闭后开”的形式。因此–s.end()是指向集合中最大元素的迭代器。
#include<iostream>
#include<algorithm>
#include<set>
using namespace std;
int main(){
set<int> s;
s.insert(1);//s.insert(x)把一个元素x插入到集合s中,时间复杂度为O(logn)。在set中,若元素已存在,则不会重复插入该元素,对集合的状态无影响。时间复杂度为O(logn)
s.insert(2);
s.insert(3);
cout<< *s.find(1) <<endl;//s.find(x) 在集合s中查找等于x的元素,并返回指向该元素的迭代器。若不存在,则返回s.end()。时间复杂度为O(logn)。
//因此我们可以使用if(s.find(x)==s.end())来判断是否能在set集合里找到x;时间复杂度为O(logn)
if(s.find(4)==s.end())//判断x在a中的是否存在
cout<<"4不存在集合s中"<< endl;
cout<<*s.lower_bound(2)<<endl;//找到大于等于2的最小元素的迭代器;时间复杂度为O(logn)
cout<<*s.upper_bound(2)<<endl;//找到大于2的最小的元素的迭代器;时间复杂度为O(logn)
set<int>::iterator it=s.begin();
s.erase(it);//删除迭代器it指向的元素;时间复杂度为O(logn)
s.erase(2);//把所有值为2的元素;时间复杂度为O(k+logn)其中k为删除的元素的个数
cout<<s.count(3)<<endl;//s.count(x) 返回集合s中等于x的元素个数,时间复杂度为 O(k +logn),其中k为元素x的个数。
return 0;
}
//运行结果
1
4不存在集合s中
2
3
1
#include<unordered_set>
#include<iostream>
#include<algorithm>
#include<undered_set>
using namespace std;
int main(){
//相对于set的有序集合,undered_set是一个无序集合
undered_set<int> s;//底层实现是一个哈希表,不能存储重复元素
//undered_set除了set中的lower_bound()和upper_bound()函数无法使用之外,其余函数都可以使用,时间复杂度都是O(1),但不支持二分
undered_multiset<int> c;//底层实现是一个哈希表
return 0;
}
#include <map>
map容器是一个键值对key-value的映射,其内部实现是一棵以key为关键码的红黑树。Map的key和value可以是任意类型,其中key必须定义小于号运算符。其实y总说还有一种mutimap(但是不经常用)这里就不说了
#include<iostream>
#include<algorithm>
#include<map>
using namespace std;
int main(){
map<int,int> a;
a[1]=3;
a[520]=1314;
cout<<a[520]<<endl;
map<string,int> b;
b["lml"]=38;
cout<<b["lml"]<<endl;
map<string,vector<int>> c;
c["lml"]=vector<int>({1,2,3,4});
c["wyq"]=vector<int>();
cout<<c["lml"][1]<<endl;
cout<<c["wyq"].size()<<endl;
return 0;
}
//运行结果
1314
38
2
0
size/empty/clear/begin/end均与set类似。
Insert/erase
与set类似,但其参数均是pair<key_type, value_type>。插入的必须是一个二元组
#include<iostream>
#include<algorithm>
#include<map>
using namespace std;
int main(){
map<string,vector<int>> b;
b.insert({"lml",{1,2,3,4}});
cout<<b["lml"][1]<<endl;
return 0;
}
//运行结果
2
h.find(x) 在变量名为h的map中查找key为x的二元组;返回的是一个迭代器
cout<<(b.find("lml")==b.end())<<endl;
[]操作符
h[key] 返回key映射的value的引用,时间复杂度为O(logn)。
[]操作符是map最吸引人的地方。我们可以很方便地通过h[key]来得到key对应的value,还可以对h[key]进行赋值操作,改变key对应的value。
#include <bitset>
#include<iostream>
#include<algorithm>
#include<bitset>
using namespace std;
int main(){
bitset<1000> a,b;//定义长度为1000的01串
a[0]=1;a[1]=1;
cout<<a[0]<<" "<<a[1]<<" "<<a[2]<<endl;//没有设置为1的位置默认为0
cout<<a.count()<<endl;//count()函数可以返回当前01串中1的个数
a&=b;
a|=b;//可以进行位运算
b.set(3);//将下标为3的b的这一位上置1
cout<<b[3]<<endl;
b.reset(3);//将下标为3的b的这一位上置0
cout<<b[3]<<endl;
return 0;
}