P1427
题目描述
小鱼最近被要求参加一个数字游戏,要求它把看到的一串数字(长度不一定,以0结束,最多不超过100个,数字不超过2^32-1),记住了然后反着念出来(表示结束的数字0就不要念出来了)。这对小鱼的那点记忆力来说实在是太难了,你也不想想小鱼的整个脑袋才多大,其中一部分还是好吃的肉!所以请你帮小鱼编程解决这个问题。
输入输出格式
输入格式:
一行内输入一串整数,以0结束,以空格间隔。
输出格式:
一行内倒着输出这一串整数,以空格间隔。
输入输出样例
输入样例:
3 65 23 5 34 1 30 0
输出样例:
30 1 34 5 23 65 3
提供以下几种做法:
1. 常规做法:反向遍历
#include<iostream> using namespace std; int x[100],c=0; int main(){ for(int i=0;;i++){ cin>>x[i]; if(x[i]==0) break; c=i; } for(int j=c;j>=0;j--) cout<<x[j]<<" "; return 0; }
2. 栈
栈是NOIP里的一种必会的基础数据结构,结构简单功能强大。栈的基本思想是先进后出,后进先出。利用栈的这个特性往往可以完成一些意想不到的操作。栈的一个用途就是将一串数据反向输出。
①手打栈
栈的实现比较简单,只需要开一个空数组,然后用一个top变量表示栈顶元素的位置即可。
入栈就将top++,然后将栈顶元素赋值即可。出栈只需top--,连清零都不用。下面就是一段手打栈解法代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 int a[101]; 4 int top=0,c; 5 int main(){ 6 while(1){ 7 cin>>c; 8 if(c==0)break; 9 a[++top]=c; 10 } 11 while(top!=0){ 12 cout<<a[top--]<<" "; 13 } 14 return 0; 15 }
②STL栈(C++)
直接调用STL里和栈有关的函数。
用vector:
代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 vector<int> a;//定义一个int型的vector 4 int c; 5 int main(){ 6 while(1){ 7 cin>>c; 8 if(c==0)break; 9 a.push_back(c);//将括号里的元素压入vector尾部 10 } 11 while(!a.empty()){ 12 cout<<a.back()<<" ";//.back()是一个返回vector尾部元素的函数 13 a.pop_back();//删除vector尾部的元素 14 } 15 /* 16 这一部分输出程序也可以写成: 17 for(int i=a.size()-1;i>=0;i--){//a.size()返回a中元素的个数 18 cout<<a[i]<<" "; 19 } 20 要注意vector是从a[0]开始存储a.size()个元素,要当心越界访问 21 */ 22 return 0; 23 }
用stack:
stack与vector类似,但是函数名称简洁了不少,而且省去了一些对于栈无用的冗杂的函数。
代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 stack<int> a;//定义一个int型的stack 4 int c; 5 int main(){ 6 while(1){ 7 cin>>c; 8 if(c==0)break; 9 a.push(c); 10 } 11 while(!a.empty()){ 12 cout<<a.top()<<" "; 13 a.pop(); 14 } 15 return 0; 16 }
3. 指针
①数组指针
1 #include <bits/stdc++.h> 2 using namespace std; 3 int a[101],c; 4 int *p=a;//定义一个int类型的指针,指向数组不用取地址符 5 int main(){ 6 while(1){ 7 cin>>c; 8 if(c==0)break; 9 p++;//指针指向当前所指元素在数组中的下一个元素 10 *p=c;//星号表示这个指针现在不是代表地址而是代表该地址的元素 11 } 12 while(p!=a){//当指针不是指向数组的第一个元素地址 13 cout<<*p<<" "; 14 p--; 15 } 16 return 0; 17 }
②STL指针
指针在STL里有一个更高端大气上档次的名字——迭代器。迭代器的用法和数组指针操作差不多,只是初始元素指针一般用STL自带的 .begin()函数返回。
下面是用迭代器的代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 vector<int> a; 4 int c; 5 int main(){ 6 while(1){ 7 cin>>c; 8 if(c==0)break; 9 a.push_back(c); 10 } 11 for(vector<int>::iterator it=a.end()-1;it>=a.begin();it--){ 12 /*a.end()返回的是a中最后一个元素后面的地址,因此一定要记得-1 13 a.begin()返回a的第一个元素的地址 14 */ 15 cout<<*it<<" ";//*it表示it所指的元素 16 } 17 return 0; 18 }
4. 链表
建立一个双向链表,将各个元素正向连接起来后反向遍历输出。
1 #include <bits/stdc++.h> 2 using namespace std; 3 struct node{ 4 int pre; 5 int next; 6 int value; 7 }a[101]; 8 int c; 9 int n=0;//表示当前建立了几个链表单元 10 int re=0;//表示上一个链表单元 11 int main(){ 12 while(1){ 13 cin>>c; 14 if(c==0)break; 15 n++; 16 a[n].value=c; 17 a[n].pre=re; 18 a[re].next=n; 19 re=n; 20 } 21 int now=n;//表示现在在访问第几个链表单元 22 while(now!=0){ 23 cout<<a[now].value<<" "; 24 now=a[now].pre; 25 } 26 return 0; 27 }
5. 排序
①sort排序
这个题目里,我们可以用一个结构体来存储各个数据点被输入的先后顺序,然后按照从晚到早的顺序排一遍序,之后顺次输出即可。要注意的一点是,sort函数默认只能将存储数字类型的数组从小到大排序,如果要自定义排序优先级,则要自定义比较函数。函数需要两个参量,分别表示数组中被比较的两个元素。比较的规则是使排序后的两个元素能够满足在前的元素与在后的元素分别代入比较函数的前后两个参量后能够 return 1。直白点就是比较两个元素,满足条件自定义函数里的排在前面。
代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 struct node{ 4 int time;//存储该数据被输入的时间先后 5 int value;//存储该数据的赋值 6 }a[101]; 7 int c,n; 8 int cmp(node x,node y){//自定义比较函数 9 return x.time>y.time; 10 } 11 int main(){ 12 while(1){ 13 cin>>c; 14 if(c==0) break; 15 a[n].value=c; 16 a[n].time=n; 17 n++; 18 } 19 sort(a,a+n,cmp);//前两个变量分别表示开始排序的元素的地址和结束排序的元素的地址 20 for(int i=0;i<n;i++){ 21 cout<<a[i].value<<" "; 22 } 23 return 0; 24 }
②STL优先队列堆排序
STL可以提供优先队列——priority_queue,它的排序写法和sort类似,但是它的时间复杂度更优,可以将输入的数据以O(log n)的时间复杂度排序,而且不用调用函数就会随时对数组中的元素排序。
不过它也有一些缺点,比如说不能对有序序列单点查询,只能每次访问排序后的第一个元素或将其删除。而且中途改变比较优先级函数会导致混乱……但这并不能掩盖它的功能的强大性。
代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 struct node{ 4 int time; 5 int value; 6 }t; 7 priority_queue<node> a; 8 bool operator<(const node &x,const node &y){//这里比较函数的名字不能改,两个const和两个&也不能去掉 9 return x.time<y.time; 10 }//定义优先队列的比较函数,这个和sort相反,是使得排序后前后代入可以return false,具体细节请自行了解吧 11 int c,n; 12 int main(){ 13 while(1){ 14 cin>>c; 15 if(c==0) break; 16 n++; 17 t.value=c; 18 t.time=n; 19 a.push(t);//将元素输入优先队列 20 } 21 while(!a.empty()){ 22 cout<<a.top().value<<" ";//.top()返回优先队列的第一个元素 23 a.pop();//删除优先队列的第一个元素 24 } 25 return 0; 26 }
6. 搜索
①DFS
把输入的数据按输入先后依次连接起来,之后从第一个元素开始DFS递归到底,然后按照出栈序输出。
代码如下:
#include <bits/stdc++.h> using namespace std; struct node{ int value; vector<int> to;//存储这个元素指向的所有元素 }a[101]; int c,n=0; void dfs(int x){ for(vector<int>::iterator it=a[x].to.begin();it<a[x].to.end();it++){ dfs(*it); }//遍历这个元素的所有搜索子树并分别递归 cout<<a[x].value<<" "; return; } int main(){ while(1){ cin>>c; if(c==0) break; n++; a[n].value=c; a[n-1].to.push_back(n); } dfs(1);//从第一个元素开始递归DFS搜索 return 0; }
②BFS
如果说深度优先搜索是用时间换空间,那么广度优先搜索就是用空间换时间。广度优先搜索代码量也不大,但是思路可能会比深度优先搜索难理解一点,而且很难优化,但是大部分时候广度优先搜索的时间复杂度都要大大优于深度优先搜索。
广度优先搜索的核心思路在于每次向下搜索时不是每次都走到搜索树的末端,而是将搜索树的整个下一层存入一个队列,然后按照出队序搜索,如果搜索到了满足条件的结果或队列为空就结束搜索。(我知道我又没讲明白,还是请感兴趣的同学自己去了解一下吧)
顺道科普一下队列:这是一种先入先出,后入后出的数据结构。相对于栈没有那么高的可操作性,但是也很实用。STL中也有一种专门为队列设计的数据结构:queue。
BFS做法和DFS做法的区别在于:DFS是建立在栈的基础上的,而BFS是建立在队列的基础上的。因此在用BFS就不能再从第一个元素开始搜索了,而要从最后一个 元素开始向前搜索。
代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 struct node{ 4 int value,num; 5 vector<node> from;//存储指向这个元素的所有元素 6 }a[101]; 7 int c,n=0; 8 queue<node> q; 9 void bfs(node x){ 10 q.push(x); 11 while(!q.empty()){//.empty()返回队列是否为空 12 cout<<q.front().value<<" ";//.front()返回队头元素 13 for(vector<node>::iterator it=q.front().from.begin();it<q.front().from.end();it++){ 14 if((*it).num!=0)q.push(*it);//.push()将括号内的元素压入队列 15 } 16 q.pop();//.pop()删除队头元素 17 } 18 return; 19 } 20 int main(){ 21 while(1){ 22 cin>>c; 23 if(c==0) break; 24 n++; 25 a[n].value=c; 26 a[n].num=n; 27 a[n].from.push_back(a[n-1]); 28 } 29 bfs(a[n]);//从最后一个元素开始BFS搜索 30 return 0; 31 }
7. 二叉树后序遍历
将每一个数据都存到它的上一个数据的左儿子里,然后后序遍历。
1 #include <bits/stdc++.h> 2 using namespace std; 3 struct tree{ 4 int value; 5 int left,right;//左儿子,右儿子 6 }a[101]; 7 int c,n=0; 8 void dfs(int x){ 9 if(a[x].left!=0){ 10 dfs(a[x].left); 11 } 12 if(a[x].right!=0){ 13 dfs(a[x].right); 14 } 15 cout<<a[x].value<<" "; 16 return; 17 } 18 int main(){ 19 while(1){ 20 cin>>c; 21 if(c==0) break; 22 n++; 23 a[n].value=c; 24 a[n-1].left=n; 25 } 26 dfs(1);//以第一个元素为根开始遍历 27 return 0; 28 }
8. 拓扑排序
提醒:求拓扑排序的方法一般是:将所有入度为零的点存入一个队列,然后每次访问队首,将队首指向的元素的入度减1,然后将队首存入另一个队列用作遍历序的存储或直接输出。当一个点被更新入度直到入度为零后,就将这个点存入存入度为零的点的队列。直到这个队列为空,结束算法。
对于这个题,我们可以将各个数据按照输入先后顺序反向建边,然后这个图的拓扑序就是我们需要的答案了。
代码如下:
#include <bits/stdc++.h> using namespace std; struct node{ int pre;//入度 int value; vector<int> to;//储存这个元素所指向的所有元素 }a[101]; queue<node> q; int c,n=0; void solve(){ while(!q.empty()){ cout<<q.front().value<<" "; for(vector<int>::iterator it=q.front().to.begin();it<q.front().to.end();it++){ a[*it].pre--; if(a[*it].pre==0&&*it!=0) q.push(a[*it]); } q.pop(); } return; } int main(){ while(1){ cin>>c; if(c==0) break; n++; a[n].value=c; a[n].to.push_back(n-1); a[n-1].pre++;//被指向的元素入度加1 } for(int i=1;i<=n;i++){ if(a[i].pre==0) q.push(a[i]); } solve(); return 0; } #include <bits/stdc++.h> using namespace std; struct node{ int pre;//入度 int value; vector<int> to;//储存这个元素所指向的所有元素 }a[101]; queue<node> q; int c,n=0; void solve(){ while(!q.empty()){ cout<<q.front().value<<" "; for(vector<int>::iterator it=q.front().to.begin();it<q.front().to.end();it++){ a[*it].pre--; if(a[*it].pre==0&&*it!=0) q.push(a[*it]); } q.pop(); } return; } int main(){ while(1){ cin>>c; if(c==0) break; n++; a[n].value=c; a[n].to.push_back(n-1); a[n-1].pre++;//被指向的元素入度加1 } for(int i=1;i<=n;i++){ if(a[i].pre==0) q.push(a[i]); } solve(); return 0; }
9. 最短路径
本题与最短路的关联是:将所有元素按照输入先后顺序建边,然后求出到最后一个元素的最短路,按距离大小排序后输出,即得所求答案。
①dijkstra算法
dijkstra算法是被使用得最广的一种求单源最短路的算法。
它最大的优点就是稳定,但同时也有两个缺点:一是不能应对有负权边的图,二是时间复杂度不是很优,O(n^2)。
不过可以借助优先队列优化来将时间复杂度优化为O(n log n)。
dijkstra的核心思想是:如果到某个点的距离已经是目前可从已确定点到达的最短距离了,那么从距离更远的点就不可能以更短的距离到达这个点了。这也是它不能应对有负权边的图的原因。
代码如下:
#include <bits/stdc++.h> using namespace std; const int INF=2147483647; struct node{ int value; int dis;//储存这个点到源点的最短距离 bool visit;//存储这个点的距离是否已经被确定为最短距离 vector<int> to;//储存这个元素所连接的所有元素 }a[101]; int c,n=0; void dijkstra(int x){ int q[101],head=0,tail=0;//存储已经确定最短路的点,这里选择手打队列 int minn,now; a[x].dis=0; a[x].visit=true; q[tail++]=x;//不要搞混++i和i++的区别 for(int i=1;i<n;i++){ minn=INF; for(int j=head;j<tail;j++) for(vector<int>::iterator it=a[q[j]].to.begin();it<a[q[j]].to.end();it++){ if(a[q[j]].dis+1<minn&&!a[*it].visit){ minn=a[q[j]].dis+1; now=*it; } }//循环遍历求当前所能到达的距离最近的点 q[tail++]=now; a[now].visit=true; a[now].dis=minn; } return; } int cmp(node x,node y){ return x.dis<y.dis; } int main(){ while(1){ cin>>c; if(c==0) break; n++; a[n].value=c; a[n].to.push_back(n-1); a[n-1].to.push_back(n);//因为是无向边所以边连接的两个元素都要建边 } dijkstra(n);//以n作为源点求最短路 sort(a+1,a+n+1,cmp); for(int i=1;i<=n;i++){ cout<<a[i].value<<" "; } return 0; }
②spfa算法
spfa算法全称为Shortest Path Faster Algorithm,即"最短路快速算法"。
spfa算法解决了dijkstra最大的缺点:不能应对有负权边的图。但同时它也有着自己的缺点,那就是如果出题人很善良,你的时间复杂度可能只有O(n)级别,但是如果出题人故意卡你的数据的话,算法复杂度就可能是O(nm)的……
spfa的核心思路是用已访问的点去不断的更新它所连接的所有点的最短距离的值。如果成功更新了,那么说明被更新的点之前更新别的点的结果偏大,于是就应当把被更新点入队,再次去更新其他的点,直到不能更新为止。
代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int INF=2147483647; 4 struct node{ 5 int value; 6 int dis=INF; 7 bool in;//存储这个点是否已经在队列中 8 vector<int> to;//储存这个元素所连接的所有元素 9 }a[101]; 10 int c,n=0; 11 void spfa(int x){ 12 queue<int> q;; 13 int minn,now; 14 a[x].dis=0; 15 a[x].in=true; 16 q.push(x); 17 while(!q.empty()){ 18 for(vector<int>::iterator it=a[q.front()].to.begin();it<a[q.front()].to.end();it++){ 19 if(a[q.front()].dis+1<a[*it].dis){ 20 a[*it].dis=a[q.front()].dis+1; 21 if(!a[*it].in){ 22 q.push(*it); 23 a[*it].in=true; 24 }//如果这个点已经在队列中就不用重复入队了 25 } 26 } 27 a[q.front()].in=false; 28 q.pop(); 29 } 30 return; 31 } 32 int cmp(node x,node y){ 33 return x.dis<y.dis; 34 } 35 int main(){ 36 while(1){ 37 cin>>c; 38 if(c==0) break; 39 n++; 40 a[n].value=c; 41 a[n].to.push_back(n-1); 42 a[n-1].to.push_back(n);//因为是无向边所以边连接的两个元素都要建边 43 } 44 spfa(n);//以n作为源点求最短路 45 sort(a+1,a+n+1,cmp); 46 for(int i=1;i<=n;i++){ 47 cout<<a[i].value<<" "; 48 } 49 return 0; 50 }