深度優先搜索入門

深度優先搜索


1.判斷點是否能到終點

int main()
    {
        將所有點標記爲新點;
        起點 = x;
        終點 = y;
        cout << dfs(起點);    
    }

    bool dfs(V)
    {
        //邊界條件
        if(v是終點)
            return true;
        if(v是舊點)
            return false;

        將v標記爲舊點 表示要訪問
        for(跟v相鄰的所有的點u)
        {
            //返回true表示u能到終點 所以v也可以到終點
            if(dfs(u) == true)  
                return true;          
        }

        //一直到循環了所有的相鄰的點 不能到達終點 所以v也不能到
        return false;           
    }


2.記錄路徑

//要記錄路徑,可以開一個數組,路徑最長就是點的個數。

    Node path[MAX_LEN];    
    int depth;            //表示深度,也就是路徑長度,初始爲0        
    int main()
    {
        將所有點都標記爲新點
        depth = 0;

        if(dfs(起點) == true)
        {
            //從0到depth,path[i]就是路徑上第i步的點
            for(int i = 0;i <= depth; i++)
            {
                cout<<path[i]<<endl;
            }
        }
    }

    bool dfs(v)
    {
        //邊界條件
        if(v爲終點)
        {
            path[depth] = v;
            return true;
        }   

        if(v是舊點)
            return false;

        將v標記爲已經訪問過
        path[depth] = v;      //嘗試把v放入到path
        depth++;              //深度應該加1

        for(與v相鄰的所有點u)
        {
            if(dfs(u) == true)
                return true;  
        }

        //如果u到不了終點 那麼說明路徑上v不合適
        depth--;                  //直接將depth--即可,下一次更新path數組時直接覆蓋

        return false;
    }


3.遍歷所有的點(不連通也可以)

    int main()
    {
        將所有點標記爲新點
        while(圖中能找到v是新點)
        {
            dfs(v);
        }
    }

    void dfs(v)
    {
        if(v是舊點)
            return ;

        將v標記爲舊點
        for(v相鄰的所有的點u)
        {
            dfs(u);
        }
    }


例題練習

題目1:城堡問題 OpenJ_Bailian - 2815

描述:

  1   2   3   4   5   6   7  
   #############################
 1 #   |   #   |   #   |   |   #
   #####---#####---#---#####---#
 2 #   #   |   #   #   #   #   #
   #---#####---#####---#####---#
 3 #   |   |   #   #   #   #   #
   #---#########---#####---#---#
 4 #   #   |   |   |   |   #   #
   #############################
           (圖 1)

   #  = Wall   
   |  = No wall
   -  = No wall

圖1是一個城堡的地形圖。請你編寫一個程序,計算城堡一共有多少房間,最大的房間有多大。城堡被分>割成mn(m≤50,n≤50)個方塊,每個方塊可以有0~4面牆。

輸入:
程序從標準輸入設備讀入數據。第一行是兩個整數,分別是南北向、東西向的方塊數。在接下來的輸入行裏,每個方塊用一個數字(0≤p≤50)描述。用一個數字表示方塊周圍的牆,1表示西牆,2表示北牆,4表示東牆,8表示南牆。每個方塊用代表其周圍牆的數字之和表示。城堡的內牆被計算兩次,方塊(1,1)的南牆同時也是方塊(2,1)的北牆。輸入的數據保證城堡至少有兩個房間。

輸出:
城堡的房間數、城堡中最大房間所包括的方塊數。結果顯示在標準輸出設備上。

樣例輸入:

4 
7 
11 6 11 6 3 10 6 
7 9 6 13 5 15 5 
1 10 12 7 13 7 5 
13 11 10 8 10 12 13 

樣例輸出:

5
9

思路分析:

  • 首先由題目可以知道,抽象爲模型,一個點可以到另一個點那麼這兩個點之間就有一條邊,那麼一個房間就是一個連通子圖;而這道題求得就是最大連通子圖的個數,以及其中點最多的最大連通子圖的頂點個數。

  • 其次,題目中說用一個數字表示方塊周圍的牆,1表示西牆,2表示北牆,4表示東牆,8表示南牆。每個方塊用代表其周圍牆的數字之和表示。我們知道1,2,4,8的二進制表示分別是0001,0010,0100,1000.也就是說這些數字的和(有哪些牆的組合)有一個規律:相應位置上的二進制爲1,那麼就表示有對應的方向的牆。比如13這個數字的二進制是1101,那麼就表示有西牆、東牆和南牆。那麼我們用 & 就可以看是否有對應的牆。

  • 知道哪些方向有牆後,就能遍歷整個圖,那麼我們用染色的思想,一個聯通子圖就染成一個顏色(用數字表示),最後就可以通過看有幾種顏色,每種顏色有多少種來得到答案了。

AC代碼

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;

    int x,y;               //x,y表示南北向,東西向的方塊數
    int map[55][55];       //用map存放城堡的地圖
    int color[55][55];     //用color數組來表示每個點是否訪問過以及分組
    int area,maxArea = 0;  //表示當前子圖的面積以及最大的面積 
    int num = 0;           //房間數 

    void dfs(int i,int j)
    {
        //若已經訪問過
        if(color[i][j] != 0)
            return;  

        area++;                //該房間可走且未走過,所以面積++ 
        color[i][j] = num;     //讓改點的color爲num房間號,染色

        //然後訪問與i,j相鄰的所有可以訪問點
        if((map[i][j] & 1) == 0) dfs(i,j-1);      //西邊無牆
        if((map[i][j] & 2) == 0) dfs(i-1,j);      //北邊無牆
        if((map[i][j] & 4) == 0) dfs(i,j+1);      //東邊無牆
        if((map[i][j] & 8) == 0) dfs(i+1,j);      //南邊無牆
    }

    int main()
    { 
        scanf("%d %d",&x,&y);
        for(int i = 0;i < x; i++)
            for(int j = 0;j < y; j++)
                scanf("%d",&map[i][j]);

        memset(color,0,sizeof(color));      //標記所有點爲新點
        for(int i = 0;i < x; i++)
        {
            for(int j = 0;j < y; j++)
            {
                if(color[i][j] == 0)
                { 
                    /*當該點未被訪問過,那麼說明一定是一個新的聯通子圖,
                    房間數++;*/
                    num ++;
                    area = 0;  //當前房間面積設置爲0      
                    dfs(i,j); 
                    if(maxArea<area) 
                        maxArea = area;
                }
            }
        }

        //遍歷完圖,房間數和最大面積也就得出
        printf("%d\n",num);
        printf("%d\n",maxArea); 

        return 0;
    }


例題練習

題目2:踩方格 OpenJ_Bailian - 4103

描述:

有一個方格矩陣,矩陣邊界在無窮遠處。我們做如下假設:
a. 每走一步時,只能從當前方格移動一格,走到某個相鄰的方格上;
b. 走過的格子立即塌陷無法再走第二次;
c. 只能向北、東、西三個方向走;
請問:如果允許在方格矩陣上走n步,共有多少種不同的方案。2種走法只要有一步不一樣,即被認爲是不同的方案。

輸入:

允許在方格上行走的步數n(n <= 20)

輸出:
計算出的方案數量

樣例輸入:

2

樣例輸出:

7  

思路分析:

  • 首先根據題目,我們可以得出是要我們求出第i,j個位置走n步有多少種走法,其中只能向三個方向走

  • 然後其實這個題也是用到了深度優先搜索,首先我們的起始狀態就是(X0,Y0,N),表示我們在第X0行第X0列上,還要走n步的不同走法。我們的結束狀態是(Xi,Yi,0),也就是到了某一個位置,還要走0步。我們要求得就是從初始狀態到結束狀態總共有多少種方法,其中每一個狀態(Xi,Yi,Ni)就代表一個點。

  • 而(Xi, Yi, Ni) = (Xi + 1, Yi, n - 1) + (Xi, Yi - 1, n - 1) + (Xi, Yi + 1, n - 1); 即從一個狀態可以到其他三種狀態,而該狀態的值就是其他三種狀態的值的和。

AC代碼


    #include<iostream>
    #include<cstdio>
    using namespace std;

    int num = 0;                //方案數

    //確定map的大小 因爲最多一個方向連走n步(n<=20) 所以上下25,左右50(從中間開始) 
    int map[25][50]={0};        
    int N;                      //要走多少步 

    int dfs(int i,int j,int n) 
    {
        //邊界條件 當要走的是0步時,那就是不走這一種方案 
        if(n == 0)
            return 1;   

        map[i][j] = 1;          //表示現在要訪問這一個點

        /*接下來是狀態的轉移  
        之前在遞歸函數開頭判斷該點是否可走 現在是可走再遞歸*/
        int x = 0;
        if(map[i+1][j] == 0) x += dfs(i + 1, j, n - 1);
        if(map[i][j+1] == 0) x += dfs(i, j + 1, n - 1);
        if(map[i][j-1] == 0) x += dfs(i, j - 1, n - 1);

        /*把map[i][j]重新置爲0,因爲表示不要這一步不要訪問這個點了 
        要退回去 讓這一步變成訪問其他的點*/ 
        map[i][j] = 0; 
        return x; 
    }

    int main()
    {
        scanf("%d",&N);
        //表示從第0行,第25列走n步的總共走法
        printf("%d",dfs(0,25,N));         
        return 0;
    }


拓展

深搜最短路徑+剪枝POJ1724:RODES

深搜+剪枝POJ1190:生日蛋糕

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章