1. 程式人生 > >簡單談談實現遞迴暴力列舉

簡單談談實現遞迴暴力列舉

簡單談一談如何用遞迴實現暴力列舉。
下面先看到一個例子。
袋子裡有2紅3綠5黃球,隨機從中摸出8個,列印顯示所有組合。
暴力列舉,其實就是實現一顆搜尋樹。
那很顯然這顆搜尋樹的層數是8層,因為只要摸8個就行了。每個節點拓展出去的兒子節點很顯然是10個,因為每次都有10種選擇。
那很顯然。8層是遞迴出口。10個是每層要列舉的分支。所以可以寫出如下程式碼。

char s[]="gggrryyyyy";//存放顏色資料
char ans[10];//存放當前方案
void dfs(int d)
{
    if(d==8)
    {
        for(int i=0;i<d;i++) printf
("%c",ans[i]); printf("\n"); return ; } for(int i=0;i<10;i++) { ans[d]=s[i]; dfs(d+1); } }

但是這並不符合題目的要求。因為這樣寫,就有可能同個字元被取了多次,而很顯然每個字元只能被取一次。好的,可以修改程式碼,得到如下程式碼。

int vis[15];
char s[]="gggrryyyyy";//存放顏色資料
char ans[10];//存放當前方案
void dfs(int d)
{
    if(d==8
) { for(int i=0;i<d;i++) printf("%c",ans[i]); printf("\n"); return ; } for(int i=0;i<10;i++) { if(vis[i]==0) { vis[i]=1; ans[d]=s[i]; dfs(d+1); vis[i]=0; } } }

這樣,加個標記資料,就可以保證在某個方案裡,一個字元只能被取一次。但是,發現,這個方法還是不行,因為一個節點發射出去的兒子節點,最多隻能是3個(“r”,”g”,”y”),而不能是(“r”,”r”,”g”,”g”“y”…),否則同種方案會被計算多次。那也就是說,在每一層搜尋的時候,搜過的字元就不能再搜了。由於字元陣列是有序的,所以用如下程式碼就可以實現上述功能。

int vis[15];
char s[]="gggrryyyyy";//存放顏色資料
char ans[10];//存放當前方案
void dfs(int d)
{
    if(d==8)
    {
        for(int i=0;i<d;i++) printf("%c",ans[i]);
        printf("\n");
        return ;
    }
    int f=-1;
    for(int i=0;i<10;i++)
    {
        if(vis[i]==0)
        {
            if(f==-1|| f!=s[i])
            {
                f=s[i];
                vis[i]=1;
                ans[d]=s[i];
                dfs(d+1);
                vis[i]=0;
            }
        }
    }
}

好了,現在已經快實現了,就差最後一步了。現在有個問題就是。可能會搜出”gggrryyy”,”gggryyyr”,那由於取得是組合,所以這兩種也應該算同種方案。那如何解決這個問題呢?其實很簡單,只要保證有序地取,就不會重複,因為不同排列的同種組合排好序是一樣的,比如這次搜尋,把s第i個兒子給了ans[d],那麼下一次搜尋,列舉兒子分支的時候就從i+1開始。這樣搜出來的絕對是有序的,就不會出現無序的情況。
程式碼如下:

char s[]="gggrryyyyy";
int vis[15];
char ans[10];
void dfs(int d,int last)
{
    if(d==8)
    {
        for(int i=0;i<8;i++) cout<<ans[i];cout<<endl;
        return ;
    }
    int f=-1;
    for(int i=last;i<10;i++)
    {
        if(vis[i]==0)
        {
            if(f==-1|| f!=s[i])
            {
                f=s[i];
                vis[i]=1;
                ans[d]=s[i];
                dfs(d+1,i+1);
                vis[i]=0;
            }
        }
    }
}

主函式裡直接dfs(0,0);就可以了。
這樣,這個問題就解決了。
下面再看到一個問題。
輸入n(1-10之間數字),將數字分解顯示,如6可以顯示為6,5+1,4+2,4+1+1…..
同樣,在寫程式碼之前,腦袋裡要有一顆搜尋樹。
但是,這裡遞迴層數就不那麼明確了。那可以用一個狀態sum來確定遞迴出口。可以寫出程式碼。

void dfs(int sum)
{
    if(sum==0)
    {
        輸出一組解
        return ;
    }
   for(int i=sum;i>0;i--)
   {
        取一個加數為i
        dfs(sum-i);
   }
}

很容易發現,怎麼輸出解啊。我怎麼知道加數有多少個呀?
簡單,再加個形參記錄層數就可以了,剛好第d層可以放第d個加數。
可以寫出程式碼。

void dfs(int d,int sum)
{
    if(sum==0)
    {
        for(int i=0;i<d-1;i++) cout<<ans[i]<<"+";cout<<ans[d-1]<<endl;
        return ;
    }
   for(int i=sum;i>0;i--)
   {
        ans[d]=i;
        dfs(d+1,sum-i);
   }
}

緊接著,你會發現你輸出了”4+2”,又輸出了”2+4”
這其實跟上面的不同排列相同組合是同一種情況,同樣可以用上面的思路解決,只要讓後面的數比前一個取的數小於等於就可以了。可以寫出程式碼。

void dfs(int d,int sum,int pre)
{
    if(sum==0)
    {
        for(int i=0;i<d-1;i++) cout<<ans[i]<<"+";cout<<ans[d-1]<<endl;
        return ;
    }
   for(int i=sum;i>0;i--)
   {
        ans[d]=i;
        if(i<=pre) dfs(d+1,sum-i,i);
   }
}

完美解決問題。
那現在這個問題會解決了嗎?
用遞迴實現,輸出用1分、2分和5分的硬幣湊成1元,一共有多少種方法。
不確定層數,又是組合問題,所以可以先定個sum的狀態,然後下一次取一定要大於等於上一次所取的,避免方案重複。

最後附上這三道題的完整程式碼。
摸球

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#define LL long long
using namespace std;
char s[]="gggrryyyyy";
int vis[15];
char ans[10];
void dfs(int d,int last)
{
    if(d==8)
    {
        for(int i=0;i<8;i++) cout<<ans[i];cout<<endl;
        return ;
    }
    int f=-1;
    for(int i=last;i<10;i++)
    {
        if(vis[i]==0)
        {
            if(f==-1|| f!=s[i])
            {
                f=s[i];
                vis[i]=1;
                ans[d]=s[i];
                dfs(d+1,i+1);
                vis[i]=0;
            }
        }
    }
}
using namespace std;
int main()
{
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    dfs(0,0);
    return 0;
}

n的所有和

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#define LL long long
using namespace std;
int ans[10];
void dfs(int d,int sum,int pre)
{
    if(sum==0)
    {
        for(int i=0;i<d-1;i++) cout<<ans[i]<<"+";cout<<ans[d-1]<<endl;
        return ;
    }
   for(int i=sum;i>0;i--)
   {
        ans[d]=i;
        if(i<=pre) dfs(d+1,sum-i,i);
   }
}
using namespace std;
int main()
{
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
   int n;
   while(cin>>n)
    dfs(0,n,n);
    return 0;
}

湊硬幣

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#define LL long long

using namespace std;
int ans=0;
int a[105]={5,2,1};
void dfs(int cur,int last)
{
    if(cur==0)
    {
        ans++;
        return ;
    }
    for(int i=last;i<3;i++)
        if(cur>=a[i]) dfs(cur-a[i],i);
}
int main()
{
    //freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    int n;
    for(int i=100;i<=100;i++)
    {
        ans=0;
        dfs(i,0);
        printf("%d\n",ans);
    }
    return 0;
}