从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过大时,还是很耗时间的。问答频道看到一个求全排列算法的问题,顺便重新学习下这个算法的实现。算法设计这门学问还是很深奥的,各种算法思想,时间和空间复杂度分析,考验我早已遗忘的数学知识啊!