方法一:暴力搜索。
比较容易想到,先按照全排列的方法,找出火车进站序列的全排列。从全排列序列中找到符合出站规则的序列,再将所有的合法序列排序,最后依次打印输出。
出站规则:用栈储存入站火车序号,另一边用一个指针顺序遍历当前的全排列序列,对比当前栈顶序号是否与指针所指的全排列序列号相等。
相等就指针右移,栈顶序号出栈。若栈空就进栈下一个火车序号,继续与指针所指序号对比。
时间复杂度:O(n*n!),空间复杂度O(catalan(n)),排序时用到了卡特兰数大小的空间。
/*从全排列序列中找出正确的出栈序列*/ #include <stdio.h> int p=0; void legal(int* a,int* b,int len,int c[][len],long d[][2]){//判断全排列的其中一个序列是否合法,并将值存入out int in[len],top=0,i=1,j=0,flag=0;//top为入站序列的栈顶指针,i为入站序列指针,j为出站序列指针 in[top]=a[0]; while(j<len){//如果还有火车没有出站 if(in[top]==b[j]){ top--; if(top==-1 && i<len){//车站空就入站 in[++top]=a[i++]; } j++; } else if(i<len){ in[++top]=a[i++]; } else{ flag=1; break; } } if(!flag){//合法就输出该序列 long sum=0; for(int j=0;j<len;j++){ c[p][j]=b[j]; sum=10*sum+b[j]; } d[p][0]=sum; d[p][1]=p; p++; } } void sort(int* a,int* b,int start,int len,int c[][len],long d[][2]){//全排列出站序列,a[]为原入站序列,b[]为全排列序列 int t; if(start>=len){ legal(a,b,len,c,d); } for(int i=start;i<len;i++){ if(start!=len){//交换两值后,对剩余len-i个元素全排列 t=b[start]; b[start]=b[i]; b[i]=t; } sort(a,b,start+1,len,c,d); if(start!=len){//回溯,返回交换之前的状态 t=b[start]; b[start]=b[i]; b[i]=t; } } } int catalan(n){//计算卡特兰数 long x1=1,x2=1; for(int i=n;i>1;i--){ x1*=i; } for(int i=2*n;i>n;i--){ x2*=i; } x2/=x1*(n+1); return x2; } void bubble_sort(long a[][2],int n){ for(int i=0;i<n-1;i++){ long t1,t2,flag=0; for(int j=n-1;j>i;j--){ if(a[j-1][0]>a[j][0]){ t1=a[j][0];t2=a[j][1]; a[j][0]=a[j-1][0]; a[j][1]=a[j-1][1]; a[j-1][0]=t1; a[j-1][1]=t2; flag=1; } } if(!flag){ return; } } } int main(){ int N;scanf("%d",&N); int a[11]={0},stack[N],in[N],I=0,t; int NN=catalan(N); int out[NN][N];long out1[NN][2]; while(scanf("%d",&t)!=EOF){ a[t]=1; stack[I++]=t;//stack[]用于储存入站火车序号 } t=0; for(int i=1;i<11;i++){//为了优化最后排序的次数,in[]按序号大小存储入站火车号 if(a[i]){ in[t++]=i; } } sort(stack,in,0,N,out,out1); bubble_sort(out1,NN); for(int i=0;i<NN;i++){ for(int j=0;j<N;j++){ printf("%d ",out[out1[i][1]][j]); } printf("\n"); } }
执行耗时23ms,消耗内存560KB
优化:用按字典序的全排列,边排边输出,不用再排序。(c++可以用next_permutation()函数实现去重递增的字典排序)
时间复杂度:O(n*n!),空间复杂度O(n),最优的空间利用。
/*从全排列序列中找出正确的出栈序列*/ #include<stdio.h> void legal(int* a,int* b,int len){//判断全排列的其中一个序列是否合法 int in[len],top=0,i=1,j=0,flag=0;//top为入站序列的栈顶指针,i为入站序列指针,j为出站序列指针 in[top]=a[0]; while(j<len){//如果还有火车没有出站 if(in[top]==b[j]){ top--; if(top==-1 && i<len){//车站空就入站 in[++top]=a[i++]; } j++; } else if(i<len){ in[++top]=a[i++]; } else{ flag=1; break; } } if(!flag){//合法就输出该序列 for(int j=0;j<len;j++){ printf("%d ",b[j]); } printf("\n"); } } int search(int* a,int key,int low,int high){//二分查找 int mid; while(low<=high){ mid=(low+high)/2; if(a[mid]==key){ return mid; } else if(a[mid]<key){ high=mid-1; } else{ low=mid+1; } } if(a[mid]>key){ return mid; } else{ return mid-1; } } void swap(int* a,int* b){//交换 int c; c=*a;*a=*b;*b=c; } void sort(int* a,int n,int* b){//改变当前序列为全排列按字典输出的下一个序列 legal(b,a,n); int pos=n-1; while(pos>0 && a[pos]<a[pos-1]){ pos--; } if(!pos){//已经完全逆序了就中止退出 return; } else{ int p=search(a,a[pos-1],pos,n-1);//二分查找确定交换位置 swap(&a[pos-1],&a[p]); } for(int i=pos;i<=(n-1+pos)/2;i++){//逆序pos后面的序列值 swap(&a[i],&a[n-1+pos-i]); } sort(a,n,b);//继续排下一个 } int main() { int N;scanf("%d",&N); int a[11]={0},stack[N],in[N],I=0,t; while(scanf("%d",&t)!=EOF){ a[t]=1; stack[I++]=t;//stack[]用于储存入站火车序号 } t=0; for(int i=1;i<11;i++){//为了优化最后排序的次数,in[]按序号大小存储入站火车号 if(a[i]){ in[t++]=i; } } sort(in,N,stack); }执行耗时18ms,消耗内存328KB
方法二:利用DFS遍历所有的合法情况,最后排序输出。(可以使用C库函数stdlib自带的qsort()排序)
因为是基于DFS,它是将一种情况或条件递归到尽头的办法。所以可以把问题对象转移为车站里是否有火车,可以分出3种情况:
1.如果站内还有火车,火车立刻出站,递归到站内没有火车,回溯;
2.如果还有火车没有进站,火车立刻进站,递归到所有火车都进站,回溯;
3.如果所有火车都曾进站,且站内没火车,此次递归结束,输出一个序列;
时间复杂度O(n!),空间复杂度O(catalan(n)),排序时用到了卡特兰数大小的空间。
/*DFS迭代找出合法序列*/ #include <stdio.h> #include <string.h> int out_p=0;//out[]的指针,表明有多少火车已出站 int in_p=0;//in[]的指针,指向下一个入站火车 int stack_p=-1;//stack[]的指针,指向车站内的栈顶火车 int I=0;//记录合法序列个数 void dfs(int* a,int* b,int* c,int n,char d[][n]){ if(out_p==n){//递归出口:当火车全部出站,记录该合法序列 for(int i=0;i<n;i++){ d[I][i]=a[i]+'0'; } I++; return; } if(stack_p>-1){//递归场景1:如果站内还有火车,立刻出站,并使出站序列out_p++ a[out_p++]=b[stack_p--];//火车出站 dfs(a,b,c,n,d); b[++stack_p]=a[--out_p];//回溯 } if(in_p<n){//递归场景2:如果还有火车没有进站,立刻进站,并使stack_p++ b[++stack_p]=c[in_p++];//火车进站 dfs(a,b,c,n,d); in_p--;stack_p--;//回溯 } } int partition(int n,char a[][n],int low,int high){//快排中确定基准序号 char* p=(char*)malloc(n); for(int i=0;i<n;i++){//原字符串没有'\0'结束符,无法使用strcpy()函数 p[i]=a[low][i]; } while(low<high){ while(low<high && strcmp(a[high],p)>=0){ high--; } for(int i=0;i<n;i++){ a[low][i]=a[high][i]; } while(low<high && strcmp(a[low],p)<=0){ low++; } for(int i=0;i<n;i++){ a[high][i]=a[low][i]; } } for(int i=0;i<n;i++){ a[low][i]=p[i]; } free(p); return low; } void quicksort(int n,char a[][n],int low,int high){//快速排序 if(low<high){ int p=partition(n,a,low,high); quicksort(n,a,low,p-1); quicksort(n,a,p+1,high); } } int catalan(n){//计算卡特兰数 long x1=1,x2=1; for(int i=n;i>1;i--){ x1*=i; } for(int i=2*n;i>n;i--){ x2*=i; } x2/=x1*(n+1); return x2; } int main(){ int N;scanf("%d",&N); int NN=catalan(N);//计算有多少个合法序列 char legal[NN][N];//为所有的合法序列预先分配空间 int in[N],t,k=0; while(scanf("%d",&t)!=EOF){ in[k++]=t;//in[]用于储存入站火车序号,即初始的序列 } int out[N];//记录出站的火车序号 int stack[N];//记录正在站内等候的火车序号 dfs(out,stack,in,N,legal);//找出所有合法序列 quicksort(N,legal,0,NN-1);//排序 for(int i=0;i<NN;i++){ for(int j=0;j<N;j++){ printf("%c ",legal[i][j]); } printf("\n"); } }执行耗时5ms,消耗内存380KB