冲刺蓝桥杯第四章标准模版库STL(上)

vector:

介绍:

vector 是 C++ 标准模板库(STL)中的一个非常重要的序列容器,它封装了动态数组的实现。vector 能够存储具有相同类型的元素序列,并且能够根据需要自动调整其大小。注意:在局部区域中(比如局部函数里面)开vector数组,是在堆空间里面开的。在局部区域开数组是在栈空间开的,而栈空间比较小,如果开了非常长的数组就会发生爆栈。故局部区域不可以开大长度数组,但是可以开大长度 vector 。
1、头文件:

#include <vector>

2、一维初始化:

vector<int> v; // 定义了一个名为v的一维数组,数组存储int类型数据
vector<double> v; // 定义了一个名为v的一维数组,数组存储double类型数据
vector<node> v; // 定义了一个名为v的一维数组,数组存储结构体类型数据,node是结构体类型

指定长度和初始值的初始化:

vector<int> v(n); // 定义一个长度为n的数组,初始值默认为0,下标范围[0, n - 1]
vector<int> v(n, 1); // v[0] 到 v[n - 1]所有的元素初始值均为1
//注意:指定数组长度之后(指定长度后的数组就相当于正常的数组了)

初始化中有多个元素:

vector<int> a{1, 2, 3, 4, 5}; // 数组a中有五个元素,数组长度就为5

拷贝初始化:

vector<int> a(n + 1, 0);
vector<int> b(a); // 两个数组中的类型必须相同,a和b都是长度为n+1,初始值都为0的数组
vector<int> c = a; // 也是拷贝初始化,c和a是完全一样的数组

3、二维初始化:
定义第一维固定长度为 5 ,第二维可变化的二维数组:

vector<int> v[5]; // 定义可变长二维数组
//注意:行不可变(只有5行), 而列可变,可以在指定行添加元素
//第一维固定长度为5,第二维长度可以改变

vector v[5] 可以这样理解:长度为5的v数组,数组中存储的是 vector 数据类型,而该类型就是数组形式,故 v 为二维数组。其中每个数组元素均为空,因为没有指定长度,所以第二维可变长。可以进行下述操作:

v[1].push_back(2);
v[2].push_back(3);

定义行列均可变的二维数组:

//初始化二维均可变长数组
vector<vector<int> > v; // 定义一个行和列均可变的二维数组,注意哦,>>之间要加上空

可以在 v 数组里面装多个数组:

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:

vector<vector<int > a(n + 1, vector<int>(m + 1, 0));

常用函数:

#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 ,访问之外的数会出现越界错误

#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、迭代器法 :类似指针,迭代器就是充当指针的作用。

vector<int> vi{1, 2, 3, 4, 5};
//迭代器访问
vector<int>::iterator it;
//相当于声明了一个迭代器类型的变量it,通俗来说就是声明了一个指针变量

方法一:

#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;
}

方法二:

#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;
}

方法三:

#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 能够自动识别并获取类型。

#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、头文件

#include<stack>

2、初始化

stack<int> s;
stack<string> s;
stack<node> s; // node是结构体类型

常用函数

#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、栈只能对栈顶元素进行操作,如果想要进行遍历,只能将栈中元素一个个取出来存在数组中

#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 速度更快,遍历元素方便

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、头文件:

#include<queue>

2、初始化:

queue<int> q;

常用函数:

#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 ,表示刚开始队列为空

#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、头文件

#include<deque>

2、初始化

deque<int> dq;

常用函数

#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依然可实现相同功能)

//从小到大
sort(dq.begin(), dq.end())
//从大到小排序
sort(dq.begin(), dq.end(), greater<int>()); // deque里面的类型需要是int型
sort(dq.begin(), dq.end(), greater());//c++高版本才可以用

priority_queue

介绍:

优先队列是一种特殊的队列,其中每个元素都被赋予一个优先级。在优先队列中,元素的出队顺序不是基于它们的入队顺序,而是基于它们的优先级。优先级最高的元素会最先被移除。优先队列分为最大优先队列和最小优先队列,前者总是移除当前优先级最高的元素(即最大的元素),后者总是移除当前优先级最低的元素(即最小的元素)。
优先队列的实现通常基于堆数据结构,特别是二叉堆。堆是一个近似完全二叉树的结构,并同时满足堆属性:即子节点的键值或索引总是小于(或大于)它的父节点。通过堆的性质,优先队列可以在对数时间复杂度内完成插入和删除最大(或最小)元素的操作。
1、头文件

#include<queue>

2、初始化

priority_queue<int> q;

常用函数:

#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、基本数据类型的优先级

priority_queue<int> q; // 默认大根堆, 即每次取出的元素是队列中的最大值
priority_queue<int, vector<int>, greater<int> > q; // 小根堆, 每次取出的元素是队列中的最小值

参数解释:
第一个参数:
就是优先队列中存储的数据类型
第二个参数:
vector 是用来承载底层数据结构堆的容器,若优先队列中存放的是 double 型数据,就要填 vector< double >总之存的是什么类型的数据,就相应的填写对应类型。同时也要改动第三个参数里面的对应类型。
第三个参数:
less 表示数字大的优先级大,堆顶为最大的数字
greater 表示数字小的优先级大,堆顶为最小的数字
int代表的是数据类型,也要填优先队列中存储的数据类型
基础数据类型优先级设置的写法:
<1>、基础写法:

priority_queue<int> q1; // 默认大根堆, 即每次取出的元素是队列中的最大值
priority_queue<int, vector<int>, less<int> > q2; // 大根堆, 每次取出的元素是队列中的最大值,同第一行
priority_queue<int, vector<int>, greater<int> > q3; // 小根堆, 每次取出的元素是队列中的最小值

<2>、自定义排序:

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、高级数据类型(结构体)优先级:即优先队列中存储结构体类型,必须要设置优先级,即结构体的比较运算(因为优先队列的堆中要比较大小,才能将对应最大或者
最小元素移到堆顶)。
优先级设置可以定义在结构体内进行小于号重载,也可以定义在结构体外。

//要排序的结构体(存储在优先队列里面的)
struct Point {
   int x, y;
};

<1>、自定义全局比较规则

//定义的比较结构体
//注意: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>、直接在结构体里面写:因为是在结构体内部自定义的规则,一旦需要比较结构体,自动调用结构体内部重载运算符规则。
结构体内部有两种方式:
方式一 :

struct node {
    int x, y;
    friend bool operator < (Point a, Point b) { // 为两个结构体参数,结构体调用一定要写上friend
         return a.x < b.x; // 按x从小到大排,x大的在堆顶
   }
};

方式二 :(推荐)

struct node {
     int x, y;
     bool operator < (const Point &a) const { // 直接传入一个参数,不必要写friend
          return x < a.x; // 按x升序排列,x大的在堆顶
    }
};

优先队列的定义:

priority_queue<Point> q;

注意: 优先队列自定义排序规则和 sort() 函数定义 cmp 函数很相似,但是最后返回的情况是相反的。即相同的符号,最后定义的排列顺序是完全相反的。
所以只需要记住 sort 的排序规则和优先队列的排序规则是相反的就可以了。
3、存储特殊类型的优先级
存储pair类型:
排序规则:
默认先对 pair 的 first 进行降序排序,然后再对 second 降序排序
对 first 先排序,大的排在前面,如果 first 元素相同,再对 second 元素排序,保持大的在前面。

#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、头文件

#include<map>

2、初始化

map<string, string> mp;
map<string, int> mp;
map<int, node> mp; // node是结构体类型

map特性:map会按照键的顺序从小到大自动排序,键的类型必须可以比较大小

常用函数

#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

#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

#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迭代器类型

#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;
}

添加元素:

先声明:

map<string ,string>mp;

方法一:

mp["abcd"]="abcd";

方法二:

mp.insert(make_pair("abcd","abcd"));

方法三:

mp.insert(pair<string ,string>("abcd","abcd"));

方法四:

mp.insert({"abcd","abcd"});

访问元素

1、下标访问:

#include<iostream>
#include<map>
using namespace std;
map<string ,string>mp;
int main() {
    mp["abcd"]="efg";
    cout<<mp["abcd"];
}

2、遍历访问:
方法一:迭代器访问

#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是结构体变量访问要用  .  访问
    }
}

方法二:智能指针

#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;
    }
}

方法三:对指定单个元素访问

#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特性

#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的比较

头文件:

#include <unordered_map>

1、内部实现原理
map:内部用红黑树实现,具有自动排序(按键从小到大)功能。
unordered_map:内部用哈希表实现,内部元素无序杂乱。
2、效率比较
map:
优点:内部用红黑树实现,内部元素具有有序性,查询删除等操作复杂度为O(logN)
缺点:占用空间,红黑树里每个节点需要保存父子节点和红黑性质等信息,空间占用较大。
unordered_map:
优点:内部用哈希表实现,查找速度非常快(适用于大量的查询操作)。
缺点:建立哈希表比较耗时。
两者方法函数基本一样,差别不大。
注意:
随着内部元素越来越多,两种容器的插入删除查询操作的时间都会逐渐变大,效率逐渐变低。
使用 [] 查找元素时,如果元素不存在,两种容器都是创建一个空的元素;如果存在,会正常索引对应的值。所以如果查询过多的不存在的元素值,容器内部会创建大量的空的键值对,后续查询创建删除效率会大大降低。
查询容器内部元素的最优方法是:先判断存在与否,再索引对应值(适用于这两种容器)

#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、头文件:

#include<map>

2、初始化:
方法一:

multimap<string, int> mymultimap{{"penny",1},{"leonard",2},{"sheldon",3}};

方法二:

//借助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容器。

//创建一个会返回临时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容器时使用。

#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容器都有提供,并且它们的用法是相同的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值