vector:
介绍:
vector 是 C++ 标准模板库(STL)中的一个非常重要的序列容器,它封装了动态数组的实现。vector 能够存储具有相同类型的元素序列,并且能够根据需要自动调整其大小。注意:在局部区域中(比如局部函数里面)开vector数组,是在堆空间里面开的。在局部区域开数组是在栈空间开的,而栈空间比较小,如果开了非常长的数组就会发生爆栈。故局部区域不可以开大长度数组,但是可以开大长度 vector 。
1、头文件:
cpp
#include <vector>
2、一维初始化:
cpp
vector<int> v; // 定义了一个名为v的一维数组,数组存储int类型数据
vector<double> v; // 定义了一个名为v的一维数组,数组存储double类型数据
vector<node> v; // 定义了一个名为v的一维数组,数组存储结构体类型数据,node是结构体类型
指定长度和初始值的初始化:
cpp
vector<int> v(n); // 定义一个长度为n的数组,初始值默认为0,下标范围[0, n - 1]
vector<int> v(n, 1); // v[0] 到 v[n - 1]所有的元素初始值均为1
//注意:指定数组长度之后(指定长度后的数组就相当于正常的数组了)
初始化中有多个元素:
cpp
vector<int> a{1, 2, 3, 4, 5}; // 数组a中有五个元素,数组长度就为5
拷贝初始化:
cpp
vector<int> a(n + 1, 0);
vector<int> b(a); // 两个数组中的类型必须相同,a和b都是长度为n+1,初始值都为0的数组
vector<int> c = a; // 也是拷贝初始化,c和a是完全一样的数组
3、二维初始化:
定义第一维固定长度为 5 ,第二维可变化的二维数组:
cpp
vector<int> v[5]; // 定义可变长二维数组
//注意:行不可变(只有5行), 而列可变,可以在指定行添加元素
//第一维固定长度为5,第二维长度可以改变
vector v[5] 可以这样理解:长度为5的v数组,数组中存储的是 vector 数据类型,而该类型就是数组形式,故 v 为二维数组。其中每个数组元素均为空,因为没有指定长度,所以第二维可变长。可以进行下述操作:
cpp
v[1].push_back(2);
v[2].push_back(3);
定义行列均可变的二维数组:
cpp
//初始化二维均可变长数组
vector<vector<int> > v; // 定义一个行和列均可变的二维数组,注意哦,>>之间要加上空
可以在 v 数组里面装多个数组:
cpp
vector<int> t1{1, 2, 3, 4};
vector<int> t2{2, 3, 4, 5};
v.push_back(t1);
v.push_back(t2);
v.push_back({3, 4, 5, 6}) // {3, 4, 5, 6}可以作为vector的初始化,相当于一个无名vector
行列长度均固定 n + 1 行 m + 1 列初始值为0:
cpp
vector<vector<int > a(n + 1, vector<int>(m + 1, 0));
常用函数:
cpp
#include<iostream>
#include<vector>
using namespace std;
vector<int>v;
int main(){
v.push_back(1);//在尾部加一个元素 O(1)
v.front();//返回第一个元素 O(1)
v.back();//返回数组中的最后一个元素 O(1)
v.pop_back();//删除最后一个元素 O(1)
v.size();//返回元素的个数(unsigned类型) O(1)
v.clear();//清除元素个数 O(n) n为元素个数
v.resize(n,v);//改变数组大小为n,n个空间数值赋为v,没有v时默认为零
v.insert(it,x);//向任一迭代器it插入一个元素x O(n)
//例子:v.insert(v.begin(),1)将1插入到v[0]的位置
v.erase(first,last);//删除[first,last)的所有元素 O(n)
v.begin();//返回首元素的迭代器(类似于地址) O(1)
v.end();//返回尾元素的下一个位置的迭代器 O(1)
v.empty();//判断是否为空,为空返回真,反之返回假 O(1)
}
访问:
共三种方法:
1、下标法:直接和普通数组一样进行访问即可。
注意:一维数组的下标是从 0 到 v.size()-1 ,访问之外的数会出现越界错误
cpp
#include<iostream>
#include<vector>
using namespace std;
vector<int>v;
int main(){
int n,t;
cin>>n;
for(int i=0;i<n;i++){
cin>>t;
v.push_back(t);
}
for(int i=0;i<n;i++){
cout<<v[i];
}
return 0;
}
2、迭代器法 :类似指针,迭代器就是充当指针的作用。
cpp
vector<int> vi{1, 2, 3, 4, 5};
//迭代器访问
vector<int>::iterator it;
//相当于声明了一个迭代器类型的变量it,通俗来说就是声明了一个指针变量
方法一:
cpp
#include<iostream>
#include<vector>
using namespace std;
vector<int>v;
int main(){
int n,t;
cin>>n;
for(int i=0;i<n;i++){
cin>>t;
v.push_back(t);
}
vector<int>::iterator it=v.begin();
for(int i=0;i<n;i++){
cout<<*(it+i);
}
return 0;
}
方法二:
cpp
#include<iostream>
#include<vector>
using namespace std;
vector<int>v;
int main(){
int n,t;
cin>>n;
for(int i=0;i<n;i++){
cin>>t;
v.push_back(t);
}
vector<int>::iterator it;
for(it=v.begin();it!=v.end();it++){//注意不能用小于只能使用不等于
cout<<*it;
}
return 0;
}
方法三:
cpp
#include<iostream>
#include<vector>
using namespace std;
vector<int>v;
int main(){
int n,t;
cin>>n;
for(int i=0;i<n;i++){
cin>>t;
v.push_back(t);
}
auto it=v.begin();
while(it!=v.end()){
cout<<*it;
it++;
}
return 0;
}
3、智能指针:使用auto :比较简便,但是只能访问数组的所有元素(特别注意0位置元素也会访问到)如果要对部分内容进行遍历,需要另选方法。
auto 能够自动识别并获取类型。
cpp
#include<iostream>
#include<vector>
using namespace std;
int main(){
int n;
cin>>n;
vector<int>v(n);//使用auto必须要有输入数量
//输入
for(auto &x:v){
cin>>x;
}
//输出
for(auto val:v){
cout<<val;
}
return 0;
}
vector 注意:
vi[i] 和 *(vi.begin() + i) 等价,与指针类似。
vector 和 string 的 STL 容器支持 *(it + i) 的元素访问。
stack
介绍
栈是STL中实现的一个先进后出,后进先出的容器。
1、头文件
cpp
#include<stack>
2、初始化
cpp
stack<int> s;
stack<string> s;
stack<node> s; // node是结构体类型
常用函数
cpp
#include<iostream>
#include<stack>
using namespace std;
stack<int>s;
int main(){
s.push(n);//将元素n入栈 O(1)
s.pop();//移除栈顶元素 O(1)
s.top();//取出栈顶元素 O(1)
s.empty();//检测栈内是否为空,空为真,非空为假 O(1)
s.size();//返回栈内的元素个数 O(1)
}
遍历
1、栈只能对栈顶元素进行操作,如果想要进行遍历,只能将栈中元素一个个取出来存在数组中
cpp
#include<iostream>
#include<stack>
using namespace std;
stack<int>s;
int main(){
int n;
cin>>n;
for(int i=0;i<n;i++){
int x;
cin>>x;
s.push(x);
}
while(!s.empty()){
int tp=s.top();
s.pop();
cout<<tp;
}
}
2、数组模拟栈进行遍历:通过一个数组对栈进行模拟,一个存放下标的变量 top 模拟指向栈顶的指针。
一般来说单调栈和单调队列写法均可使用额外变量 tt 或 hh 来进行模拟。
特点: 比 STL 的 stack 速度更快,遍历元素方便
cpp
int s[100]; // 栈 从左至右为栈底到栈顶
int tt = -1; // tt 代表栈顶指针,初始栈内无元素,tt为-1
for(int i = 0; i = 5; i++) {
//入栈
s[++tt] = i;
}
//出栈
int top_element = s[tt--];
//入栈操作示意
//0 1 2 3 4 5
// tt
//出栈后示意
//0 1 2 3 4
// tt
queue
介绍:
队列是一种先进先出的数据结构。
1、头文件:
cpp
#include<queue>
2、初始化:
cpp
queue<int> q;
常用函数:
cpp
#include<iostream>
#include<queue>
using namespace std;
queue<int>q;
int main(){
q.push(n);//将n元素入队 O(1)
q.front();//返回队首元素 O(1)
q.back();//返回队尾元素 O(1)
q.pop();//删除第一个元素(出队) O(1)
q.size();//返回队列中元素的个数,返回值类型unsigned int O(1)
q.empty();//判断队列是否为空,队列为空返回true,为非空返回false O(1)
}
对列模拟
使用 q[] 数组模拟队列
hh 表示队首元素的下标,初始值为 0
tt 表示队尾元素的下标,初始值为 -1 ,表示刚开始队列为空
cpp
#include<iostream>
using namespace std;
const int N = 1e5 + 5;
int q[N];
int main(){
int hh = 0,tt = -1;
//入队
q[++tt] = 1;
q[++tt] = 2;
//将所有元素出队
while(hh = tt) {
int t = q[hh++];
printf("%d ",t);
}
return 0;
}
deque
介绍
首尾都可插入和删除的队列为双端队列,在两端都可以进行操作。
1、头文件
cpp
#include<deque>
2、初始化
cpp
deque<int> dq;
常用函数
cpp
#include<iostream>
#include<deque>
using namespace std;
deque<int>dq;
int main(){
dp.push_back(n);//把元素n插入到队尾 O(1)
dp.push_front(n);//把元素n插入到队首 O(1)
dp.back();//返回队尾元素 O(1)
dp.front();//返回队首元素 O(1)
dp.pop_back();//删除队尾元素 O(1)
dp.pop_front();//删除队首元素 O(1)
dp.erase(iterator it);//删除双端队列中的某一个元素
dp.erase(iterator first,iterator last);//删除双端队列中[first,last)中的元素
dp.empty();//判断双端队列是否为空 O(1)
dp.size();//返回双端队列的元素数量 O(1)
dp.clear();//清空双端队列
}
注意点
deque可以进行排序(双端队列排序一般不用,使用其他STL依然可实现相同功能)
cpp
//从小到大
sort(dq.begin(), dq.end())
//从大到小排序
sort(dq.begin(), dq.end(), greater<int>()); // deque里面的类型需要是int型
sort(dq.begin(), dq.end(), greater());//c++高版本才可以用
priority_queue
介绍:
优先队列是一种特殊的队列,其中每个元素都被赋予一个优先级。在优先队列中,元素的出队顺序不是基于它们的入队顺序,而是基于它们的优先级。优先级最高的元素会最先被移除。优先队列分为最大优先队列和最小优先队列,前者总是移除当前优先级最高的元素(即最大的元素),后者总是移除当前优先级最低的元素(即最小的元素)。
优先队列的实现通常基于堆数据结构,特别是二叉堆。堆是一个近似完全二叉树的结构,并同时满足堆属性:即子节点的键值或索引总是小于(或大于)它的父节点。通过堆的性质,优先队列可以在对数时间复杂度内完成插入和删除最大(或最小)元素的操作。
1、头文件
cpp
#include<queue>
2、初始化
cpp
priority_queue<int> q;
常用函数:
cpp
#include<iostream>
#include<queue>
using namespace std;
priority_queue<int>q;
int main(){
q.top();//访问队首元素 O(1)
q.push(n);//将n入队 O(logn)
q.pop();//堆顶(队首)元素出队 O(logn)
q.size();//队列元素个数 O(1)
q.empty();//判断是否为空,空为真,非空为假 O(1)
//注意:优先队列没有clear()
//优先队列只能通过 top() 访问队首元素(优先级最高的元素)
}
设置优先级
1、基本数据类型的优先级
cpp
priority_queue<int> q; // 默认大根堆, 即每次取出的元素是队列中的最大值
priority_queue<int, vector<int>, greater<int> > q; // 小根堆, 每次取出的元素是队列中的最小值
参数解释:
第一个参数:
就是优先队列中存储的数据类型
第二个参数:
vector 是用来承载底层数据结构堆的容器,若优先队列中存放的是 double 型数据,就要填 vector< double >总之存的是什么类型的数据,就相应的填写对应类型。同时也要改动第三个参数里面的对应类型。
第三个参数:
less 表示数字大的优先级大,堆顶为最大的数字
greater 表示数字小的优先级大,堆顶为最小的数字
int代表的是数据类型,也要填优先队列中存储的数据类型
基础数据类型优先级设置的写法:
<1>、基础写法:
cpp
priority_queue<int> q1; // 默认大根堆, 即每次取出的元素是队列中的最大值
priority_queue<int, vector<int>, less<int> > q2; // 大根堆, 每次取出的元素是队列中的最大值,同第一行
priority_queue<int, vector<int>, greater<int> > q3; // 小根堆, 每次取出的元素是队列中的最小值
<2>、自定义排序:
cpp
struct cmp1 {
bool operator()(int x, int y) {
return x > y;
}
};
struct cmp2 {
bool operator()(const int x, const int y) {
return x < y;
}
};
priority_queue<int, vector<int>, cmp1> q1; // 小根堆
priority_queue<int, vector<int>, cmp2> q2; // 大根堆
2、高级数据类型(结构体)优先级:即优先队列中存储结构体类型,必须要设置优先级,即结构体的比较运算(因为优先队列的堆中要比较大小,才能将对应最大或者
最小元素移到堆顶)。
优先级设置可以定义在结构体内进行小于号重载,也可以定义在结构体外。
cpp
//要排序的结构体(存储在优先队列里面的)
struct Point {
int x, y;
};
<1>、自定义全局比较规则
cpp
//定义的比较结构体
//注意:cmp是个结构体
struct cmp { // 自定义堆的排序规则
bool operator()(const Point& a,const Point& b) {
return a.x < b.x;
}
};
//初始化定义
priority_queue<Point, vector<Point>, cmp> q; // x大的在堆顶
<2>、直接在结构体里面写:因为是在结构体内部自定义的规则,一旦需要比较结构体,自动调用结构体内部重载运算符规则。
结构体内部有两种方式:
方式一 :
cpp
struct node {
int x, y;
friend bool operator < (Point a, Point b) { // 为两个结构体参数,结构体调用一定要写上friend
return a.x < b.x; // 按x从小到大排,x大的在堆顶
}
};
方式二 :(推荐)
cpp
struct node {
int x, y;
bool operator < (const Point &a) const { // 直接传入一个参数,不必要写friend
return x < a.x; // 按x升序排列,x大的在堆顶
}
};
优先队列的定义:
cpp
priority_queue<Point> q;
注意: 优先队列自定义排序规则和 sort() 函数定义 cmp 函数很相似,但是最后返回的情况是相反的。即相同的符号,最后定义的排列顺序是完全相反的。
所以只需要记住 sort 的排序规则和优先队列的排序规则是相反的就可以了。
3、存储特殊类型的优先级
存储pair类型:
排序规则:
默认先对 pair 的 first 进行降序排序,然后再对 second 降序排序
对 first 先排序,大的排在前面,如果 first 元素相同,再对 second 元素排序,保持大的在前面。
cpp
#include<iostream>
#include<queue>
using namespace std;
int main() {
priority_queue<pair<int, int> >q;
q.push({7, 8});
q.push({7, 9});
q.push(make_pair(8, 7));
while(!q.empty()) {
cout << q.top().first << " " << q.top().second;
q.pop();
}
return 0;
}
//结果:
//8 7
//7 9
//7 8
map
介绍
映射类似于函数的对应关系,每个 key 对应一个 value ,而 map 是每个键对应一个值。
1、头文件
cpp
#include<map>
2、初始化
cpp
map<string, string> mp;
map<string, int> mp;
map<int, node> mp; // node是结构体类型
map特性:map会按照键的顺序从小到大自动排序,键的类型必须可以比较大小
常用函数
cpp
#include<iostream>
#include<map>
using namespace std;
map<int ,int>mp;
int main() {
mp.find(key);//返回健为key的映射的迭代器 O(logN)
//注意:用find函数来定位数据出现的位置,它返回一个迭代器。当数据存在时,返回数据所在位置的迭代器,当数据不存在时,返回mp.end()
mp.erase(it);//删除迭代器对应的键和值 O(1)
mp.erase(key);//根据映射的键删除键和值 O(logN)
mp.erase(first,last);//删除[first,last)区间的迭代器对应的键和值 O(last-first)
mp.size();//返回映射的对数 O(1)
mp.clear();//清空map中的所有元素 O(n)
mp.insert();//插入元素,插入时要构造键值对
mp.empty();//判断map是否为空,为空返回true,为假返回false
mp.begin();//返回指向map第一个元素的迭代器(地址)
mp.end();//返回指向map最后一个元素的下一个地址
mp.rbegin();//返回指向map最后一个元素的迭代器(地址)
mp.rend();//返回指向元素map第一个元素的前一个地址(逆向迭代器)
mp.count(key);//查看元素是否存在,因为map中的键值是唯一的,存在返回true,不存在返回false
mp.lower_bound();//返回一个迭代器,指向键值>=key的第一个元素
mp.upper_bound();//返回一个迭代器,指向键值>key的第一个元素
}
注意点:
查找元素是否存在时,可以使用① mp.find() ② mp.count() ③ mp[key]但是第三种情况,如果不存在对应的 key 时,会自动创建一个键值对(产生一个额外的键值对空间)所以为了不增加额外的空间负担,最好使用前两种方法。
迭代器进行正反向遍历:
1、mp.begin() 和 mp.end() 用于正向遍历map
cpp
#include<iostream>
#include<map>
using namespace std;
map<int ,int>mp;
int main() {
int n;
cin>>n;
for(int i=1;i<=n;i++){
int x,y;
cin>>x>>y;
mp[x]=y;
}
auto it=mp.begin();
while(it!=mp.end()){
cout<<it->first<<" "<<it->second<<endl;
it++;
}
}
2、mp.rbegin() 和 mp.rend()用于逆向遍历map
cpp
#include<iostream>
#include<map>
using namespace std;
map<int ,int>mp;
int main() {
int n;
cin>>n;
for(int i=1;i<=n;i++){
int x,y;
cin>>x>>y;
mp[x]=y;
}
auto it=mp.rbegin();
while(it!=mp.rend()){
cout<<it->first<<" "<<it->second<<endl;
it++;
}
}
二分查找:
map的二分查找以第一个元素(即键为准),对键进行二分查找返回值为map迭代器类型
cpp
#include<iostream>
#include<map>
using namespace std;
map<int ,int>mp{{1,2},{2,2},{3,2},{6,2},{8,2}};//有序
int main() {//判断的是key返回的也是key的值
map<int ,int>::iterator it1=mp.lower_bound(2);//左边界
cout<<it1->first;//2
map<int ,int>::iterator it2=mp.upper_bound(6);//右边界
cout<<it2->first;//8
return 0;
}
添加元素:
先声明:
cpp
map<string ,string>mp;
方法一:
cpp
mp["abcd"]="abcd";
方法二:
cpp
mp.insert(make_pair("abcd","abcd"));
方法三:
cpp
mp.insert(pair<string ,string>("abcd","abcd"));
方法四:
cpp
mp.insert({"abcd","abcd"});
访问元素
1、下标访问:
cpp
#include<iostream>
#include<map>
using namespace std;
map<string ,string>mp;
int main() {
mp["abcd"]="efg";
cout<<mp["abcd"];
}
2、遍历访问:
方法一:迭代器访问
cpp
#include<iostream>
#include<map>
using namespace std;
map<string ,string>mp;
int main() {
mp["abcd"]="efg";
map<string ,string>::iterator it;
for(it=mp.begin();it!=mp.end();it++){
cout<<it->first<<" "<<it->second;
//it是结构体指针访问要用 -> 访问
cout<<(*it).first<<" "<<(*it).second;
//*it是结构体变量访问要用 . 访问
}
}
方法二:智能指针
cpp
#include<iostream>
#include<map>
using namespace std;
map<string ,string>mp;
int main() {
mp["abcd"]="efg";
map<string ,string>::iterator it;
for(auto i:mp){
cout<<i.first<<" "<<i.second;
}
}
方法三:对指定单个元素访问
cpp
#include<iostream>
#include<map>
using namespace std;
map<string ,string>mp;
int main() {
mp["abcd"]="efg";
map<string ,string>::iterator it=mp.find("abcd");//key的值
cout<<it->first<<" "<<it->second;
}
方法四:c++17特性
cpp
#include<iostream>
#include<map>
using namespace std;
map<string ,string>mp;
int main() {
mp["abcd"]="efg";
for(auto [x,y]:mp){
cout<<x<<" "<<y;
}
}
map与unordered_map的比较
头文件:
cpp
#include <unordered_map>
1、内部实现原理
map:内部用红黑树实现,具有自动排序(按键从小到大)功能。
unordered_map:内部用哈希表实现,内部元素无序杂乱。
2、效率比较
map:
优点:内部用红黑树实现,内部元素具有有序性,查询删除等操作复杂度为O(logN)
缺点:占用空间,红黑树里每个节点需要保存父子节点和红黑性质等信息,空间占用较大。
unordered_map:
优点:内部用哈希表实现,查找速度非常快(适用于大量的查询操作)。
缺点:建立哈希表比较耗时。
两者方法函数基本一样,差别不大。
注意:
随着内部元素越来越多,两种容器的插入删除查询操作的时间都会逐渐变大,效率逐渐变低。
使用 [] 查找元素时,如果元素不存在,两种容器都是创建一个空的元素;如果存在,会正常索引对应的值。所以如果查询过多的不存在的元素值,容器内部会创建大量的空的键值对,后续查询创建删除效率会大大降低。
查询容器内部元素的最优方法是:先判断存在与否,再索引对应值(适用于这两种容器)
cpp
#include<iostream>
#include<map>
using namespace std;
map<string ,string>mp;
int main() {
mp["abcd"]="efg";
string s="abcd";//key的值
if(mp.count(s)){
cout<<mp[s];
}
}
multimap
键可以重复,即一个键对应多个值。
1、头文件:
cpp
#include<map>
2、初始化:
方法一:
cpp
multimap<string, int> mymultimap{{"penny",1},{"leonard",2},{"sheldon",3}};
方法二:
cpp
//借助pair类模板的构造函数生成各个pair类型的键值对
multimap<string,int> mymultimap{
pair<string,int>{"penny",1},
pair<string,int>{"leonard",2},
pair<string,int>{"sheldon",3}
};
//调用make_pair()函数,生成键值对元素;然后创建并初始化multimap容器
multimap<string,int> mymultimap{
make_pair("penny", 1),
make_pair("leonard",2),
make_pair("sheldon",3)
};
方法三:
拷贝(复制)构造函数,也可以初始化新的multimap容器。
cpp
//创建一个会返回临时multimap对象的函数
multimap<string,int> tmpmultimap(){
multimap<string,int> tempmultimap{{"penny",1},{"leonard",2}};
return tempmultimap;
}
//在main函数中调用multimap类模板的移动构造函数创建newmultimap容器
multimap<string,int> newmultimap(tmpmultimap());
方法四:
multimap类模板还支持从已有的multimap容器中,选取某块区域内的所有键值对,用作初始化新multimap容器时使用。
cpp
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main(){
//创建并初始化multimap容器
multimap<string,int> mymultimap{
{"penny",1},{"leonard",2},{"sheldon",3},{"howard",4}};
multimap<string,int> newmultimap(++mymultimap.begin(),mymultimap.end());
for(auto iter = newmultimap.begin(); iter != newmultimap.end(); iter++){
cout<<iter->first<<" "<<iter->second<<endl;
}
return 0;
}
和map容器相比,multimap未提供at()成员方法,也没有重载[ ] 运算符。map容器中通过指定键获取指定键值对的方式,将不再适用于multimap容器。multimap容器提供的成员方法,map容器都有提供,并且它们的用法是相同的。