1. 程式人生 > >深度搜索入門

深度搜索入門

要去 四種 但是 順序 width 初始 can result printf

深度優先搜索是搜索的手段之一。它從某個狀態開始,不斷地轉移狀態,直到無法轉移,然後回退到前一步的狀態,繼續轉移到其他狀態,如此重復,直到找到最終的解。

做這類題目,抓住兩樣東西:1.總體上遞歸幾次(幾層)?每一次遞歸確定一層上的數。 2.每次遞歸,有幾種選擇的情況。所以dfs()函數,只有兩部分(if、else結構):1.(if部分)若每一層都選擇了,判斷是否符合條件,做出題目要求的操作。2.(else部分)若還有層沒有選擇,就做出選擇,所有選擇的情況列出。

下面是幾個考察dfs的題目:

1.部分和問題(入門題)——南陽1282(dfs)

問題描述:http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=1282 題目要求:在n個數中,選出某些,使得它們之和等於k,可以做到,輸出YES,不可以做到,輸出NO。 分析:有n個數,大體遞歸n(或n+1)次,分別確定n(或n+1)層的數;每次遞歸,有兩種選擇:加這個數、不加這個數。 其狀態轉移過程如下: 技術分享圖片
代碼<c語言>:
 1 //每個數分取和不取兩種情況
 2 #include<stdio.h>
 3 int n,k,arr[21];//設成全局變量,是的dfs函數的參數減少,函數變簡潔
//dfs函數,用來確定第i層上的數
4 bool dfs(int i,int sum) 5 { 6 if(i==n)return sum==k; 7 //兩種選擇 8 if(dfs(i+1,sum)) return true;//不取這個數,sum+arr[i]表示到第i+1層數的和 9 if(dfs(i+1,sum+arr[i])) return true;//取這個數
10 return false; 11 }
//主函數
12 int main() 13 { 14 int i; 15 while(~scanf("%d %d",&n,&k)) 16 { 17 for(i=0; i<n; i++) 18 { 19 scanf("%d",&arr[i]); 20 } 21 22 if(dfs(0,0))//第一層,是0,不是第一個數,所以要n+1層 23 printf("YES\n"); 24 else
25 printf("NO\n"); 26 } 27 return 0; 28 }

2.部分和問題——南陽1058(dfs+1組記錄)

題目描述:http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=1058

題目要求:在題目1的基礎上,輸出由那些數組成。

分析:同題目1,但是不僅要判斷是否可以找出某些數之和正好是k,還要輸出這些數,我們在遞歸的過程中,如果滿足條件,就把數存入數組。本題的dfs函數,只比上一題多一行代碼:result[j++]=arr[i],存數的過程。

註意:若給出的數,有多組都符合條件,此代碼只輸出一組。並且,選擇的次序不同,會導致結果不同。如:

輸入:4 12

4 8 1 7

“先加這個數”:輸出:YES

4 1 7

“後加這個數”:輸出:YES

4 8

但是,兩種寫法,雖然結果不同,都AC了。

代碼<c語言>:

 1 #include<stdio.h>
 2 int n,k,j=0,arr[21],result[21];//arr[]存儲題目給出的數,result[]存儲符合條件的數的下標
//dfs函數
3 bool dfs(int i,int sum){ 4 if(i==n)return sum==k; 5 if(sum>k)return false; 6 //下面自然是i<n的情況,有兩種情況 7 /*if(dfs(i+1,sum+arr[i])) {//位置不同(先加這個數),結果不同,但是都AC了 8 result[j++]=arr[i];//記錄取出的數據 9 return true; 10 }*/ 11 if(dfs(i+1,sum)) 12 return true; 13 if(dfs(i+1,sum+arr[i])) {//位置不同(後加這個數) 14 result[j++]=arr[i];//記錄取出的數據,arr[]下標大的放在前面,輸出的時候註意從後往前輸出 15 return true; 16 } 17 return false; 18 }
//主函數
19 int main(){ 20 int i; 21 while(~scanf("%d %d",&n,&k)){ 22 j=0;//初始化 23 for(i=0;i<n;i++){ 24 scanf("%d",&arr[i]); 25 } 26 27 if(dfs(0,0)){ 28 printf("YES\n"); 29 j=j-1; 30 while(j>=0){//輸出 31 printf("%d ",result[j--]); 32 } 33 printf("\n"); 34 } 35 else 36 printf("NO\n"); 37 } 38 return 0; 39 }

**拓展:那怎樣寫,才能將所有的結果都輸出呢?大部分題目還是要求這樣的。(dfs+多組記錄+還原)

思路:與前面的大同小異,異在:輸入數據的時候,從下標1開始存,參數sum也定義為全局變量;及時還原,並且找到了就輸出,不在主函數裏來輸出。(if、else結構)

輸入:4 12

4 8 1 7

輸出:YES

4 1 7

4 8

代碼<c語言>:

#include<stdio.h>
int n,k,arr[21],result[21],sum=0,flag=0;
void dfs(int i)//(if、else部分)
{
    int j;
    if(i==n+1) //已經全部做好選擇(每一層都選擇好了)
    {
        if(sum==k) //正好與所要求的相等
        {
            ++flag;
            if(flag==1)//改組數據第一次找到符合的,輸出YES,再次找到就不用輸YES啦
                printf("YES\n");
            for(j=1; j<=n; j++)//找到就輸出
            {
                if(result[j])
                    printf("%d ",arr[j]);
            }
            printf("\n");
        }
        else
            return;
    }
    else
    {
        //對下一個數進行選擇
//1.取數
dfs(i+1);//2.不取數 sum+=arr[i]; result[i]=1;//表示用過 dfs(i+1);//對下一個數進行選擇 result[i]=0;//表示沒用過,還原 sum=sum-arr[i]; } return; } int main() { int i; //輸入 while(~scanf("%d %d",&n,&k)) { flag=0;//初始化 sum=0; for(i=1; i<=n; i++) //從數組下標1開始存 { scanf("%d",&arr[i]); } dfs(1);//第一層,第一個數,所以要n層 if(flag==0) printf("NO\n"); } return 0; }

3.組合數——南陽32(dfs+記錄+還原)

題目描述:http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=32

題目要求:找出從自然數1、2、... 、n(0<n<10)中任取r(0<r<=n)個數的所有組合。還要求輸出時,每一個組合中的值從大到小排列,組合之間按逆字典序排列。

分析:先不看輸出要求,要確定r個數,則有r層,遞歸r次;那每一層有哪些選擇呢?以第count(1<count<r)層為例,也就是要確定第count個數,這時第count-1個數已經確定了,第count個數一定在它之後,這樣取出來的數就有一定的順序,那第count個數能取1~n的最後一個數嗎?自然是不可以的,不然剩下的r-count個數放哪呢?所以第count個數的下標要小於n-(r-count),所以每一層的選擇是:“前一個數的下標”<i<n-r+count。每一層的數的下標可以用result[]保存。

考慮輸出要求,逆字典序,所以輸入時,倒著存,以n,n-1,n-2.......2,1存入數組,並且以下標1開始(方便)。

代碼<c語言>:

#include <stdio.h>
int arr[10],result[10],len,num;//result存取出來的數的下標,len——數組的長度,num——要取出數是個數
void dfs(int count){//(if、else結構)
    int i;
    if(count==num+1) //num個數都取出來了
    {
        for(int j=1; j<=num; j++)
        {
        printf("%d",arr[result[j]]);
        }
        printf("\n");
    }
    else{
        for(i=result[count-1]+1; i<len-num+count; i++) //可選擇的範圍,其下標一定會在所取前一個數下標之後,在len-num+count之前(包括)它
        {
        result[count]=i;
        dfs(count+1);
        }
    }
        return;
}
int main()
{
    int n,r,i;
    scanf("%d %d",&n,&r);
    len=n+1;
    num=r;
    for(i=1; i<=n; i++) //數組長度為n+1,註意
    {
        arr[i]=n-i+1;//倒著存
    }
    dfs(1);
    return 0;
}

4.四色問題——code<vs>1116(dfs+還原)

題目描述:http://www.codevs.cn/problem/1116/

題目要求:給地圖上的點用4種顏色塗色,求共有多少種塗法,要求相鄰點的顏色不同。

分析:n個點,n層,n次遞歸;每一個點顏色有4種選擇,但是要去掉相鄰點的顏色。

代碼<c語言>:

#include<stdio.h>
int a[10][10],b[10],c=0,n;//a[10][10]儲存相鄰關系,b[10]保存塗的顏色
void dfs(int x) //(if、else結構)x可以表示第幾層,也可以表示第幾個點
{
    int i,j;
//if
if(x==n+1) //表示所有點都塗完了 { c++; return;//這裏return了,下面就不用寫else。 }
//else
for(i=1; i<=4; i++) //塗四種顏色,四種選擇 1,2,3,4表示四種顏色 { // b[x]=i;//塗色,放在這裏不合適,抹去顏色只有兩種途徑,1.顏色可以這樣塗,便可以dfs下去,等待回溯後,進行b[x]=0操作。2.顏色選錯了,break後等待下一次選色,覆蓋它的顏色。但問題在於若當前已經是1,2,3,4中最後一種顏色了,就覆蓋不了了 //應該先判斷能不能塗,再進行操作 for(j=1; j<=n; j++) { if(a[x][j]&&b[j]==i)//找出與當前 點 相鄰,並且顏色相同的,就說明當前顏色塗錯了 break; } if(j==n+1) //正常跳出,不是break出來的,就說明可以塗這種顏色 { b[x]=i;//塗色 dfs(x+1); b[x]=0;//塗完過後,還原 } } return; } int main() { //輸入 int i,j; //freopen("1.txt","r",stdin); scanf("%d",&n); for(i=1; i<=n; i++) { for(j=1; j<=n; j++) { scanf("%d",&a[i][j]); } } dfs(1); printf("%d",c); return 0; }

5.素數環——南陽488(dfs+記錄+還原)

題目描述:http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=488

題目要求:若形成素數環,從1開始(其實第一個位置上的數已經確定),從大到小輸出

分析:n個數,n層,可以代表素數環的n個位置;每個位置,有1~n個選擇,只是,前面位置用過的,不能再用,這個數與前一位置上的數之和為不為素數,不能用。

代碼<c語言>:

//超時問題 1.將相鄰位之和是否為素數 的判斷放在 取數的階段,而不是先將數放好,再判斷素數問題2.若n為奇數,一定不成素數環 3.判斷素數,用埃氏篩選。
//特殊情況? n=1,1為奇數,但是可以成素數環
#include<stdio.h>
#include<math.h>
int n,a[21],result[21],A[50],flag2;//result數組從下標1開始存數
//埃氏篩選出素數,i為素數,A[i]=0;i不為素數,A[i]=1
void AS()
{
    int q,i;
    q=sqrt(50);
    A[0]=1;
    A[1]=1;//A[i]=1表示i不為素數
    for(i=0; i<=q; i++)
    {
        if(!A[i])//如果i為素數,則i的倍數不為素數
            for(int j=i+i; j<50; j=i+j) //j為i的倍數
            {
                A[j]=1;
            }
    }
}
void dfs(int count)
{
    int i,j,flag=1;//flag用來標記相鄰位是否是素數,flag2用來標記是否形成過素數環
    if(count==n+1) //已經形成一個環
    {
        //判斷是否是素數環
        /*  for(j=1;j<n;j++){
        if(A[result[j]+result[j+1]])//相鄰位置之和不是素數。將素數的判斷放在 每個位置上的數都選好之後,花時間長
        flag=0;
        }*/
        if(A[result[n]+result[1]])//頭尾
            flag=0;
        if(flag) //是素數環
        {
            flag2=1;//表示形成過素數環
            for(j=1; j<=n; j++)
            {
                printf("%d ",result[j]);
            }
            printf("\n");
        }
    }
    else
    {
        for(i=1; i<=n; i++) //每一個位置(每一層)有n種選擇
        {
            if(a[i]&&!A[i+result[count-1]]) //之前沒有用過 並且 相鄰位置數之和為素數 這樣的數才符合要求(將素數判斷放在 選擇數之前,節約時間)
            {
                result[count]=i;
                a[i]=0;//i用過後就用0標記
                dfs(count+1);
                a[i]=i;//還原
            }
        }
    }
    return;
}
int main()
{
    int k=1;
    //輸入
    AS();
    while(~scanf("%d",&n)&&n)
    {
        flag2=0;//初始化
        printf("Case %d:\n",k++);
        if(n==1)
            printf("1\n");
        else if(n%2)
            printf("No Answer\n");
        else
        {
            for(int i=1; i<=n; i++)
            {
                a[i]=i;//將數放入數組
            }
            result[1]=1;//已經確定
            a[1]=0;//用過了
            dfs(2);//確定第一個數
            if(!flag2)//說明一組都沒找著
                printf("No Answer\n");
        }
    }
    return 0;
}

6.Lake Counting——poj488(dfs)

題目描述:http://poj.org/problem?id=2386

要求:輸出水窪的個數

分析:其實就是簡單的深度搜索(8種選擇),搜索不下去的時候,一個水窪就確定了,又開始下一個搜索。‘W‘代表水,只有‘W’能走,走過的地方變為‘.’,避免重復走過,不必還原。

代碼<c語言>:

#include<stdio.h>
int m,n;
char a[105][105];
void dfs(int x,int y)
{
    int nx,ny,dx,dy;
    a[x][y]=.;//將遍歷過的點,由‘W’變為‘.‘,避免再次遍歷
    for(dx=-1; dx<=1; dx++)//八個方向,8種選擇
    {
        for(dy=-1; dy<=1; dy++)
        {
            nx=x+dx;//八個方向遍歷,不能寫成nx,ny
            ny=y+dy;
            if(a[nx][ny]==W&&nx>=0&&nx<m&&ny>=0&&ny<n) //若下一個方向上的點 是水窪,且沒超過邊界,繼續遍歷
            {
                dfs(nx,ny);
            }
        }
    }
    return;
}
int main()
{
    int i,j,c=0;
//輸入
//freopen("1.txt","r",stdin);
    scanf("%d %d",&m,&n);
    getchar();//註意
    for(i=0; i<m; i++)
    {
        for(j=0; j<n; j++)
        {
            scanf("%c",&a[i][j]);
        }
        getchar();//註意
    }
    for(i=0; i<m; i++)
    {
        for(j=0; j<n; j++)
        {
            if(a[i][j]==W) //只遍歷‘W‘
            {
                dfs(i,j);//遍歷結束一次,構成一個水窪
                c++;
            }
        }
    }
    printf("%d",c);
    return 0;
}

深度搜索入門