DFS 之 全排列


題目描述
輸出自然數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;//返回 
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM