1. 程式人生 > >全排列DFS思路詳解

全排列DFS思路詳解

首先考慮一道奧數題目:

問題一:

□□□ + □□□ = □□□,要將數字1~9分別填入9個□中,使得等式成立。例如173+286 = 459。請輸出所有合理的組合的個數。

我們或許可以列舉每一位上所有的數,然後判斷每一位上的數需要互不相等且滿足等式即可,但是用程式碼寫出來需要宣告9個變數且判斷。

那麼我們把這個問題考慮為一個求這個9個數的全排列問題,即可得到更優雅的解答方式。

問題二:

輸入一個數,輸出1~n的全排列。

例項:現在我們考慮有1、2、3的3張撲克牌和編號為1、2、3的3個盒子,需要將這3張撲克牌放到3個盒子裡,求其所有可能性。

解析:

  1. 首先我們考慮1號盒子,我們約定每到一個盒子面前都按數字遞增的順序擺放撲克牌。
    於是把1號撲克牌放到1號盒子中。

      2.接著考慮2號盒子,現在我們手裡剩下2號和3號撲克牌,於是我們可以把2號撲克牌放入2號盒子中。於是在3號盒子只剩一種可能性,我們繼           續把3號撲克放入3號盒子。此時產生了一種排列——{1,2,3}。

      3.接著我們收回3號盒子中的3號撲克牌,嘗試一種新的可能,此時發現別無他選。於是選擇回到2號盒子收回2號撲克。

      4.在2號盒子中我們放入3號撲克,於是自然而然的在3號盒子中只能放入2號撲克。此時產生另一種排列——{1,3,2};

      5.重複以上步驟就能得到數字{123}的全排列。

1、現在我們用C語言程式碼描述往每個小盒子中放入所有可能撲克牌的步驟:

for(int i = 1; i <= n; i++){ a[step] = i; //將i號撲克牌放入第step個盒子中 }

2、a是一個裝入了所有小盒子的陣列,變數step表示當前正處於第step號小盒子。i則表示撲克牌的序號。現在我們需要考慮另外一個問題,則如果一張撲克牌已經被放入別的盒子中,則不能再被放入當前盒子。

因此需要一個book陣列標記哪些牌已經被使用。此時我們完善上述程式碼。

for(int i = 1; i <= n; i++){ if(book[i] == 0){ a[step] = i; //將i號撲克牌放入第step個盒子中 book[i] = 1; // 置1表示第i號撲克牌不在手中 } }

現在對於step號盒子已經處理完,那麼我們要考慮step+1號盒子。第step+1個的盒子的處理方式與第step個盒子的處理方式完全一樣。因此,我們可以對上述操作做一個封裝。

void dfs(int step)

//step表示當前要處理的盒子

 for(int i = 1; i <= n; i++)

if(book[i] == 0)

{ a[step] = i; //將i號撲克牌放入第step個盒子中

 book[i] = 1; // 置1表示第i號撲克牌不在手中 

} } }

於是我們重新回想文章開頭闡述的放置撲克牌的思路:

我們在當前盒子放置完第i個撲克牌之後,便立即處理下一個盒子。於是:

void dfs(int step)

//step表示當前要處理的盒子 

for(int i = 1; i <= n; i++)

if(book[i] == 0)

a[step] = i; //將i號撲克牌放入第step個盒子中 

book[i] = 1; // 置1表示第i號撲克牌不在手中

 dfs(step+1); //遞迴呼叫

 book[i] = 0; // 非常重要,收回該盒子中的撲克牌才能進行下一次嘗試。

 } } }

需要注意到的是,我們需要收回每一次嘗試的撲克牌i,才能進行下一次嘗試。

現在需要考慮最後一個問題,那就是什麼時候得到一個滿足要求的排列,也就是考慮終止條件。這裡很容易得到,當我們處理完成第n個盒子的時候,就已經得到一個符合要求的排列了。加上終止條件的程式碼如下:

void dfs(int step){

 //step表示當前要處理的盒子 

if(step == n+1)

//輸出排列 

for(i = 1; i <= n; i++) printf("%d", a[i]);

 printf("\n"); return; } 

for(int i = 1; i <= n; i++)

if(book[i] == 0)

{ a[step] = i; 

//將i號撲克牌放入第step個盒子中 

book[i] = 1; 

// 置1表示第i號撲克牌不在手中 

dfs(step+1); //遞迴呼叫

 book[i] = 0; // 非常重要,收回該盒子中的撲克牌才能進行下一次嘗試。 } } }

現在深度優先搜尋(DFS)的基本模型展現在我們眼前。

其核心在於,在當前步驟要把每一種可能性都嘗試一遍(使用for迴圈),解決完當前步驟後進入下一步。而下一步的解決方式完全等同於當前步驟的解決方法。於是可以總結出DFS的基本模型:

void dfs(int step){ 

*判斷結束邊界* 嘗試每一種可能 

for(i = 1; i <= n; i++)

嘗試下一步 dfs(step + 1);

 } return; }