題目描述
輸出自然數1到n所有不重復的排列,即n的全排列,要求所產生的任一數字序列中不允許出現重復的數字。
我們可以模擬出n個盒子和n張卡片,我們需要將n張卡片分別放到n個盒子里,且每個盒子只能放1張卡片,那有多少種方案呢?
我們來模擬一下放卡片。
現在放了第1張卡片,接下來亦是如此。
產生排列"1 2 3"。
經過綜上,已經完成了一種排列。那是不是就結束了呢?顯然不是!因為產生了一種排列后需要立即返回,現在我們要把第3張卡片收回。
取回了第3張卡片后,發現手里仍然只有第3張卡片,沒有別的選擇,於是不得不把2號卡片收回。
現在手里有2張卡片了,分別是2、3號卡片。現在需要把3號卡片放入第2個盒子里,放好后再把2號卡片放到3號盒子里,產生排列"1 3 2"。
接下來按照上面的程序去模擬,會依次生成所有排列"2 1 3"、"2 3 1"、"3 1 2"、"3 2 1"。
這個模擬的過程,就是dfs的基本操作。
現在請出代碼。
考慮2個情況:1是如何往盒子里放卡片,2是放過的卡片就不能放到其他盒子里了,因為一個盒子只能放1個卡片。這里1個for循環和標記判斷就能搞定。
for(i=1;i<=n;i++)//從1到n個盒子產生排列 { if(b[i]==0)//如果卡片在手上,b[i]==0表示第i號卡片在手上 { a[step]=i;//將第i個卡片放入第step個盒子里 b[i]=1;//標記第i號卡片不在手上 } }
這里a數組是表示小盒子,b數組是標記卡片是否在手上。step表示正處在第step個盒子上。
OK,現在已經處理掉第step個盒子了,接下來往深處走,處理第step+1個盒子。如何處理step+1個盒子呢?其實和處理第step個盒子是一樣的。顯然我們需要把剛才處理第step個盒子封裝成函數。
inline void dfs(int step)//處理第step個盒子 { for(i=1;i<=n;i++)//從1到n個盒子產生排列 { if(b[i]==0)//如果卡片在手上,b[i]==0表示第i號卡片在手上 { a[step]=i;//將第i個卡片放入第step個盒子里 b[i]=1;//標記第i號卡片不在手上 } } return; }
好,寫成函數就好辦了,在處理完第step個盒子后,就要處理第step+1個盒子,方法就是dfs(step+1)。
inline void dfs(int step)//處理第step個盒子 { for(i=1;i<=n;i++)//從1到n個盒子產生排列 { if(b[i]==0)//如果卡片在手上,b[i]==0表示第i號卡片在手上 { a[step]=i;//將第i個卡片放入第step個盒子里 b[i]=1;//標記第i號卡片不在手上 dfs(step+1);//通過遞歸實現處理下一個盒子 b[i]=0;//一定要把剛才嘗試的卡片收回,才能進行下一次嘗試 } } return; }
上面b[i]=0十分重要,因為在一次擺放嘗試結束返回時,如果不把剛才的卡片收回,那將無法進行下一次擺放。
還有一個問題,什么時候才能輸出一個滿足要求的序列呢?我們再回到那張模擬圖。
由圖可知,當滿足條件時應該就是卡片全部都在盒子里。當我們要處理第n+1個盒子時,說明前n個盒子都已經放好卡片了。
inline void dfs(int step)//處理第step個盒子 { if(step==n+1)//如果前面n個盒子已經排列好 { for(i=1;i<=n;i++)//輸出 { cout<<a[i]<<' '; } cout<<endl; return;//非常重要!返回之前一步,也就是最近調用一次dfs的地方,否則程序將無止境地調用下去 } for(i=1;i<=n;i++)//從1到n個盒子產生排列 { if(b[i]==0)//如果卡片在手上,b[i]==0表示第i號卡片在手上 { a[step]=i;//將第i個卡片放入第step個盒子里 b[i]=1;//標記第i號卡片不在手上 dfs(step+1);//通過遞歸實現處理下一個盒子 b[i]=0;//一定要把剛才嘗試的卡片收回,才能進行下一次嘗試 } } return; }
完整代碼如下:
#include <stdio.h> #include <iostream> using namespace std; int a[101],b[101],n; void print() { int i; for(i=1;i<=n;i++) { cout<<a[i]<<' '; } cout<<endl; } inline void dfs(int i)//現在是第i層,也可以看成是第i個盒子,把數據放到這個盒子里 { int j; if(i==n+1)//如果到達了第n+1層說明已經搜索完成,輸出 { print();//輸出方案 return;//返回上一層(上一個盒子) } for(j=1;j<=n;j++)//開始放數據 { if(b[j]==0)//這個數可以放(未標記) { a[i]=j;//放這個數 b[j]=1;//標記被放過了 dfs(i+1);//放第i+1個盒子(層) b[j]=0;//返回之前一步,回溯 } } } int main() { ios::sync_with_stdio(false); cin>>n; dfs(1);//開始深搜 return 0; }
通過一個例子我們發現,dfs也就那回事。還是開頭那句話:理解深搜的重要關鍵點是在於解決“現在該怎么做”。至於“接下來該怎么做”和“現在該怎么做”是一樣的(這在騙分中十分重要)。比如上述程序的dfs(step)就是當你在第step個盒子前你應該怎么做。通常應該用循環把每一種可能都試一遍,當這步解決后就處理下一步[dfs(step+1)]。
下面是dfs的模版。
inline void dfs(int step) { 判斷邊界 開始嘗試每一種可能 { ... dfs(step+1);//繼續下一步 } return;//返回 }