11习题2.8 输出全排列(编程题)【PTA浙大版《数据结构(第2版)》题目集】

11习题2.8 输出全排列(编程题)【PTA浙大版《数据结构(第2版)》题目集】

1.原题链接

习题2.8 输出全排列 (pintia.cn)

2.题目描述

请编写程序输出前 n n n个正整数的全排列( n < 10 n<10 n<10),并通过9个测试用例(即 n n n从1到9)观察 n n n逐步增大时程序的运行时间。

输入格式:

输入给出正整数 n n n < 10 <10 <10)。

输出格式:

输出1到 n n n的全排列。每种排列占一行,数字间无空格。排列的输出顺序为字典序,即序列 a 1 , a 2 , ⋯   , a n { a_1, a_2, \cdots, a_n } a1,a2,,an排在序列 b 1 , b 2 , ⋯   , b n { b_1, b_2, \cdots, b_n } b1,b2,,bn之前,如果存在 k k k使得 a 1 = b 1 , ⋯   , a k = b k a_1=b_1, \cdots, a_k=b_k a1=b1,,ak=bk 并且 a k + 1 < b k + 1 a_{k+1}<b_{k+1} ak+1<bk+1

输入样例:

3

输出样例:

123
132
213
231
312
321

3.参考答案

答案一

#include<stdio.h>
int a[9];
void arrange(int m, int n){
    
    
	int i,j,num;
	if(m==n){
    
    
        for(i=0;i<n;i++)
            for(j=i+1;j<n;j++)
                if(a[i]==a[j])
                    return;
        for(i=0;i<n;i++)
            printf("%d",a[i]);
        printf("\n");
	}
	else{
    
    
		for(num=1;num<=n;num++){
    
    
            a[m]=num;
            arrange(m+1, n);
		}
	}
}
int main(){
    
    
    int n;
    scanf("%d", &n);
    arrange(0, n); 
    return 0;
}

答案二

#include<stdio.h>
int a[9];
void arrange(int m, int n){
    
    
	int i,j,num;
	if(m==n){
    
    	
		for(i=0;i<n;i++)
			printf("%d",a[i]);
		printf("\n");
	}
	else{
    
    
		for(num=1;num<=n;num++){
    
    
			for(j=0;j<m;j++)
				if(a[j]==num)break;
			if(j==m){
    
    
				a[m]=num;
				arrange(m+1, n);
			}
		}
	}
}
int main(){
    
    
    int n;
    scanf("%d", &n);
    arrange(0, n); 
    return 0;
}

答案三

#include <stdio.h>

int visited[10]={
    
    0};
int  list[10];
void dfs(int n,int m){
    
    
    int i;
	if(m==n+1){
    
    
		for(int i=1;i<=n;i++)
			printf("%d",list[i]);
		printf("\n");
	}
	else{
    
    
		for(i=1;i<=n;i++){
    
    
			if(!visited[i]){
    
    
				list[m]=i;	
				visited[i]=1;
				dfs(n,m+1);	
				visited[i]=0;
			}
		}
	}
}
int main(){
    
    
	int n;
	scanf("%d", &n);
	dfs(n,1);
	return 0;
} 

答案四

#include <stdio.h>
int a[10];
void shift( int l, int i);
void shiftb( int l, int i );
void Per( int l, int r );
int main(){
    
    
    int n, i;
    scanf("%d", &n);
    for (i=0; i<=n; i++) a[i] = i;
    Per(1, n);
    return 0;
}
void shift( int l, int i){
    
    
     int j, t=a[i];
     for (j=i; j>l; j--)
         a[j]=a[j-1];
     a[l] = t;
}
void shiftb( int l, int i ){
    
    
     int j, t=a[l];
     for (j=l; j<i; j++)
         a[j] = a[j+1];
     a[i] = t;
}
void Per( int l, int r ){
    
    
     int i;
     if (r==l) {
    
    
        for (i=1; i<=r; i++) printf("%d", a[i]);
        printf("\n");
     }
     else {
    
    
          for (i=l; i<=r; i++) {
    
    
              shift(l, i);
              Per(l+1, r);
              shiftb(l, i);
          }
     }
}

4.解题思路

本题输出前 n n n个正整数的全排列( n < 10 n<10 n<10),假如 n n n是固定的值,比如3,那么可以通过三重循环来实现全排列,实现方式如下:

#include<stdio.h>
int main(){
    
    
	int i,j,k;
	for(i=1;i<=3;i++){
    
    
		for(j=1;j<=3;j++){
    
    
			for(k=1;k<=3;k++){
    
    
				if(i!=j&&j!=k&&i!=k)
					printf("%d%d%d\n",i,j,k);
			}
		}
	}
	return 0;
}

但是本题中的 n n n是一个变量,如果用循环来实现,循环的层数是不确定,所以可以考虑用递归来实现。从循环的实现方式可以看出三点:

  1. 每一层循环按顺序选择一个数,调用递归自然也是每一层调用按顺序选择一个数;

  2. 循环到达最后一层时输出,调用递归函数自然也是递归调用到最后一层输出。

    //m记录当前层级,若当前层级m到达最大层级n,输出排列完毕之后的数组
    if(m==n){
          
          	
        for(i=0;i<n;i++)
            printf("%d",a[i]);
        printf("\n");
    }
    

    同时全排列的递归函数至少需要两个参数:1.当前调用的层级;2.最大的层级,即输入的 n n n

    //排列permutation
    void Per( int now, int n )
    
  3. 输出的每位数字不重复。如果要让输出的每位数字不重复有四种算法:

    • 算法1:上面三重循环是在输出前检查每位数字的重复性,本题中的 n n n是不确定的,也就是数字的个数不确定,如果在输出前检查每位数字的重复性,需要用二重循环将每位数字与其他位的每位数字做比较,该算法最后一个测试点会超时。
    • 算法2:优化算法1,不在最后检查数字的重复性,在进入每一层时检查。从前面示例的三重循环可以看出,每一层排列是遍历全部的数字,比如第一层循环是遍历1至3,第二层循环也是遍历1至3。检查当前层级选择的数字是否在前面层级出现过,若当前数字在前面的位置出现过,跳过该数字,若当前数字没有在前面的位置出现过,则将该数放入数组当前层级的位置。
    • 算法3:进一步优化算法2,不使用遍历的方法检查重复性,给每一个使用的数字做标记,通过标记判断选择的数字是否可以使用。
    • 算法4:先建立一个数组int a[10];,并初始化for (i=0; i<=n; i++) a[i] = i;,调整数组元素的位置,对数组进行全排列,因为数组元素的值从1至9已经被确定下来了,不会出现不同位数字重复的情况。这是本题参考答案采用的做法。

5.答案详解

答案一详解

该算法最后一个测试点会超时。

枚举每个位置可能的取值,生成所有可能的排列,实现了生成1到n的整数的全排列,并使用数组来存储每个位置上的值,在输出前检查数字是否重复,将满足无重复元素的排列输出。

具体分析如下:

  1. 定义了一个全局数组int a[9];,用于存储每一次生成的排列。
  2. 定义了递归函数void arrange(int m, int n);,该函数的作用是生成排列。
  3. arrange函数的参数m表示当前正在生成的排列的位置,n表示需要生成的排列的长度。
  4. 当m=n时,表示已经生成了一个完整的排列,此时需要检查该排列中是否有重复元素,如果有,则返回;否则,输出该排列,并换行。
  5. 当m<n时,需要继续生成排列,此时从1到n逐个尝试填入当前位置m,然后递归生成下一个位置的排列。
  6. main函数中,首先读入需要生成的排列长度n,然后调用arrange函数,将初始位置设为0
#include<stdio.h>
int a[9];
//m是当前层级,n是最大层级
void arrange(int m, int n){
    
    
	int i,j,num;
    //数组排列到n-1位置就排列完了,m==n排列完成输出
	if(m==n){
    
    
        //二重循环检查每位数字的重复性
        for(i=0;i<n;i++)
            for(j=i+1;j<n;j++)
                if(a[i]==a[j])//如果有数字重复则不输出
                    return;
        //如果程序执行到这里,则没有数字重复输出结果
        for(i=0;i<n;i++)
            printf("%d",a[i]);
        printf("\n");
	}
	else{
    
    
         //每一层都遍历所有的数字1至n
		for(num=1;num<=n;num++){
    
    
            a[m]=num;
            arrange(m+1, n);//递归方程计算下一个层级的数字
		}
	}
}
int main(){
    
    
    int n;
    scanf("%d", &n);
    arrange(0, n); 
    return 0;
}

答案二详解

在算法1的基础上优化,在每层选择数字时遍历前面的数字检查数字是否重复。

#include<stdio.h>
int a[9];
//m是当前层级,n是最大层级
void arrange(int m, int n){
    
    
	int i,j,num;
    //数组排列到n-1位置就排列完了,m==n排列完成输出 
	if(m==n){
    
    	
		for(i=0;i<n;i++)
			printf("%d",a[i]);
		printf("\n");
	}
	else{
    
    
         //每一层都遍历所有的数字1至n
		for(num=1;num<=n;num++){
    
    
             //检查当前选择的数字num是否在前面的位置j出现过
			for(j=0;j<m;j++)
                 //如果当前选择的数字num在前面的位置j出现过则退出此循环,遍历下一个数字
				if(a[j]==num)break;
             //结束上面的循环之后,若j==m,即当前选择的数字num在之前没有出现过
			if(j==m){
    
    
				a[m]=num;
				arrange(m+1, n);//递归方程计算下一个层级的数字
			}
		}
	}
}
int main(){
    
    
    int n;
    scanf("%d", &n);
    arrange(0, n); 
    return 0;
}

答案三详解

深度优先搜索(DFS)。遍历所有组合,在每层选择数字时通过标记检查数字是否已被使用。

数组int visited[10]记录一个数字是否被使用过,其中visited[i] = 0表示数字i还没有被使用,visited[i] = 1表示数字i已经被使用过。

数组int list[10];表示生成的排列,其中list[i]表示排列中第i个位置上的数字。

函数dfs(n, step)表示搜索在生成排列的第step个位置时的情况,其中n表示排列的长度(即1~n),step表示当前搜索的位置。

当step = n+1时,已经生成了一个完整的排列,直接输出即可。

否则,依次尝试使用1~n中的数字作为当前位置的数字,并在visited数组中标记该数字已经被使用过,然后递归地搜索下一个位置。当回溯到当前位置时,需要撤销visited数组的标记。

#include <stdio.h>

int visited[10]={
    
    0}; //做标记,数组下标对应相应的数字,没用过记作 0, 用过后记作 1 
int  list[10];
//m是当前层级,n是最大层级
void dfs(int n,int m){
    
    
    int i;
    //m==n+1时,n个层级排列完毕
	if(m==n+1){
    
    
		for(int i=1;i<=n;i++)
			printf("%d",list[i]);
		printf("\n");
	}
	else{
    
    
         //每一层都遍历所有的数字1至n
		for(i=1;i<=n;i++){
    
    
			if(!visited[i]){
    
    //如果选择的数字没用过 
				list[m]=i;	//把它存入数组对应层级的位置
				visited[i]=1;	//标记为已使用
				dfs(n,m+1);	//进入下一层
				visited[i]=0;	//取消标记
			}
		}
	}
}
int main(){
    
    
	int n;
	scanf("%d", &n);
	dfs(n,1);	//代表从第一层开始搜索 
	return 0;
} 

答案四详解

调整数组元素的位置,对数组进行全排列。

具体分析如下:

  1. 定义了一个长度为10的整型数组a[],并声明了三个函数:shift()shiftb()Per()
  2. main()函数中,首先输入一个整数n,然后用循环将数组a[]初始化为从1到n的整数。
  3. 然后调用Per()函数。
  4. Per()函数中,如果左右两端点相同,则输出当前从1n的全排列。
  5. 否则,对于左端点l到右端点r的范围内的每一个i,进行以下操作:
    • 调用shift()函数,将数组a[]中第i个元素移动到左端点l的位置。
    • l+1r的范围进行递归操作。
    • 调用shiftb()函数,将数组a[]还原到原始状态。
#include <stdio.h>
int a[10];//程序中用不到a[0],只使用a[1]至a[9]
void shift( int l, int i);
void shiftb( int l, int i );
void Per( int l, int r );

int main(){
    
    
    int n, i;
    scanf("%d", &n);
    for (i=0; i<=n; i++) a[i] = i;
    Per(1, n);
    return 0;
}

//对数组元素进行交换,将数组a[]中第i个元素移动到左端点l的位置
void shift( int l, int i){
    
    
     int j, t=a[i];
     for (j=i; j>l; j--)
         a[j]=a[j-1];
     a[l] = t;
}
//对数组元素进行交换,将数组a[]还原到原始状态
void shiftb( int l, int i ){
    
    
     int j, t=a[l];
     for (j=l; j<i; j++)
         a[j] = a[j+1];
     a[i] = t;
}
//排列permutation
void Per( int l, int r ){
    
    
     int i;
     //如果左右两端点相同,则排列完毕,输出当前的全排列
     if (r==l) {
    
    
        for (i=1; i<=r; i++) printf("%d", a[i]);
        printf("\n");
     }
     else {
    
    
          for (i=l; i<=r; i++) {
    
    
              //将数组a[]中第i个元素移动到左端点l的位置
              shift(l, i);
              //每一层调用都会有一个for循环,for循环改变每一层函数调用的左边界位置的数字
              Per(l+1, r);
              //每一层会先改变了元素位置,内层函数执行完毕后,再把位置改变回去以便下一个循环排列
              shiftb(l, i);
          }
     }
}

6.知识拓展

深度优先搜索

本题主要复习递归,关于深度优先搜索后面会做详细讲解,这里仅做简单介绍。

深度优先搜索(Depth-First Search,简称DFS)是一种常用的图遍历算法,它是通过深度遍历图中节点的方式来实现的。

具体来说,DFS算法从图的某个节点出发,首先访问该节点,然后选择一个相邻的未访问过的节点继续访问,直到所有相邻节点都被访问过。如果当前节点没有未访问的相邻节点,则回溯到上一个节点,继续访问它的未访问过的相邻节点,直到所有节点都被访问过。

DFS算法可以使用递归实现,也可以使用栈来实现。使用递归实现时,可以通过一个visited数组来记录已经访问过的节点,防止重复访问;使用栈实现时,则可以利用栈的后进先出(LIFO)的特性,来保存当前节点的未访问相邻节点。

猜你喜欢

转载自blog.csdn.net/weixin_40171190/article/details/130048516