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~