廣度優先搜索BFS(Breadth First Search)也稱為寬度優先搜索,它是一種先生成的結點先擴展的策略。
在廣度優先搜索算法中,解答樹上結點的擴展是按它們在樹中的層次進行的。首先生成第一層結點,同時檢查目標結點是否在所生成的結點中,如果不在,則將所有的第一層結點逐一擴展,得到第二層結點,並檢查第二層結點是否包含目標結點,……,對層次為n+1的任一結點進行擴展之前,必須先考慮層次完層次為n的結點的每種可能的狀態。因此,對於同一層結點來說,求解問題的價值是相同的,可以按任意順序來擴展它們。通常采用的原則是先生成的結點先擴展。
為了便於進行搜索,要設置一個表存儲所有的結點。由於在廣度優先搜索算法中,要滿足先生成的結點先擴展的原則,所以存儲結點的表一般采用隊列這種數據結構。
在編寫程序時,可用數組q模擬隊列。front和rear分別表示隊頭指針和隊尾指針,初始時front=rear=0。
元素x入隊操作為 q[rear++]=x;
元素x出隊操作為 x =q[front++];
廣度優先搜索算法的搜索步驟一般是:
(1)從隊列頭取出一個結點,檢查它按照擴展規則是否能夠擴展,如果能則產生一個新結點。
(2)檢查新生成的結點,看它是否已在隊列中存在,如果新結點已經在隊列中出現過,就放棄這個結點,然后回到第(1)步。否則,如果新結點未曾在隊列中出現過,則將它加入到隊列尾。
(3)檢查新結點是否目標結點。如果新結點是目標結點,則搜索成功,程序結束;若新結點不是目標結點,則回到第(1)步,再從隊列頭取出結點進行擴展。
最終可能產生兩種結果:找到目標結點,或擴展完所有結點而沒有找到目標結點。
如果目標結點存在於解答樹的有限層上,廣度優先搜索算法一定能保證找到一條通向它的最佳路徑,因此廣度優先搜索算法特別適用於只需求出最優解的問題。當問題需要給出解的路徑,則要保存每個結點的來源,也就是它是從哪一個節點擴展來的。
對於廣度優先搜索算法來說,問題不同則狀態結點的結構和結點擴展規則是不同的,但搜索的策略是相同的。廣度優先搜索算法的框架一般如下:
void BFS()
{
隊列初始化;
初始結點入隊;
while (隊列非空)
{
隊頭元素出隊,賦給current;
while (current 還可以擴展)
{
由結點current擴展出新結點new;
if (new 重復於已有的結點狀態) continue;
new結點入隊;
if (new結點是目標狀態)
{
置flag= true; break;
}
}
}
}
對於不同的問題,用廣度優先搜索法的算法基本上都是一樣的。但表示問題狀態的結點數據結構、新結點是否為目標結點和是否為重復結點的判斷等方面則有所不同。對具體的問題需要進行具體分析,這些函數要根據具體問題進行編寫。
【例1】黑色方塊
有一個寬為W、高為H的矩形平面,用黑色和紅色兩種顏色的方磚鋪滿。一個小朋友站在一塊黑色方塊上開始移動,規定移動方向有上、下、左、右四種,且只能在黑色方塊上移動(即不能移到紅色方塊上)。編寫一個程序,計算小朋友從起點出發可到達的所有黑色方磚的塊數(包括起點)。
例如,如圖1所示的矩形平面中,“#”表示紅色磚塊,“.”表示黑色磚塊,“@”表示小朋友的起點,則小朋友能走到的黑色方磚有28塊。
(1)編程思路。
采用廣度優先搜索法解決這個問題。
用數組q模擬隊列操作,front為隊頭指針,rear為隊尾指針,初始時front=rear=0。
入隊操作為 q[rear++]=cur;
出隊操作為 cur=q[front++]。
程序中定義方磚的位置坐標(x,y)為Node類型,定義數組int visit[N][N]標記某方磚是否已走過,visit[i][j]=0表示坐標(i,j)處的方磚未走過,visit[i][j]=1表示坐標(i,j)處的方磚已走過。初始時visit數組的所有元素值均為0。
具體算法步驟為:
① 將出發點(startx,starty)入隊列q,且置visit[startx][starty]=1,表示該處的方磚已被處理,以后不再重復搜索。
② 將隊列q的隊頭元素出棧,得到一個當前方磚cur,黑色方磚計數(sum++),沿其上、下、左、右四個方向上搜索未走過的黑色方磚,將找到的黑色方磚的坐標入隊列q。
③ 重復執行②,直至隊列q為空,則求出了所有能走過的黑色方磚數。
(2)源程序。
#include <iostream>
using namespace std;
#define N 21
struct Node
{
int x;
int y;
};
int dx[4]={-1,1,0,0};
int dy[4]={0,0,-1,1};
char map[N][N];
int visit[N][N];
int bfs(int startx, int starty,int w,int h)
{
Node q[N*N],cur,next; // q為隊列
int front,rear; // front為隊頭指針,rear為隊尾指針
int i,x,y,sum;
front=rear=0; // 隊列q初始化
sum=0;
cur.x=startx; cur.y=starty;
visit[startx][starty]=1;
q[rear++]=cur; // 初始結點入隊
while(rear!=front) // 隊列不為空
{
cur=q[front++]; // 隊頭元素出隊
sum++; // 方磚計數
for (i=0;i<4;i++)
{
x=cur.x+dx[i]; y=cur.y+dy[i];
if(x >=0 && x<h && y>=0 && y<w && map[x][y]!='#' && visit[x][y]==0)
{
visit[x][y] = 1;
next.x=x; next.y=y; // 由cur擴展出新結點next
q[rear++]=next; // next結點入隊
}
}
}
return sum;
}
int main()
{
int i,j,pos_x,pos_y,w,h,sum;
while(1)
{
cin>>w>>h;
if (w==0 && h==0) break;
for(i=0;i<h;i++)
{
for(j=0;j<w;j++)
{
cin>>map[i][j];
if (map[i][j]=='@')
{
pos_x = i;
pos_y = j;
}
visit[i][j] = 0;
}
}
sum=bfs(pos_x, pos_y,w,h);
cout<<sum<<endl;
}
return 0;
}