归纳法-生成全排列算法Java实现

   全排列问题

    从n个不同元素中任取m(m≤n)个元素,按照一定的顺序排列起来,叫做从n个不同元素中取出m个元素的一个排列。当m=n时所有的排列情况叫全排列。《算法设计与技巧分析》这本书中,使用归纳法生成一组数【1,2,3……n】的所有排列问题,并用数组P[1……n]来存放每一个排列。该归纳法基于这样一个假设:可以生成n-1个数的所有排列,那么就可以扩展方法,生成1,2,……n这n个数的全排列。

   全排列生成方法

    生成2,3,……n,这n-1个数的所有排列,再在这些所有排列前面加上第N个数1;接下来生成1,3,4……n这n-1个数的排列,然后再在每个排列前面加上第N个数2,依次类推,直到生成1,2,3……n-1这n-1个数的所有排列,并且在每个排列前面加上第N个数n,那么这所有的排列就构成了1,2,……n这个N个数所有的排列结果集了。

     这一过程可以描述为,设一集合变量p = {r1, r2, r3, ... ,rn}, 全排列为permute(p),定义一个集合变量pn,pn=p-{rn},即pn是集合p移除某个元素rn后的集合。那么r1permute(p1)就是集合[r2,r3,……rn]这n-1个数的排列的,然后每个排列之前在加上第N个元素r1得到的排列。

   归纳公式

    求p=【1,2,3……n】的全排列的算法,可以归纳得到计算公式为:permute(p)=1.permute(p1)+2.permute(p2)+3.permute(p3)……n.permute(pn),也就是直接对于序列1,2,3……n,依次将第i个元素和第一个元素进行交换,然后再求剩余n-1个元素的集合的全排列,最后加上第一个元素,就成了一轮排列;还原交换的数据,再去求下一轮排列。所以实现算法又称为置换算法,递归实现全排列算法如下:

/**
 * 归纳法的排列Arrangement
 * 1.2.3....n:生成所有排列的问题,以数组p[1-n]来存放每一个排列,假设可以生成n-1个数的所有排列。
 * 那么在这些所有排列的前面加上第n个数就好了。
 * 置换法:初始时时1-n的顺序排列的一种排列方式
 * 因此perm(p) = r1perm(p1), r2perm(p2), r3perm(p3), ... , rnperm(pn)。当n = 1时perm(p} = r1。
 * r1perm(p1)就是以r1开头的所有pn-r1的其他元素的全排列
为了更容易理解,将整组数中的所有的数分别与第一个数交换,这样就总是在处理后n-1个数的全排列。
 * @author 金涛
 *
 */
public class Permutation {
	private static int count=0 ;
	private static int n ;
	private static int [] arrangementResult;
	
	public static void arrange(){
		Scanner scanner = new Scanner(System.in);
		System.out.println("请输入数字n:");
		int n = scanner.nextInt();
		Permutation.n = n;
		scanner.close();
		arrangementResult= new int[n+1];
		for(int i =1;i<n+1;i++){
			arrangementResult[i] = i;
		}
		
		permutation(1);
	}

	private static void permutation(int m) {
		//由于一个数的全排列就是其本身,所以m=n时是一种全排列
		if(m==n){
			count++;
			System.out.print("此次排列结果:");
			printResult();
		}else{
			for(int j = m;j<=n;j++){
				swap(j,m);
				permutation(m+1);
				swap(m,j);
			}
		}
	}

	private static void printResult(){
		for(int i =1;i<=n;i++){
			System.out.print(arrangementResult[i]+"  ");
		}
		System.out.println();
	}
	
	private static void swap(int j,int m){
		int temp_j = arrangementResult[j];
		int temp_m = arrangementResult[m];
		arrangementResult[j]=temp_m;
		arrangementResult[m]=temp_j;
	}
	
	public static void main(String[] args) {
		arrange();
		System.out.println("总共有"+count+"种排列方式。");
	}
}

      此处有两个交换操作,第一个交换是将第j个元素,与待求全排列的集合的第一个元素交换,然后再求取出首元素后剩余N-1个元素的全排列,这是递归算法的实现。完成一轮全排列的输出后,还需要将元素交换回去,保证初始原始的顺序,再求下一轮的全排序。

   算法分析

     由于存在n!种排列,所以算法permute(1)的第一步输出某个排列的操作,总共需要执行n*n!次,即输出所有的排列;第二步的for循环操作,permute(1)时,m=1,因此for循环执行n次,然后再加上递归调用permute(2)的执行次数,由此可归纳出该算法中for循环的反复次数f(n)的递推公式为:

     算法分析和设计中,这个递推公式在算法设计与分析中得出的总和f(n)=n!h(n)<2n!(求解挺复杂,待细细推论理解)。这说明整个算法的执行时间由两部分组成:输出部分n*n!+循环总次数<n*n!+2*n!。也就是(n+1)*n!,算法的时间复杂度由输出语句决定,即n*n!。算法使用的递归,当n过大时,还是很耗时间的。问答频道看到一个求全排列算法的问题,顺便重新学习下这个算法的实现。算法设计这门学问还是很深奥的,各种算法思想,时间和空间复杂度分析,考验我早已遗忘的数学知识啊!



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值