#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(反向迭代器rbegin()指向最后一个元素,rend()指向第一个元素,和begin()相等)
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
//在指定位置插入元素:insert(lterator it,int a)
vector <int> s;
s.insert(i,n);//在s的第i个元素后面插入a
//代码参考:
#include <iostream>
#include <stdlib.h>
#include <vector>
using namespace std;
int main()
{
vector <int> s({6,2,3});
s.insert(s.begin()+0,4);
for(int i=0;i<s.size();i++)
cout<<s[i]<<" ";
system("pause");
return 0;
}
//运行结果:
4 6 2 3
s.insert(s.begin()+1,4);//运行结果就变成了6 4 2 3
//重新分配元素assign(iterator first,iterator last)或者assgin(size_type n,size_type u)
//第一种是使用迭代器,这种方式一般用不到
//第二种方式是重新分配为n个元素,每个元素都是u
int main()
{
vector <int> s({6,2,3});
s.assign(3,4);
for(int i=0;i<s.size();i++)
cout<<s[i]<<" ";
system("pause");
return 0;
}
//运行结果:
4 4 4
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
vector还可以进行比较运算,按照字典序进行排序和长度无关,从第一个元素按照ASCII值进行比较
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> a(3,4),b(4,3);
if(a>b)
cout<<"yes"<<endl;
return 0;
}
//yes
#include <string>
substr(int pos,int len)
:从下标pos开始截取长度为len的字串。如果没有这两个参数,那么就不截取字串;如果pos的位置大于string的长度,那么就会报错;如果从pos+len>string的长度,那么只会截取到string结束的部位。
c_str()
:将string类型的字符串转换成C语言的char数组
+
:string可以利用+
对字符串进行连接;(在字符串后进行连接)
#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_front()向队头插入元素
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;
//lower_bound()与upper_bound()是set最核心的两个操作
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为删除的元素的个数
/*
erase():如果参数是一个元素数字,那么删除与这个数字相同的节点
如果参数是一个迭代器,那么删除这个迭代器
*/
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>
与unordered_multiset
底层都是是哈希表,不支持lower_bound()与upper_bound()
#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(但是不经常用)这里就不说了。这两个都是基于平衡二叉树
map是一个十分玄幻的容器----y总
#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
声明
map<key_type, value_type> name;
例如:
map<long, long, bool> vis;
map<string, int> hash;
map<pair<int, int>, vector<int>> test;
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 <unordered_map>
与unordered_multimap
底层一样都是哈希表来实现
哈希表实现增删改查的时间复杂度是O(1)。
unordered_map是C++中的哈希表,可以在任意类型与类型之间做映射。
基本操作
引用头文件(C++11):#include <unordered_map>
定义:unordered_map<int,int>
、unordered_map<string, double>
…
插入:例如将(“ABC” -> 5.45) 插入unordered_map<string, double>
hash中,hash["ABC"]=5.45
查询:hash[“ABC”]会返回5.45
判断key是否存在:hash.count("ABC") != 0
或 hash.find("ABC") != hash.end()
遍历
for (auto &item : hash) { cout << item.first << ' ' << item.second << endl; }
或
for (unordered_map<string, double>::iterator it = hash.begin(); it != hash.end(); it ++ ) { cout << it->first << ' ' << it->second << endl; }
进阶操作
如果想让自定义的class作为key(unordered_map<key,value>
)来使用unordered_map,需要实现:
(1) 哈希函数,需要实现一个class重载operator(),将自定义class变量映射到一个size_t类型的数。一般常用std::hash模板来实现。
(2) 判断两个自定义class类型的变量是否相等的函数,一般在自定义class里重载operator==。
示例代码:
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
class Myclass
{
public:
int first;
vector<int> second;
// 重载等号,判断两个Myclass类型的变量是否相等
bool operator== (const Myclass &other) const
{
return first == other.first && second == other.second;
}
};
// 实现Myclass类的hash函数
namespace std
{
template <>
struct hash<Myclass>
{
size_t operator()(const Myclass &k) const
{
int h = k.first;
for (auto x : k.second)
{
h ^= x;
}
return h;
}
};
}
int main()
{
unordered_map<Myclass, double> S;
Myclass a = { 2, {3, 4} };
Myclass b = { 3, {1, 2, 3, 4} };
S[a] = 2.5;
S[b] = 3.123;
cout << S[a] << ' ' << S[b] << endl;
return 0;
}
输出结果:
2.5 3.123
#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;
}
bitset<10000> s;
~, &, |, ^
<<,>>
==, !=
[]
count() 返回有多少个1
any() 判断是否至少有一个1
none() 判断是否全为0
set() 把所有位置成1
set(k, v) 将第k位变成v
reset() 把所有位变成0
flip() 等价于~
flip(k) 把第k位取反
pair
pair是一个二元组,可能看上去有点像map,但是实现起来就不是map了哈哈
#include<iostream>
using namespace std;
int main(){
pair<int,string> a;
a={123,"lml"};
cout<<a.first<<" "<<a.second<<endl;
//但是在C++99里面pair就不能这样赋值,需要使用函数
a=make_pair(676,"wyq");
cout<<a.first<<" "<<a.second<<endl;
return 0;
}
//运行结果
123 lml
676 wyq
也可以使用pair内嵌到pair中:
pair<int,pair<int,int>> a;
pair有什么好处?
pair可以比较二元的大小,比较运算符在pair都可以使用(<、>、<=、>=、!、==.stc)
它会先比较first的大小,如果first不同就比较second的大小
string,字符串
size()/length() 返回字符串长度
empty()
clear()
substr(起始下标,(子串长度)) 返回子串
c_str() 返回字符串所在字符数组的起始地址
queue, 队列
size()
empty()
push() 向队尾插入一个元素
front() 返回队头元素
back() 返回队尾元素
pop() 弹出队头元素
priority_queue, 优先队列,默认是大根堆
size()
empty()
push() 插入一个元素
top() 返回堆顶元素
pop() 弹出堆顶元素
定义成小根堆的方式:priority_queue<int, vector, greater> q;
stack, 栈
size()
empty()
push() 向栈顶插入一个元素
top() 返回栈顶元素
pop() 弹出栈顶元素
deque, 双端队列
size()
empty()
clear()
front()/back()
push_back()/pop_back()
push_front()/pop_front()
begin()/end()
[]
set, map, multiset, multimap, 基于平衡二叉树(红黑树),动态维护有序序列
size()
empty()
clear()
begin()/end()
++, – 返回前驱和后继,时间复杂度 O(logn)
set/multiset
insert() 插入一个数
find() 查找一个数
count() 返回某一个数的个数
erase()
(1) 输入是一个数x,删除所有x O(k + logn)
(2) 输入一个迭代器,删除这个迭代器
lower_bound()/upper_bound()
lower_bound(x) 返回大于等于x的最小的数的迭代器
upper_bound(x) 返回大于x的最小的数的迭代器
map/multimap
insert() 插入的数是一个pair
erase() 输入的参数是pair或者迭代器
find()
[] 注意multimap不支持此操作。 时间复杂度是 O(logn)
lower_bound()/upper_bound()
unordered_set, unordered_map, unordered_multiset, unordered_multimap, 哈希表
和上面类似,增删改查的时间复杂度是 O(1)
不支持 lower_bound()/upper_bound(), 迭代器的++,–
bitset, 圧位
bitset<10000> s;
~, &, |, ^
>>, <<
==, !=
[]
count() 返回有多少个1
any() 判断是否至少有一个1
none() 判断是否全为0
set() 把所有位置成1
set(k, v) 将第k位变成v
reset() 把所有位变成0
flip() 等价于~
flip(k) 把第k位取反