昨天,樓主關注的微信公衆號推送了一篇關於DFS的算法文章。第一反應:迷宮搜索問題中的一種,即能否走出迷宮(另一種是搜索迷宮中能走出去的最短路徑,一般用BFS)。當然了,文章本身沒有將迷宮,而是通過一個全排列的題目引入的。
題目:給定整數 ,求出由 中 個數組成的所有全排列。共有多少種全排列不用算,大家肯定都知道,共有 種。
第一反應,看不出有什麼關係,直接暴力求解。
public static void main(String[] args) {
int count = 0;// 記錄排序次數
int[] arr = new int[5]; // 存儲結果
for (arr[1] = 1; arr[1] < 5; arr[1]++) {
for (arr[2] = 1; arr[2] < 5; arr[2]++) {
for (arr[3] = 1; arr[3] < 5; arr[3]++) {
for (arr[4] = 1; arr[4] < 5; arr[4]++) {
int[] result = new int[5];// 判斷是否重複
boolean flag = true;
for (int i = 1; i < 5; i++) {
result[arr[i]] += 1;
if (result[arr[i]] > 1)
flag = false; // 判斷當前數字是否重複出現
}
if (flag) { // 所有的數字出現
System.out.println(arr[1] + " " + arr[2] + " " + arr[3] + " " + arr[4]);
count++;
}
}
}
}
}
System.out.println("total count:" + count);
}
大致思路是:對於給定的整數 ,先將一個排列中的每一位全部設置爲1,然後從最後一位開始遞增。每遞增一次,判斷一次當前是否爲一個排列(即所有數字不能重複)。若是,則直接輸出並計數一次。對應下圖,先從綠色箭頭對應的位置相加並判斷是否爲一個排列,直到 。然後計算藍色箭頭對應的 ,紅色對應的 ,黃色對應的 。
這樣的代碼並不美觀,而且,若 每修改一次 的值,程序結構都會做出改變。文章提到我們可以用遞歸來優化一下。
static int[] arr = new int[5];
static int step_total = 5;
static int count = 0;
public static void main(String[] args) {
full(1); // 1開始
System.out.println("total count:" + count);
}
public static void full(int step) {
if (step >= step_total) { // 出口;當下標超出
int[] result = new int[5];// 判斷是否重複
boolean flag = true;
for (int i = 1; i < step_total; i++) {
result[arr[i]] += 1;
if (result[arr[i]] > 1)
flag = false; // 判斷當前數字是否重複出現
}
if (flag) { // 所有的數字出現
System.out.println(arr[1] + "" + arr[2] + "" + arr[3] + "" + arr[4]);
count++;
}
return;
}
// 重複內容
for (int i = 1; i < step_total; i++) {
arr[step] = i; // 第step下標位置 放i
full(step + 1);
}
}
這段代碼的原理和上一段代碼的原理一樣,只是採用了遞歸的方式來計算。好像和DFS還是沒有什麼關係。接着,文章寫出了採用DFS方式的代碼如下:
static int[] arr = new int[5]; // 存儲結果數組
static int[] result = new int[5];// 判斷是否重複
static int step_total = 5;
static int count = 0;
public static void main(String[] args) {
full(1); // 1開始
System.out.println("total count:" + count);
}
public static void full(int step) {
if (step >= step_total) { // 出口;當下標超出
System.out.println(arr[1] + "" + arr[2] + "" + arr[3] + "" + arr[4]);
count++;
return;
}
// 重複內容
for (int i = 1; i < step_total; i++) {
if (result[i] == 0) { // 如果當前數沒有被全排
result[i] = 1; // 標記當前數已經全排
arr[step] = i; // 第step下標位置 放i
full(step + 1);
result[i] = 0;
// 標記當前數不全排,這一步是因爲當前數已經全排完畢
// (Full()這個方法遞歸已經結束!)
// 此時應該取消之前標記這個數的已經全排
}
}
}
理了一下,大致的過程如下所示:
第一次輸出後,兩數組和 的值如第一行左邊圖所示。然後回調至 ,此時設 ,然後繼續回調至 ,設 ,後循環至 ,設 ,然後繼續遞歸至 ,設 ,然後輸出。從而實現採用遞歸的方式來輸出全排列。三個程序的輸出均相同:
1 2 3 4
1 2 4 3
1 3 2 4
1 3 4 2
1 4 2 3
1 4 3 2
2 1 3 4
2 1 4 3
2 3 1 4
2 3 4 1
2 4 1 3
2 4 3 1
3 1 2 4
3 1 4 2
3 2 1 4
3 2 4 1
3 4 1 2
3 4 2 1
4 1 2 3
4 1 3 2
4 2 1 3
4 2 3 1
4 3 1 2
4 3 2 1
total count:24