递归与分治之全排列问题和火车进站问题

来源:互联网 发布:数据分析报表怎么做 编辑:程序博客网 时间:2024/05/17 04:42

1、问题:给出一组数列,输出它的全排列。

2、分析

假设有1个数,那就是它本身,有2个数1,2,那全排列只有2组为1,2和2,1;有3个数1,2,3,全排列有6组为1,2,3;1,3,2;2,1,3;2,3,1,;3,1,2;3,2,1;......

        假设数集合为U,全排列记为Perm(U),U的全排列定义如下:

   (1)  当n=1时,Perm(U) = (u),u是集合中的元素。

   (2)  当n>1时,Perm(U) 由(u1)Perm(U1), (u2)Perm(U2),(u3)Perm(U3).....(un)Perm(Un)构成。

        其中U1,U2,U3...Un的长度是length-1.  (假设U的长度是length),此时问题被分割成n个子问题,还需将U1,U2,U3......Un分别分成n个子问题,一直分割下去,知道每个子问题的长度为1,此时子问题的数量n!每个子问题的很容易解。

public class Main {public static void main(String[] args) {int []array = {1,2,3};Perm(array,0,2);}public static void Perm(int[] array,int k,int m){System.out.println("Perm("+k+","+m+")");int temp = 0;if(k == m){for(int i=0;i<=m;i++){System.out.print(array[i]+" ");}System.out.println();}else{for(int i=k;i<=m;i++){temp = array[k];array[k] = array[i];array[i] = temp;System.out.println("k:"+k+" i:"+i);Perm(array,k+1,m);temp = array[k];array[k] = array[i];array[i] = temp;System.out.println("k:"+k+" i:"+i);}}}}
运行结果:

Perm(0,2)
k:0 i:0
Perm(1,2)
k:1 i:1
Perm(2,2)
1 2 3 
k:1 i:1
k:1 i:2
Perm(2,2)
1 3 2 
k:1 i:2
k:0 i:0

k:0 i:1
Perm(1,2)
k:1 i:1
Perm(2,2)
2 1 3 
k:1 i:1
k:1 i:2
Perm(2,2)
2 3 1 
k:1 i:2
k:0 i:1

k:0 i:2
Perm(1,2)
k:1 i:1
Perm(2,2)
3 2 1 
k:1 i:1
k:1 i:2
Perm(2,2)
3 1 2 
k:1 i:2
k:0 i:2


首先,Perm(0,2)被分为3个子问题,第一个子问题第一步是第一个位置和自己交换,第二个子问题第一步是第一个位置和第二个位置交换,第三个子问题第一步是第一个位置和第三个位置交换,它们的第二步都是第二个与第三个元素交换,此外在每次交换后,输出该排序后,还要把之前交换的过的顺序再交换回来。这样再进行下面的交换顺序,顺序是可控的。


另一种全排列的实现方式

import java.util.Scanner;public class Main {// 全排列数的个数static int n;// 存放全排列的结果static int[] array;// 用于标记该数字是否可用static int[] book;  // 保存要全排列的数static int[] input;public static void main(String[] args) {Scanner scanner = new Scanner(System.in);n = scanner.nextInt();input = new int[n];array = new int[n];book = new int[n];for(int i=0;i<n;i++){input[i] = scanner.nextInt();array[i] = 0;book[i] = 0;}dfs(0);scanner.close();}public static void  dfs(int step){int i;// 全排列已经排好,输出if(step == n){for(i=0;i<n;i++){System.out.print(array[i]);}System.out.println();return;}for(i=0;i<n;i++){if(book[i] == 0){array[step] = input[i];// 表示该数字已经不可用book[i] = 1;dfs(step+1);book[i] = 0;}}return;}}







3、应用实例:火车进站问题

火车进站问题涉及到栈的知识,但是解决火车进站问题如果从栈的角度可能比较复杂,如果从全排列角度可能会更容易点。

解决火车进站问题的思路:假设有1,2,3...n辆火车进入车站,先对它们进行全排列,把不满足火车出栈序列的出站顺序给删掉,那么什么样的出站序列是不满足条件的?

举个例子可能更好理解,比如3,1,2.如果只有3辆火车进站,出站序列不能是3,1,2,因为如果3出站了,那么栈中剩下1,2.而2在栈顶,此时1不可能出栈,只能2先出站,1再出站

如果有4辆火车,难么4,3,1,2 也是不可能的出站顺序。从这些例子中总结一下,就是在全排序列中,只要小于当前数字的后续数字中存在正序的就是不满足条件的,比如上面例子3,1,2,当前数字是3,小于3的后续数字有1,2它们是正序的则不满足条件。

  本代码中的isReversed(int[] array)方法思路就是,先保存当前值,然后保存小于它的两个数,判断这两个数是正序还是逆序,如果是正序直接return false。注意每次比较前需要重新对临时变量t赋值(它的作用是保存第一个小于当前值得数),否则会出错。火车进站代码只需在上面全排列代码稍作修改就可以了。



import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);String len = scanner.nextLine();String str = scanner.nextLine();int n = Integer.parseInt(len);int[] array = new int[n];String[] strings = str.split(" ");for(int i=0;i<n;i++){array[i] = Integer.parseInt(strings[i]);}scanner.close();Perm(array,0,n-1);}// 求全排列public static void Perm(int[] array,int k,int m){int temp = 0;if(k == m && isReversed(array)){           for(int i=0;i<=m;i++){if(i == m){System.out.print(array[i]);}else{System.out.print(array[i]+" ");}}System.out.println();}else{for(int i=k;i<=m;i++){temp = array[k];array[k] = array[i];array[i] = temp;Perm(array,k+1,m);temp = array[k];array[k] = array[i];array[i] = temp;}}}// 判断数组是否是逆序的,是返回true,否返回falsepublic static boolean isReversed(int[] array){int temp = 0;int t;for(int i=0;i<array.length-1;i++){temp = array[i];t = Integer.MAX_VALUE;for(int j=i+1;j<array.length;j++){if(array[j] < temp){if(array[j] > t){return false;}t = array[j];}}}return true;}}












0 0