问题简述:输出任意各自然数(可不连续)所有不重复的排列,即全排列,要求所产生的任一数字序列中不允许出现重复的数字。
解决方法: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();
(还原标记,一般为交换回来)//是否还原标记根据题意,如果加上(还原标记)就是 回溯法
}
}
}