深度優先在全排列上的應用
問題簡述
N個連續自然數的全排列。
變形:
有N張卡片(1,2,3,4,…,n),得出所有排列的可能。
排列到 N 個盒子中
想法一
在第一時間想到的肯定是枚舉法,用 for 循環來遍歷所有可能
每一個盒子都有N種可能,那麼用N層,最後判斷N個數各不重複即可。
這個時間複雜成都是O(N^9),會佔用大量的時間,而且編寫代碼也變得非常複雜。
題目解讀
- 有N個數字,每個盒子都有N種可能性放入
- 人爲規定:小的優先放入
- 判斷卡片是否已經被放入
想法二
我最開始還是想到了for循環
大概的想法是:
//第一個盒子N種可能
當前位置=1;
for(int i=1;i<=N;i++){
if(沒被放入){
放入當前盒子
當前位置=2;//下一個
//第二個盒子N種可能
for(int i2=1;i2<=N;i2++){
if(沒有放入){
放入當前盒子
當前位置=3;//下一個
........
//第N個盒子
for(int iN=1;iN<=N;iN++){....結束的標誌}
當前位置-1;//返回 N-1層
}
}
當前位置-1; //for2層結束返回for1層
}
}
//程序結束
顯然,這並沒有減少編程的負擔,而且一旦更改N的數值,就得修改代碼…反正我不願意這麼幹哈哈。
分析這段代碼,可以發現每一層for循環內執行的任務是一樣的:找一個沒有放入的最小的數放入當前,然後跳入下一層,排完一組排列後再一層一層跳回。一組排序的結束標誌:當前位置跳到了N+1(N+1本身是不存在的)
想法三
由此想到“遞歸”編程:
- 重複的代碼段處理相同的任務
- 有結束標誌
一層一層返回也可以聯想到遞歸,遞歸函數種的 return 返回到上一次調用本函數的位置,即返回了上一層。
int box[10],book[10],N=5;
//解決第step個盒子前的組合問題
void df(int step){
//結束標誌
if(step == N+1){
//打印盒子數組種的值
for(int i=1;i<=N;i++)
cout<<box[i];
cout<<endl;
return;//返回上一層
}
//遍歷N種可能
for(int i=1;i<=N;i++){
//判斷是否被佔用,判斷是否被標記
if(book[i]!=1){
//放入
box[step] = i;
//置標記
book[i]=1;
//跳入下一層
df(step +1);
//跳回本層
book[i]=0;//拿出卡片,繼續循環
}
}
return;//跳回上一層
}
深度優先搜索
以上思想其實就是深度有限搜索的基本模型
void dfs(int step){
判斷邊界
嘗試每一種可能{
繼續下一步 ;
}
全部完成返回上一步
}
我個人覺得遞歸其實還是挺難的,主要在於抓住傳入的參數,重複的代碼段,和結束條件。