BFS(廣度優先搜尋演算法)
一、BFS的介紹
BFS(廣度優先搜尋,也可稱寬度優先搜尋)是連通圖的一種遍歷策略。因為它的基本思想是從一個頂點V0開始,輻射狀地優先遍歷其周圍較廣的區域。
廣度優先搜尋(BFS)類似於二叉樹的層序遍歷演算法,它的基本思想是:首先訪問起始頂點v,然後由v出發,依次訪問v的各個未被訪問過的鄰接頂點w1,w2,w3….wn,然後再依次訪問w1,w2,…,wi的所有未被訪問過的鄰接頂點,再從這些訪問過的頂點出發,再訪問它們所有未被訪問過的鄰接頂點….以此類推,直到途中所有的頂點都被訪問過為止。類似的想法還將應用與Dijkstra單源最短路徑演算法和Prim最小生成樹演算法。
廣度優先搜尋是一種分層的查詢過程,每向前走一步可能訪問一批頂點,不像深度優先搜尋(DFS)那樣有回退的情況,因此它不是一個遞迴的演算法,為了實現逐層的訪問,演算法必須藉助一個輔助佇列並且以非遞迴的形式來實現。
二、BFS搜尋的步驟
1、首先建立一個visit[ ]陣列和一個佇列q,分別用來判斷該位置是否已經訪問過及讓未訪問過的點入隊;
2、初始化visit[ ]陣列,清空q佇列;
3、讓起點start入隊,並使該點的visit置1;
4、while(!q.empty()){......}執行搜尋操作,
a、取出隊頭元素後使隊頭元素出隊,判斷該元素是否為目標到達點;
b、如果是目標點,就返回結果(一般是最短時間、最短路徑);
c、如果不是目標點,就繼續訪問與其相鄰的位置點,將可走的相鄰的位置點入隊,並更新visit[ ]陣列;
三、BFS的應用
BFS演算法一般應用於單源最短路徑的搜尋。
1、尋找非加權圖(或者所有邊權重相同)中任兩點的最短路徑。
2、尋找其中一個連通分支中的所有節點。(擴散性)3、bfs染色法判斷是否為二分圖。
四、核心程式碼
#include<iostream> #include<queue> #include<string.h> #define maxn 105 using namespace std; int n,m; //矩陣的大小 int sx,sy; int vis[maxn][maxn],s[maxn][maxn],t[maxn][maxn]; queue<struct node>Q; int px[]={1,-1,0,0}; //人可走的4個方向 int py[]={0,0,1,-1}; struct node{ int x,y,step; }r,p,q; int BFS() { //清空佇列及初始化vis陣列 while(!Q.empty()) Q.pop(); memset(vis,0,sizeof(vis)); p.x=sx; p.y=sy; p.step=0; vis[p.x][p.y]=1; Q.push(p); while(!Q.empty()) { p=Q.front(); Q.pop(); if(s[p.x][p.y]=='t') return p.step; for(int i=0;i<4;i++) { q=p; q.x+=px[i]; q.y+=py[i]; q.step++; if(q.x<0||q.y<0||q.x>=n||q.y>=m) continue; //訪問未被訪問過的位置,且此時是在火勢蔓延到此前訪問的,再將該位置入隊 if(vis[q.x][q.y]==0&&q.step<t[q.x][q.y]) { vis[q.x][q.y]=1; Q.push(q); } } } return -1; } int main(){ //功能需求程式碼 //........ return 0; }
五、例題
【奇怪的電梯】
-題目描述-
有一天我做了一個夢,夢見了一種很奇怪的電梯。大樓的每一層樓都可以停電梯,而且第i層樓(1<=i<=N)上有一個數字Ki(0<=Ki<=N)。電梯只有四個按鈕:開,關,上,下。上下的層數等於當前樓層上的那個數字。當然,如果不能滿足要求,相應的按鈕就會失靈。例如:3 3 1 2 5代表了Ki(K1=3,K2=3,……),從一樓開始。在一樓,按“上”可以到4樓,按“下”是不起作用的,因為沒有-2樓。那麼,從A樓到B樓至少要按幾次按鈕呢?點選開啟連結
-輸入格式-
共二行。第一行為 3 個用空格隔開的正整數,表示 N,A,B(1≤N≤200, 1≤A,B≤N)N,A,B(1≤N≤200,1≤A,B≤N) 。第二行為 N 個用空格隔開的非負整數,表示 K_i。
-輸出格式-
一行,即最少按鍵次數,若無法到達,則輸出 -1−1 。
-樣例資料-
input
5 1 5
3 3 1 2 5
output
3
-分析-
這道題所要求的是最小值(最少要按的按鈕數)。用BFS找到答案便可以退出(DFS不能保證答案為最優解)。要注意邊界:是否可以繼續往上或向下(沒有0樓,-1樓......)。
-程式碼-
#include<bits/stdc++.h>
using namespace std;
int n,a,b;
int que[222]={};
int k[222]={};
int vis[222]={};//標記是否詢問過
int tmp[222]={};
int head=1,tail=0;//隊首隊尾指標
void bfs(int now)
{
for(;head<=tail;)//當佇列不為空時
{
int top=que[head];//top為隊首的層數
head++;//彈出隊首
if(top==b)
return ;//假如已經到達了目標(b)層,直接退出
if(top+k[top]<=n&&vis[top+k[top]]==0)//假如沒有超出邊界並且沒有訪問過
{
que[++tail]=top+k[top];//入隊
tmp[top+k[top]]=tmp[top]+1;//次數為上一次+1
vis[top+k[top]]=1;//標記
}
if(top-k[top]>=1&&vis[top-k[top]]==0)
{
que[++tail]=top-k[top];
tmp[top-k[top]]=tmp[top]+1;
vis[top-k[top]]=1;
}
}
return ;
}
int main()
{
cin>>n>>a>>b;
for(int i=1;i<=n;i++)
cin>>k[i];
que[++tail]=a;
vis[a]=1;
bfs(a);
if(vis[b]==0)//假如沒有訪問過
cout<<-1<<endl;
else//訪問過
cout<<tmp[b]<<endl;
return 0;
}
【青銅蓮花池】
-題目描述-
為了讓奶牛們娛樂和鍛鍊,農夫約翰建造了一個美麗的池塘。這個長方形的池子被分成了 M 行 N 列個方格(1 ≤ M, N ≤ 30) 。一些格子是堅固得令人驚訝的蓮花,還有一些格子是岩石,其餘的只是美麗、純淨、湛藍的水。貝西正在練習芭蕾舞,她站在一朵蓮花上,想跳到另一朵蓮花上去,她只能從一朵蓮花跳到另一朵蓮花上,既不能跳到水裡,也不能跳到岩石上。貝西的舞步很像象棋中的馬步:每次總是先橫向移動 M1 (1 ≤ M1 ≤ 30)格,再縱向移動 M2 (1 ≤ M2 ≤ 30, M1≠M2)格,或先縱向移動 M1 格,再橫向移動 M2 格。最多時,貝西會有八個移動方向可供選擇。給定池塘的佈局和貝西的跳躍長度,請計算貝西從起點出發,到達目的地的最小步數,我們保證輸入資料中的目的地一定是可達的。點選開啟連結
-輸入格式-
第一行:四個用空格分開的整數:M,N,M1 和 M2第二行到 M + 1 行:第 i + 1 行有 N 個用空格分開的整數,描述了池塘第i 行的狀態:0 為水,1 為蓮花,2 為岩石,3 為貝西所在的起點,4 為貝西想去的終點。
-輸出格式-
一行,從起點到終點的最少步數。
-樣例資料-
input
4 5 1 2
1 0 1 0 1
3 0 2 0 4
0 1 2 0 0
output
2
-分析-
貝西的跳躍方式像馬(馬按“日”字形走(點選開啟連結))。只要列出八個方向,判斷這幾個方向是否可以進行跳躍(跳躍到的位置必須有蓮葉且先前沒有進行過標記),如果可以則入隊,不斷彈出隊首,直至佇列為空或者跳到目標位置,跳出迴圈。
-程式碼-
#include<bits/stdc++.h>
using namespace std;
int m,n,m1,m2;
int a[33][33]={};
int vis[33][33]={};
int ans[33][33]={};
int lx,ly;
struct kk
{
int x;
int y;
} que[3333]={};
int head=1,tail=0;
int bfs()
{
for(;head<=tail;)
{
int x=que[head].x;
int y=que[head].y;//記錄隊首的座標
head++;//彈出隊首
if(x==lx&&y==ly)
return ans[x][y];//返回答案
if(x+m1<=m&&y+m2<=n&&vis[x+m1][y+m2]==0&&a[x+m1][y+m2]==1)
{
que[++tail]=(kk){x+m1,y+m2};
vis[x+m1][y+m2]=1;
ans[x+m1][y+m2]=ans[x][y]+1;
}
if(x-m1>0&&y-m2>0&&vis[x-m1][y-m2]==0&&a[x-m1][y-m2]==1)
{
que[++tail]=(kk){x-m1,y-m2};
vis[x-m1][y-m2]=1;
ans[x-m1][y-m2]=ans[x][y]+1;
}
if(x-m1>0&&y+m2<=n&&vis[x-m1][y+m2]==0&&a[x-m1][y+m2]==1)
{
que[++tail]=(kk){x-m1,y+m2};
vis[x-m1][y+m2]=1;
ans[x-m1][y+m2]=ans[x][y]+1;
}
if(x+m1<=m&&y-m2>0&&vis[x+m1][y-m2]==0&&a[x+m1][y-m2]==1)
{
que[++tail]=(kk){x+m1,y-m2};
vis[x+m1][y-m2]=1;
ans[x+m1][y-m2]=ans[x][y]+1;
}
if(x+m2<=m&&y+m1<=n&&vis[x+m2][y+m1]==0&&a[x+m2][y+m1]==1)
{
que[++tail]=(kk){x+m2,y+m1};
vis[x+m2][y+m1]=1;
ans[x+m2][y+m1]=ans[x][y]+1;
}
if(x-m2>0&&y-m1>0&&vis[x-m2][y-m1]==0&&a[x-m2][y-m1]==1)
{
que[++tail]=(kk){x-m2,y-m1};
vis[x-m2][y-m1]=1;
ans[x-m2][y-m1]=ans[x][y]+1;
}
if(x-m2>0&&y+m1<=n&&vis[x-m2][y+m1]==0&&a[x-m2][y+m1]==1)
{
que[++tail]=(kk){x-m2,y+m1};
vis[x-m2][y+m1]=1;
ans[x-m2][y+m1]=ans[x][y]+1;
}
if(x+m2<=m&&y-m1>0&&vis[x+m2][y-m1]==0&&a[x+m2][y-m1]==1)
{
que[++tail]=(kk){x+m2,y-m1};
vis[x+m2][y-m1]=1;
ans[x+m2][y-m1]=ans[x][y]+1;
}
}//八個方向進行跳躍
}
int main()
{
cin>>m>>n>>m1>>m2;
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
{
cin>>a[i][j];
if(a[i][j]==3)
{
que[++tail]=(kk){i,j};
vis[i][j]=1;
}//如果為初始位置,入隊,並進行標記
if(a[i][j]==4)
{
lx=i;
ly=j;
a[i][j]=1;//為了方便,這樣只需要判斷a[i][j]為1時可以跳躍
} //記錄結束位置
}
cout<<bfs()<<endl;//bfs
return 0;
}