生成排列(全排列)的两种写法

问题简述:输出任意各自然数(可不连续)所有不重复的排列,即全排列,要求所产生的任一数字序列中不允许出现重复的数字。

解决方法:1.交换法    2.数组访问法,两种方法都是由dfs回溯完成,也可以使用剪支。

文末将给出DFS的套路模板

一.交换法

  方法是先固定一个元素,然后将固定的数字与其他元素进行交换

见下图

import java.util.Scanner;

public class 生成排列交换法 {
	static Scanner sc = new Scanner(System.in);
	static int ans = 0;
	static int n = sc.nextInt();
	static int pre[] = new int[n];
	public static void main(String[] args) {
		for(int i = 0 ; i < pre.length ; i++) {
			pre[i] = sc.nextInt();
		}
		dfs(0);//从第0层,指最上层为0
                /*
                *此处的dfs的参数为0,是指将最上层当作第0层,也可以改为1,但对应dfs函数中的 
                *if(step == n)就要相应的改动                                       
                */
		System.out.println(ans);
	}
	public static void dfs(int step) {
		if(step == n) {
			ans++;
			print();
		}
		for(int i = step ; i < n; i++) {
                        //此循环体一定要重点理解,弄清原理主要就靠这循环体
			swap(i,step);//对应图中的元素交换
			dfs(step+1);
			swap(step,i);
		}
		
	}
	public static void print() {
		for(int i = 0 ; i < n ; i++) {
			System.out.printf("%5d",pre[i]);
		}
		System.out.println();
	}
	public static void swap(int i, int j) {
		int temp;
		temp = pre[i];
		pre[i] = pre[j];
		pre[j] = temp;
	}
}

 注意在顺序问题上,是属于dfs情形,一直探寻到底,直到最后框里只剩一个元素的时候,再折返,这就是循环体中swap(step,i)的意义,采用回溯。

二.数组访问法

import java.util.Scanner;

public class 全排列_n个不连续数字全排列 {
		static Scanner sc = new Scanner(System.in);
		static int n = sc.nextInt();
		static int ans = 0;
		static int pre[] = new int [n+1];
		static int result[] = new int [n];
		static int vis[] = new int[n];
		public static void main(String[] args) {
			for(int i = 0 ; i < n ; i++) {
				pre[i] = sc.nextInt();
			}
			dfs(0);
			System.out.println(ans);
		}
		private static void dfs(int step) {
			if(step >= n) {
				ans++;
				print();
				return;
			}
			for(int i = 0 ; i < n ; i++) {
				if(vis[i] == 0) {
					vis[i] = 1;
					result[step] = pre[i];
					dfs(step+1);
					vis[i] = 0;
				} 
			}
		}
		private static void print() {
			for(int i = 0 ; i < n ; i++) {
				System.out.printf("%5d",result[i]);
			}
			System.out.println();
		}
}

采用vis数组来记录数组的元素访问情况,数组在初始默认情况下元素都是0(即为未被访问过)。

最好去仔细画一遍dfs的图,看看其原理,是什么时候往回退的,一步步往回退的过程就是回溯,过程中将已经访问过的元素重新赋值为0。因为你赋值为0的一个重要作用,就是在你触底,不能在继续访问新元素的时候,要往回退,往回退的过程中还要有另一条路会用到此元素,重点理解的地方,这个数字只存在与一个数组中,你访问数字,即为访问数组的元素,而且是只有一个数组,所以要一步步的回溯。例如:从1-4的过程中,当你选择step1:1      ->     step2:2    ->     step3:3      ->       step4:4 。此时1234这4个数字都被访问过,vis[i]都为1,如果你要换一种排列,他们都不在访问范围内,所以第一步回溯,将step4中的4 vis[i]重新置为0,然后发现除了4没有其他数字选择,那么再次回溯,将step3的数字的也置为0(不是数字置为0,而是vis[]置为0),此时就有了选择,除了3以外,4也没被访问过,所以第二种方案,为step3为4,然后继续dfs,step4为3.(最后的结果 为 :1243)。

三.关于dfs的方法模板如下,仅限于简单初始的dfs

具体的模板完全可以参照上面两个代码块,其实算比较标准的模板代码了

public static void dfs()//参数用来表示状态  

{  

    if(到达终点状态,也称出口)  

    {  
        ...//主要为打印或者结束处理  
        return;  
    }  
    for(扩展方式)  //循环一层的所有情况
    {  
        if(扩展方式所达到状态合法)  
        {  
            具体操作;//根据题意来添加
            标记;  
            dfs();  
            (还原标记);  //是否还原标记根据题意,如果加上(还原标记)就是 回溯法  
        }  
    }  

}  
public static void dfs()//参数用来表示状态  

{  

    if(到达终点状态,也称出口)  

    {  
        ...//主要为打印或者结束处理  
        return;  
    }  
    for(扩展方式)  //循环一层的所有情况
    {  
        if(扩展方式所达到状态合法)  
        {  
            交换;  
            dfs();  
            (还原标记,一般为交换回来)//是否还原标记根据题意,如果加上(还原标记)就是 回溯法  
        }  
    }  
}  

 

  • 9
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值