問題簡述:輸出任意各自然數(可不連續)所有不重複的排列,即全排列,要求所產生的任一數字序列中不允許出現重複的數字。
解決方法: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();
(還原標記,一般爲交換回來)//是否還原標記根據題意,如果加上(還原標記)就是 回溯法
}
}
}