STL容器(常用)
题记:
这应该算是我写博客以来写的最长的一篇了hhh
文章目录
1. #include < vector >
定义:
vector是变长数组(用多少开多少),是一个可以自动改变长度的数组,支持随机访问,不支持在任意位置O(1)插入。为了保证效率,元素的增删一般应该在末尾进行。
声明:
#include <vector>//头文件
vector<int> a; //定义一个int类型的vector(相当于一个长度动态变化的int数组)
vector<int> b[233];//定义了233个vector,可以看成是个二维数组,第一维是静态,第二维是动态(相当于第一维长233,第二维长度动态变化的int数组)
struct rec
{
int x,y;//定义结构体
};
vector<rec> c;//自定义的结构体类型保存在vector中
关于vector的函数:
1. size/empty
size函数返回vector的实际长度(包含的元素个数)
empty函数返回一个bool类型,表明vector是否为空
二者的时间复杂度都是O(1)
(所有的STL容器都支持这两个方法,含义也都相同)
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> a;
vector<int> b[233];
a.size();//表示数组的长度
a.empty();//表示数组是否为空(它返回的是bool值,若数组为空,返回true;若数组不是空的,则返回false)
return 0;
}
2. clear
clear函数把vector清空
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> a;
vector<int> b[233];
a.clear();//表示把当前数组清空(例:当前数组里有一些元素,若想删去则用clear )
return 0;
}
注:除了队列、优先队列、栈之外,其他所有的STL容器都是有clear函数的
3.迭代器:
*迭代器就像STL容器的“指针”,可以用星号“ * ”操作符解除引用
一个保存int的vector的迭代器声明方法为:
vector<int>: :iterator it;
vector的迭代器是“随机访问迭代器”,可以把vector的迭代器与一个整数相加减,其行为和指针的移动类似,可以把vector的两个迭代器相减,其结果也和指针相减类似,得到两个迭代器对应下标之间的距离
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> a;
vector<int> b[233];
vector<int>::iterator it=a.begin();//begin是a的一个起始迭代器
//迭代器是一个随机访问的迭代器,支持相加减
it+2;//访问的就是a[2]
it;//访问的就是a[0]
*it;//取得迭代器的值
return 0;
}
4. begin/end
begin函数返回指向vector中第一个元素的迭代器
例如a是一个非空的vector,则*a.begin()
与a[0]
的作用相同
所有的容器都可以视作一个“前闭后开”的结构,end函数返回vector的尾部,即第n个元素再往后的“边界”
*a.end()
与a[n]
都是越界访问,其中n=a.size()
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> a;
vector<int> b[233];
vector<int>::iterator it=a.begin();//begin是a的第一个元素的迭代器,也就是a的第一个元素的地址
a.end();//end是最后一个位置的下一个位置
*a.begin();//效果和a[0]一样(因为begin是第一个迭代器,*就是取第一个迭代器里的值)
return 0;
}
关于a.begin()
和a[0]
效果一样的代码证明举例:
由此可证:*a.begin()
效果和a[0]
一样
关于遍历vector<int> a
并输出它的所有元素的方法:
第一种:
代码:(像遍历数组一样)
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> a({1,2,3});//初始化一个vector,这个vector里包含三个值1、2、3
for(int i=0;i<a.size();i++)
{
cout<<a[i]<<' ';
}
cout<<endl;
return 0;
}
结果:
第二种:
代码:(使用迭代器来遍历,就是从begin到end遍历)
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> a({1,2,3});//初始化一个vector,这个vector里包含三个值1、2、3
for(vector<int>::iterator i=a.begin();i!=a.end();i++)
{//这里vector<int>::iterator可以用auto来替代
cout<<*i<<' ';
}
cout<<endl;
return 0;
}
结果:
第三种:
代码:(范围遍历)
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> a({1,2,3});//初始化一个vector,这个vector里包含三个值1、2、3
for(int x:a)
{
cout<<x<<' ';
}
cout<<endl;
return 0;
}
结果:
5.front/back
front函数返回vector的第一个元素,等价于*a.begin()
和a[0]
back函数返回vector的最后一个元素,等价于a[a.size()-1]
再次强调,*a.end()
指的是vector的最后一位的后一位
第一条观点的证明:
第二条观点的证明:
6.push_back()和pop_back()
a.push_back(x)
把元素x插入到vector a
的尾部
a.pop_back()
删除vector a
的最后一个元素
第一条观点的证明:(时间复杂度O(1))
第二条观点的证明:
拓展:
vector如何实现动态增长空间???
(基于倍增的思想)
举例:
vector首先开一个长度是10的数组,然后每次往里面加元素,当我们想加第11个元素的时候,就会发现这个数组不够用了,那么我们就再开一个长度为20的数组,再把刚刚的前面10个数复制到现在的数组里,这样的话,新开的数组里就会多出10个位置,然后我们再继续往后写。写到20的时候就会发现又不够了,那么我们就又要开一个长度是40的数组,然后再把刚刚的20个复制到我们新开的这个数组里,然后…综上,虽然上面会涉及到一些关于数组的拷贝,但平均来看,如果我们插入n个数的话,那么我们会拷贝 n 2 \frac{n}{2} 2n+ n 4 \frac{n}{4} 4n+ n 8 \frac{n}{8} 8n+…(每次拷贝的数量是越往上越除以二),所以总共拷贝的数量是 n( 1 2 \frac{1}{2} 21+ 1 4 \frac{1}{4} 41+ 1 8 \frac{1}{8} 81+…) < n,这样看,虽然我们每次都会拷贝一遍,但是平均来看,我们拷贝数组的次数是小于n的。所以vector如果单次去private的话,可能某一次会比较慢,但是总共来看,它平均每次是O(1)的,所以很快。
2.#include < queue >
头文件queue
主要包括循环队列queue
和优先队列priority_queue
(实现返回所有队列里的最大值)两个容器
队列声明:
queue<int> q;//队列的定义
queue<double> a;//也可以换成double类型的队列
struct rec
{
int a,x,y;//定义一个结构体
};
queue<rec> b;//定义这个结构体的队列
注:队列有一个“先进先出”的性质
——————
3 2 1
——————
这两根横线就相当于一个管道,(左边是入口,右边是出口)我们从左边开始先插入1,再插入2,再插入3,那么当我们往外弹的时候,就会先弹1,再弹2,再弹3
优先队列:
(插入时的顺序无所谓,往外弹的时候会先弹所有数的最大值)
例如插入1、2、3,那么它会优先往外弹3,然后再往外弹2,最后才是1
声明:
queue<int> q;//队列
priority_queue<int> a;//默认是大根堆
priority_queue<int,vector<int>,greater<int>>b;//小根堆
//优先队列里面也是可以换成其他类型的,如下:
//priority_queue<pair<int,int>>c;//pair是一个二元组
struct rec
{
int a,b;//定义的结构体
bool operator < (const rec& t) const
{
return a < t.a;
}
};
priority_queue<rec> d;//若想定义一个结构体类型的优先队列的话,一定要重载"<"(因为是大根堆)(具体见上面bool开头的那段代码)
综上:如果是重载小根堆的话,就要用">";
如果是重载大根堆的话,就要用"<".
循环队列 queue
(队列长度与队列中现存元素的数量相关,与插入次数无关)
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
int main()
{
queue<int> q;
q.push(1);//在队头插入一个元素
q.pop();//弹出队尾元素
q.front();//返回队头
q.back();//返回队尾
}
优先队列 priority_queue
priority_queue<int> a;//大根堆
a.push(1);//插入一个数
a.top();//取最大值
a.pop();//删除最大值
清空队列
q=queue<int> ();//就是重新初始化一下
因为队列、优先队列、栈,都是没有clear函数的
3.#include < stack >
(栈和队列类似,但它是”先进后出“,意思是弹出的元素是最后插入的元素,其余类似)
声明:
#include <iostream>
#include <stack>
using namespace std;
int main()
{
stack<int> stk;//栈的定义
stk.push(1);//插入一个函数
stk.top();//返回栈顶元素
stk.pop();//删除栈顶元素,就是删除最后一个元素
return 0;
}
4.#include < deque >
定义:
队列:队头弹出,队尾插入
栈:队尾插入,队尾弹出
双端队列:没有限制
双端队列deque是一个支持在两端高效插入或删除元素的连续线性存储空间。它就像是vector和queue的结合。与vector相比,deque在头部增删元素仅需要O(1)的时间;与queue相比,deque像数组一样支持随机访问
声明:
#include <iostream>
#include <deque>
using namespace std;
int main()
{
deque<int> a;//定义
a.begin();//返回deque的头迭代器
a.end();//返回deque的尾迭代器
a.front();//队头元素
a.back(); //队尾元素
a.push_back(1);//在队尾插入一个元素
a.push_front(2);//在队头插入一个元素
a[0];//随机访问一个元素
a.pop_back();//弹出最后一个元素
a.pop_front();//弹出第一个元素
a.clear();//清空一个deque
return 0;
}
5.#include < set >
定义:
头文件set主要包括set和multiset两个容器,分别是”有序集合“和”有序多重集合“,即前者的元素不能重复,而后者可以包含若干个相等的元素。set和multiset的内部实现是一棵红黑树,它们支持的函数基本相同
声明:
#include <iostream>
#include <set>
using namespace std;
int main()
{
set<int> a;//元素不能重复
multiset<int> b;//元素可以重复
struct rec
{
int x,y;//定义一个结构体
bool operator < (const rec& t) const//需要重载"<"
{
return x < t.x;
}
};
set<rec> c;
multiset<double> s;
}
1.size/empty/clear
与vector类似
2.迭代器
set和multiset的迭代器称为“双向访问迭代器”,不支持“随机访问”,支持星号()解除引用,仅支持"++“和”–"两个与算术相关的操作
设it是一个迭代器,例如set<int>::iterator it;
若把it++,则it会指向“下一个”元素。这里的“下一个”元素是指在元素从小到大排序结果中,排在it下一名的元素。同理,若把it–,则it将会指向排在“上一个”的元素。
3.begin/end
返回集合的首、尾迭代器,时间复杂度均为O(1)。
s.begin() 是指向集合中最小元素的迭代器。
s.end() 是指向集合中最大元素的下一个位置的迭代器。换言之,就像vector一样,是一个“前闭后开”的形式。因此–s.end()是指向集合中最大元素的迭代器。
4.insert
s.insert(x)把一个元素x插入到集合s中,时间复杂度为O(logn)。
在set中,若元素已存在,则不会重复插入该元素,对集合的状态无影响。
5.find
s.find(x) 在集合s中查找等于x的元素,并返回指向该元素的迭代器。若不存在,则返回s.end()。时间复杂度为O(logn)。
6.lower_bound/upper_bound
这两个函数的用法与find类似,但查找的条件略有不同,时间复杂度为 O(logn)。
s.lower_bound(x) 查找大于等于x的元素中最小的一个,并返回指向该元素的迭代器。
s.upper_bound(x) 查找大于x的元素中最小的一个,并返回指向该元素的迭代器。
7.erase
设it是一个迭代器,s.erase(it) 从s中删除迭代器it指向的元素,时间复杂度为O(logn)
设x是一个元素,s.erase(x) 从s中删除所有等于x的元素,时间复杂度为O(k+logn),其中k是被删除的元素个数。
8.count
s.count(x) 返回集合s中等于x的元素个数,时间复杂度为 O(k +logn),其中k为元素x的个数。
#include <iostream>
#include <set>
using namespace std;
int main()
{
set<int> a;//元素不能重复
multiset<int> b;//元素可以重复
set<int>::iterator it=a.begin();
a.begin();//表示集合中最小元素的迭代器,时间复杂度为O(1)
a.end();// 表示最大元素的后一个位置的迭代器,时间复杂度为O(1)
a.insert(x);//插入一个x,时间复杂度为O(logn)
a.find(x);//查找等于x的元素,并返回指向该元素的迭代器;若不存在,则返回a.end(),就相当于它此时的值等于a.end(),时间复杂度为O(logn)
if(a.find(x)==a.end())//判断x在a中是否存在
a.lower_bound(x);//找到大于等于x的最小的元素的迭代器,时间复杂度O(logn)
a.upper_bound(x);//找到大于x的最小的元素的迭代器,时间复杂度O(logn)
a.erase(x);//设x是一个元素,把a中所有等于x的元素删掉,时间复杂度O(k+logn) ,k为被删除的元素的个数
a.erase(it);// 设it是一个迭代器,把a中迭代器it指向的元素全部删掉,时间复杂度O(logn)
a.count(x);//表示x在a里面的个数,由于a是一个set,里面不存在重复元素,所以如果存在x的话会返回1;如果不存在x的话,会返回0,时间复杂度O(k+logn),k为元素x的个数
}
6.#include < map >
定义:
map容器是一个键值对key-value的映射,其内部实现是一棵以key为关键码的红黑树。map的key和value可以是任意类型,其中key必须定义小于号运算符
声明:
#include <iostream>
#include <map>
using namespace std;
int main()
{
map<int,int> a;//定义
a[1]=2;//插入了一个2
a[100000000]=3;
cout<<a[100000000]<<endl;
//map定义完之后的用法和数组差不多
return 0;
}
map<int,int> a;//前后两个结构体可以自己定义的
证明1:
证明2:
证明3:
声明(补充)
map<key_type, value_type> name;
例如:
map<long, long, bool> vis;
map<string, int> hash;
map<pair<int, int>, vector> test;
1.size/empty/clear/begin/end均与set类似。
2.Insert/erase
与set类似,但其参数均是pair<key_type, value_type>。
也可以以下方式插入
#include <iostream>
#include <map>
#include <vector>
using namespace std;
int main()
{
map<string,vector<int>> a;//定义
a.insert({"a",{}});//插入一个二元组,第一个元组是string类型,第二个元组是第二个类型
return 0;
}
3.find
h.find(x) 在变量名为h的map中查找key为x的二元组。
map<string,vector<int>> a;
cout<<(a.find("qf")==a.end())<<endl;
4.[]操作符
h[key] 返回key映射的value的引用,时间复杂度为O(logn)。
[]操作符是map最吸引人的地方。我们可以很方便地通过h[key]来得到key对应的value,还可以对h[key]进行赋值操作,改变key对应的value。
7.#include < unordered_set >
定义:
无序的set
和set的用法完全一样
但没有lower_bound和upper_bound这两个函数
比set的效率高,但它不支持二分
#include <iostream>
#include <unordered_set>
using namespace std;
int main()
{
unordered_set<int> s;//哈希表,不能存储重复元素
unordered_multiset<int> b;//哈希表,可以存储重复元素
return 0;
}
8.#include < unordered_map >
和map的用法一样
比map效率高,但不支持二分
#include <iostream>
#include <unordered_map>
using namespace std;
int main()
{
unordered_map<int,int> a;//哈希表
return 0;
}
9.#include < bitset >
定义一个很长很长的二进制的01串
用法1:
用法2:
用法3:
#include <iostream>
#include <bitset>
using namespace std;
int main()
{
bitset<1000> a,b;//中括号里面写长度 ,定义了一个长度为1000的01串
a[0]=1;//像用数组一样使用
a[1]=1;
a.set(3);//把第三位设成1
a.reset(3);//把第3位设成0
cout<<a[3]<<endl;
return 0;
}