DFS——求圖的連通性問題


 DFS作為一個競賽必學的一個知識點,怎么說我都得寫一下

            遍歷就相當於爆搜,只不過是搜的方式比較規整罷了。

 

深度優先遍歷為了避免重復訪問某個頂點,可以設一個標志數組vis[i],未訪問時值為0,訪問一次后就改為1。

      代碼實現:

//DFS參考代碼
#include <cstdio>
const int maxn=1010;
int a[maxn][maxn];
int vis[maxn];
int n,m;
void dfs(int u){
    printf("%d\n",u);
    vis[u]=1;
    for(int i=1;i<=n;i++)
        if(a[u][i]==1&&vis[i]==0) dfs(i);
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        a[x][y]=a[y][x]=1;            //不同的問題可能不需要雙邊見圖,對於坐標點,就只需要a[x][y]
} dfs(1); return 0; }

 

廣度優先遍歷的實現:  與深度優先遍歷類似避免重復訪問,需要一個狀態數組 vis[n],用來存儲各頂點的訪問狀態。

   如果 vis[i] = 1,則表示頂點 i 已經訪問過;如果 vis[i] = 0,則表示頂點 i 還未訪問過。初始時,各頂點的訪問狀態均為 0。

     代碼實現:

//BFS參考代碼
#include <cstdio>
#include <iostream>
using namespace std;
const int maxn=1010;
int q[maxn];
int a[maxn][maxn];
int vis[maxn];
int n,m;
void bfs(int u){
    int head=0,tail=1;
    q[0]=u;
    vis[u]=1;
    while(head<tail){
        int p=q[head++];
        cout<<p<<endl;
        for(int i=1;i<=n;i++){
            if(a[p][i]==1&&vis[i]==0){
                q[tail++]=i;
                vis[i]=1;
            }
        }
    }
}
int main(){
    cin>>n>>m;
    for(int i=0;i<m;i++){
        int x,y;
        cin>>x>>y;
        a[x][y]=a[y][x]=1;        //同上,不同的問題可能不需要雙邊見圖,對於坐標點,就只需要a[x][y]

} bfs(1);
return 0; }

 

 

 

經典例題:

 

例1油田(zoj1709 poj1562)
題目描述:
GeoSurvComp 地質探測公司負責探測地下油田。每次 GeoSurvComp 公司都是在一塊長方形的土地上來探測油田。在探測時,他們把這塊土地用網格分成若干個小方塊,然后逐個分析每塊土地,用探測設備探測地下是否有油田。方塊土地底下有油田則稱為 pocket,如果兩個pocket相鄰,則認為是同一塊油田,油田可能覆蓋多個 pocket。
你的工作是計算長方形的土地上有多少個不同的油田。
輸入描述:
輸入文件中包含多個測試數據,每個測試數據描述了一個網格。
每個網格數據的第一行為兩個整數:m n,分別表示網格的行和列;如果m = 0,則表示輸入結束,否則 1≤m≤100,1 ≤n≤100。
接下來有m 行數據,每行數據有 n 個字符(不包括行結束符)。每個字符代表一個小方塊,如果為"*",則代表沒有石油,如果為"@",則代表有石油,是一個 pocket。
輸出描述:
對輸入文件中的每個網格,輸出網格中不同的油田數目。如果兩塊不同的 pocket 在水平、垂直、或者對角線方向上相鄰,則被認為屬於同一塊油田。每塊油田所包含的 pocket 數目不會超過 100。

 

思路:DFS的板子題,我們從第一個點開始,尋找它周圍與它相連通的點然后打上標記,一次dfs結束就代表它已經找完與它相連通的油田,接下來的每一次dfs都是從未標記的點(也就是未找過的點,也就是未連通的點)開始遍歷,如果所有的點都找完一遍了,就意味着我們遍歷完一遍了,然后看看我們dfs了多少遍(代碼中有cnt來記錄的),也就是有多少連通塊;

 

代碼實現&講解:

 

#include<iostream>
#include<cstdio>
#include<cstring> 
using namespace std;
const int MAXN=110;
int a[MAXN][MAXN];
   const  int  dx[8]={-1,0,1,-1,1,-1,0,1};    //我們把它要走的八個方向的坐標都寫出來 
  const  int  dy[8]={1,1,1,0,0,-1,-1,-1}; 
int n,m,cnt;
void dfs(int x,int y ){
    a[x][y]=0;         //找完這個點之后就打上標記,表明已找完這個點 
    for(int i=0;i<8;i++){
             int xx=x+dx[i];           
             int yy=y+dy[i];
        if(xx>=1&&xx<=n&&yy>=1&&yy<=m&&a[xx][yy]==1){   //一定要注意邊界條件!!! 
            dfs(xx,yy);
        }
    } 
}

int main(){
         while(cin>>n>>m&&(n>0)){   // 這種輸入是因為有多組停止輸入輸出,且個數未知,當讀到0停止 所以n>0 
             char c;
             memset (a,0,sizeof(a));
             for(int i=1;i<=n;i++){
                 for(int j=1;j<=m;j++){
                     cin>>c;
                     if(c=='@')a[i][j]=1;  // 如果為'@'說明有油田 
                     else a[i][j]=0;
                 }
             }
            int  cnt=0;
             for(int i=1;i<=n;i++){
                 for(int j=1;j<=m;j++){
                     if(a[i][j]==1){
                         dfs(i,j);   //每次搜完之后就表明這它已經找完所有和它聯通的點了 
                         cnt++;      // 這就找完了一個油田塊,油田塊+1,然后就接着找其他與它不相鄰的未標記的點 
                     } 
                 }
             }
             cout<<cnt<<endl;   //輸出有多少油田塊 
         }

return 0;
}

 

 

 

 

例2紅與黑(zoj2165 poj1979)


題目描述:
有一個長方形的房間,房間里的地面上布滿了正方形的瓷磚,瓷磚要么是紅色,要么是黑色。一男子站在其中一塊黑色的瓷磚上。男子可以向他四周的瓷磚上移動,但不能移動到紅色的瓷磚上,只能在黑色的瓷磚上移動。
本題的目的就是要編寫程序,計算他在這個房間里可以到達的黑色瓷磚的數量。
輸入描述:
輸入文件中包含多個測試數據。
每個測試數據的第 1 行為兩個整數 W 和 H,分別表示長方形房間里 x 方向和 y 方向上瓷磚的數目。W 和 H 的值不超過20。
接下來有 H 行,每行有 W 個字符,每個字符代表了瓷磚的顏色,這些字符的取值及含義為:
1) '.' - 黑色的瓷磚;
2) '#' - 紅色的瓷磚;
3) '@' - 表示該位置為黑色瓷磚,且一名男子站在上面,注意每個測試數據中只有一個'@'符號。
輸入文件中最后一行為兩個 0,代表輸入文件結束。
輸出描述:
對輸入文件中每個測試數據,輸出占一行,為該男子從初始位置出發可以到達的黑色瓷磚的數目(包括他初始時所處的黑色瓷磚)。

 

思路:這個題與上一個題的不同之處在於:上一個題求的是有多少連通塊,而這個題求的是一個連通塊有多大,我們只需要在最后遍歷一遍所有的點,記錄我們到達的點

 

 

 代碼實現&講解:

 

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN=1010;
int w,h,cnt;
int a[MAXN][MAXN];
const int dx[4]={0,0,-1,1};
const int dy[4]={1,-1,0,0};    //預處理出可走的四個方向 
void dfs(int x,int y){
    a[x][y]=5;       // 我們從我們可以走的點中找我們所走過的點,然后打上標記 
   for(int i=0;i<4;i++){
         int xx=x+dx[i];
         int yy=y+dy[i];
         if(a[xx][yy]==1&&xx>=1&&xx<=h&&yy>=1&&yy<=w){
             dfs(xx,yy);
         }
    }
}
int  main() {
    int x,y;
    memset(a,1,sizeof(a));
        while(cin>>w>>h&&(w>0)){     //h為行,w為列 
            for(int i=1;i<=h;i++){
                for(int j=1;j<=w;j++){
                char c;
                cin>>c;
                if(c=='.')a[i][j]=1;  //這是我們可以走的點 
                if(c=='#')a[i][j]=0;   //這是我們不能走的點 
                if(c=='@'){     //這是我們的起點 
                    x=i;       //標記號我們的起點,dfs就從這個點開始 
                    y=j;
                }
            }
        }
        dfs(x,y);
        cnt=0;
        for(int i=1;i<=h;i++){
            for(int j=1;j<=w;j++){
                if(a[i][j]==5)cnt++;   //數我們走過的點
            }
        }
        cout<<cnt<<endl;       //輸出結果 
    }  
    return 0;
}

 

 

 

 

 

End~

 


免責聲明!

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



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