目录
一、stl
又叫标准模板库,其包含有大量的模板类和模板函数。
从根本上说,STL 是主要组件:容器、算法、迭代器和其他一些组件的集合,可以说,STL 基本上达到了各种存储方法和相关算法的高度优化。
包含容器有等。
主要容器有:vector(动态数组),deque(双端队列),list(双链表),forward_list(单链表),queue(队列),priority_queue(优先队列),stack(栈),pair(二元组),map/multimap(映射),set/multiset(有序集合),bitset(位集合)
在此介绍vector、pair、map与stack容器。
1.vector:不定长数组
相当于数组,但其大小可以不预先指定,并且自动扩展。
头文件:#include<vector>
定义方式:
基本方式:vector<typename> name;//默认长度为0
typename可以是基础数据类型如int, double, char 等,也可以是结构体类型或容器。
二维,如:vector<int> a[100]或:vector<vector<int> > a;
其他:vector <int> v(N); 效果类似int v[N];但是N可以是变量
vector <int> v(N, i);可变数组最开始有N个元素,每个元素的值是i,i可以省略不写
常用操作:
补充知识:迭代器
迭代器相当于容器的指针,可以理解为“元素的地址”。
定义方法如:vector<typename>::iterator it
取出迭代器指向地址的元素具体内容的方法: *it
分类:双向访问迭代器,支持++ --
随机访问迭代器,支持++ -- it+n it-n
遍历:
一、通过迭代器访问:
注意:不能写成 it < a.end(),只能用 it != a.end(),因为没有重载<运算符
其他:vector迭代器是随机访问迭代器,支持++和--运算符,同时支持 it+n , it-n
二、支持下标运算符[]。如:
2.pair:二元组
相当于二元结构体,属于单个元素。
头文件:#include<utility>
定义:
pair<typename1,typename2> name;
typename1,typename2可以是任何类型;name是变量名;
使用方法:
读入:make_pair(a,b);读入a,b合成一个二元组。如:
访问:类似于结构体
其他:可直接进行比较运算,依据是字典序比较。first是第一关键字,相同则比较second
3.map:映射
头文件:#include<map>
建立key到value的映射关系(key和value的二元组集合),可以看做另一种数组。
与数组区别及优势:
例如数组int a[k]=n;便是建立了整型k到整型n的映射,
double a[k]=n;便是建立了整型k到double型n的映射。
由于数组下标只能是整型,所以只能建立整型到某一数据类型间的映射
map并无数据类型限制,能建立任意型到任意型的对应。
且不像数组需要预定内存,与vector一样,即用即扩充,一般不会出现内存超限情况
定义:
map<key_type,value_type> name;
key_type,value_type可以是任意数据类型,包括结构体和容器。
可二维定义,作用类比二维数组。
基本操作:
遍历:
支持下标运算符map[key]=value;如:
支持迭代器,但 [ ] 过于好用,故不常用。
map的迭代器属于双向访问迭代器,支持++,--,不支持 it+n,it-n
5.stack:栈
栈是一种线性储存结构,遵循先入后出规则。
头文件:#include<stack>
定义:
stack<type> a; type可以是基本数据类型。(其他不知道)
特点:
栈的所有操作都在栈顶进行,即数据读入和弹出的那一端
故栈不提供遍历功能
但在一般条件下,我们可以用数组模拟栈。
概念:
栈顶(Top):线性表允许进行插入删除的那一端。
栈底(Bottom):固定的,不允许进行插入和删除的另一端。
空栈:不含任何元素的空表。
常用操作
数组模拟栈:
简单情况大多用数组直接模拟,不但操作简单,还可进行stack中不能进行的遍历等操作。
关于下标运算符和迭代器:
1.支持下标运算符 [ ] 的有: vector deque map string bitset
2.支持随机访问迭代器的有:vector deque string
不支持迭代器的:stack queue priority_queue
其他大都是双向访问迭代器
二、模拟链表
1.介绍:
链表是一种 物理存储结构上非连续 、非顺序的存储结构,数据元素的 逻辑顺序 是通过链表中的 指针链接 次序实现的 。
是stl中的一种容器,但容易发生内存泄漏,常用结构体数组模拟链表。
构成:
对于一组数据,我们不必知晓所有数据的位置,只需要知道每个数据的前一个或后一个是谁就可以推测出整个序列,链表正是这样一种存储方式。
所以对于数据在链表中的存储包含数据的前一个或后一个元素地址和数据本身。
我们可以用结构体来存储
优势:
非连接、非顺序的存储结构表明当在序列中插入元素或删除元素时不必考虑整个序列,只考虑相邻元素的连接即可。
所以在面对在序列中需要频繁插入或删除元素时,常利用链表来解决。
1.单向链表
只记录链表中每个元素的前一项或后一项,这里以后一项来演示。
此时的结构体定义为:
插入:
在i号元素与j号元素中插入k号元素
我们需要进行的操作有:
1. i 后元素由 j 变为 k
2. k 后元素连为 j
即:
##注意代码顺序,防止覆盖后再赋值
删除:
将k号元素从i号元素与j号元素中删除
我们需要进行的操作有:
1. i 后元素转为原k后元素
2. k 后不再存在元素
即:
但是显然,对于只记录后一个元素的单向链表,我们是不容易在 i 元素前的进行插入和删除工作,同时也不能双向遍历,只记录前一个元素的单向地址同理。所以,我们大多使用双向链表。
2.双向链表
对于链表中元素,不仅记录前一个元素还记录后一个元素。
此时结构体定义为:
插入:
将 k 号元素插入到 i 与 就 j 之间,原理与单向链表相同
我们需要进行的操作有:
1. k 元素的前一位是原 j 的上一位 i
2. k 元素的后一位是原 i 的后一位 j
3. i 元素的后一位改为k
4. j 元素的前一位改为k
即:
##自转换时,注意代码顺序,防止覆盖后再赋值;
删除:
将k号元素删除:
我们需要进行的操作为:
1. i(k的前一个元素)的后一个元素是 j(k的后一个元素)
2. j(k的后一个元素)的后一个元素是 i(k的前一个元素)
3. k的上一个元素和后一个元素的存储清零
即:
![]()
细节及遍历:
链表初始化时,推荐以0号元素为起始,方便链表正序遍历
遍历方式:
由0开始,不断找到当前元素的下一位,终止条件即末元素的下一位不存在(=0)。
![]()
三、题解
1.map相关
1.P3613 【深基15.例2】寄包柜 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
此题很容易就想到用二维数组存储每个格子,但我们发现
,显然空间超限。
我们还发现,开二维数组空间有大量冗余,我们考虑用二维vector。由于vector下标是连续的,若满足题意还需压缩下标,不易操作。
于是我们考虑二维map,map即用即开,节省了大量空间。
做法:定义map<map<int,int>,int> a;
表示map<int,int>(第n个寄包柜与第ai个格子映射)与 a(即存储的数据)的映射。
存储:直接赋值
用下标运算符定位。
代码:
map<int,map<int,int> > a; int main() { int n,q; scanf("%d%d",&n,&q); while(q--)//q次操作 { int f;scanf("%d",&f); if(f==1)//存储 { int i,j,k; scanf("%d%d%d",&i,&j,&k); a[i][j]=k;//直接赋值即可 } else//查询 { int i,j; scanf("%d%d",&i,&j); printf("%d\n",a[i][j]); } } return 0; }
2.栈相关
2.P1241 括号序列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
3.P1449 后缀表达式 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
第二题:
“左侧离它最近”,符合先入后出原则,用栈来解决。
题目有限定匹配条件,需注意其中一句“考察它与它左侧离它最近的未匹配的的左括号”,表示若目前是“)”遇到未配对的“(”或“[”都停止并判断是否可匹配。
故 ([) 的输出不是 ([]) 而是 ()[]()
分析:设一个栈,从前往后遍历,对于每一个元素:
1.左括号:入栈,不论括号类型
2.右括号:比对栈顶元素,若可匹配:
1)二者符合条件,标记;
2)由于栈顶已被标记,弹出栈顶元素
若不匹配,不作操作。
最终未标记的元素则未配对,在输出时匹配输出即可。
代码:
string a; char mi[1000];//栈 int v[1000];//是否被标记 int main() { cin>>a; int cnt=0;//栈顶标记 int n=a.length(); for(int i=0;i<n;++i) { if(a[i]=='('||a[i]=='[') mi[++cnt]=i;//左括号入栈 //右括号入栈 if(a[i]==')'&&a[mi[cnt]]=='(') { v[i]=v[mi[cnt]]=1;--cnt; } if(a[i]==']'&&a[mi[cnt]]=='[') { v[i]=v[mi[cnt]]=1;--cnt; } } for(int i=0;i<n;++i) { if(v[i]==0)//未被标记 { if(a[i]=='('||a[i]==')') printf("()"); else printf("[]"); } else printf("%c",a[i]); } return 0; }
第三题:
同样的先入后出。
分析:用字符串读入
对于输入的数据:
1.数字:入栈
2.符号:弹出栈顶两元素,令先入栈的+、-、*、/后入栈的,再将结果入栈。
注意:数字以'.'结尾,注意多位数的转化
代码:
int a[100]; string s; int tot=0; int main() { cin>>s; int n=s.length(); for(int i=0;i<n-1;++i) { char c=s[i]; if(c<='9'&&c>='0')//读入数字 { int k=0; while(s[i]!='.') { k=k*10+s[i]-'0'; ++i; } a[++tot]=k;//数字入栈 } if(c=='+') a[tot-1]=a[tot-1]+a[tot];--tot; //栈顶弹出两个,又入一个,相当于栈弹出一个 //注意数据改变即可,以下同上 if(c=='-') a[tot-1]=a[tot-1]-a[tot];--tot; if(c=='*') a[tot-1]=a[tot-1]*a[tot];--tot; if(c=='/') a[tot-1]=a[tot-1]/a[tot];--tot; } printf("%d",a[tot]); return 0; }
3.链表相关
4.P1160 队列安排 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
频繁的插入和删除,链表解决。
几乎模板,参照上方链表介绍。
注意:插入时注意顺序
由于按学号插入,故下标即为学号,结构体中不需value值。
代码:
struct list { int st,nex; }a[100010]; int v[100010]; int main() { int n;scanf("%d",&n); a[1].st=0;a[0].nex=1;//初始化 for(int i=2;i<=n;++i)//1号已经站好,从2号开始 { int k,p; scanf("%d%d",&k,&p); if(p==0) { a[i].st=a[k].st; a[i].nex=k; a[a[k].st].nex=i; a[k].st=i; //注意顺序 } if(p==1) { a[i].nex=a[k].nex; a[i].st=k; a[a[k].nex].st=i; a[k].nex=i; //注意顺序 } } int m;scanf("%d",&m); for(int i=1;i<=m;++i) { int k; scanf("%d",&k); if(a[k].nex==0&&a[k].st==0) continue; a[a[k].st].nex=a[k].nex; a[a[k].nex].st=a[k].st; a[k].nex=0;a[k].st=0; } for(int i=a[0].nex;i;i=a[i].nex) { printf("%d ",i); } return 0; }
尾记
stl内容物多,功能强大,是封装好的高封装层次的基础组件。
所学甚少,无法一窥全貌,今暂至此,日后还需更多学习。