層層遞進——寬度優先搜索(BFS)
問題引入
我們接著上次“解救小哈”的問題繼續探索,不過這次是用寬度優先搜索(BFS)。
註:問題來源可以點擊這裏 http://www.cnblogs.com/OctoptusLian/p/7429645.html
最開始小哼在入口(1,1)處,一步之內可以到達的點有(1,2)和(2,1)。
但是小哈並不在這兩個點上,那小哼只能通過(1,2)和(2,1)這兩點繼續往下走。
比如現在小哼走到了(1,2)這個點,之後他又能夠到達哪些新的點呢?有(2,2)。再看看通過(2,1)又可以到達哪些點呢?可以到達(2,2)和(3,1)。
此時你會發現(2,2)這個點既可以從(1,2)到達,也可以從(2,1)到達,並且都只使用了兩步。
註:為了防止一個點多次被走到,這裏需要一個數組來記錄一個點是否已經被走到過。
此時小哼2步可以走到的點就全部走到了,有(2,2)和(3,1),可是小哈並不在這兩個點上。看來沒有別的辦法,還得繼續往下嘗試,看看通過(2,2)和(3,1)這兩個點還能到達哪些新的沒有走到過的點。
通過(2,2)這個點我們可以到達(2,3)和(3,2),通過(3,1)可以到達(3,2)和(4,1)。
現在三步可以到達的點有(2,3)、(3,2)和(4,1),依舊沒有到達小哈的所在點,所以我們需要繼續重復剛才的做法,直到找到小哈所在點為止。
解決步驟
回顧一下剛才的算法,可以用一個隊列來模擬這個過程。在這裏我們用一個結構體來實現隊列
struct note{ int x; //橫坐標 int y; //縱坐標 int s; //步數 }; struct note que[2501]; //因為地圖大小不超過50*50,因此隊列擴展不會超過2500個 int head,tail; int a[51][51] = {0}; //用來存儲地圖 int book[51][51] = {0}; //數組book的作用是記錄哪些點已經在隊列中了,防止一個點被重復擴展,並全部初始化為0 /*最開始的時候需要進行隊列初始化,即將隊列設置為空*/ head = 1; tail = 1; //第一步將(1,1)加入隊列,並標記(1,1)已經走過。que[tail].x = 1; que[tail].y = 1; que[tail].s = 0; tail++; book[1][1] = 1;
然後從(1,1)開始,先嘗試往右走到達了(1,2)。
tx = que[head].x; ty = que[head].y+1;
需要判斷(1,2)是否越界。
if(tx < 1 || tx > n || ty < 1 || ty > m) continue;
再判斷(1,2)是否為障礙物或者已經在路徑中。
if(a[tx][ty] == 0 && book[tx][ty] == 0) { }
如果滿足上面的條件,則將(1,2)入隊,並標記該點已經走過。
book[tx][ty] = 1; //註意bfs每個點通常情況下只入隊一次,和深搜不同,不需要將book數組還原 //插入新的點到隊列中 que[tail].x = tx; que[tail].y = ty; que[tail].s = que[head].s+1; //步數是父親的步數+1 tail++;
接下來還要繼續嘗試往其他方向走。
在這裏我們規定一個順序,即按照順時針的方向來嘗試(右→下→左→上)。
我們發現從(1,1)還是可以到達(2,1),因此也需要將(2,1)也加入隊列,代碼實現與剛才對(1,2)的操作是一樣的。
對(1,1)擴展完畢後,此時我們將(1,1)出隊(因為擴展完畢,已經沒用了)。
head++;
接下來我們需要在剛才新擴展出的(1,2)和(2,1)這兩個點上繼續向下探索(因為還沒有到達小哈所在的位置,所以還需要繼續)。
(1,1)出隊之後,現在隊列的head正好指向了(1,2)這個點,現在我們需要通過這個點繼續擴展,通過(1,2)可以到達(2,2),並將(2,2)也加入隊列。
(1,2)這個點已經處理完畢,於是可以將(1,2)出隊。(1,2)出隊之後,head指向了(2,1)這個點。通過(2,1)可以到達(2,2)和(3,1),但是因為(2,2)已經在隊列中,因此我們只需要將(3,1)入隊。
到目前為止我們已經擴展出從起點出發2步以內可以到達的所有點,可是依舊沒有到達小哈所在的位置,因此還需要繼續擴展,直到找到小哈所在的點才算結束。
為了方便向四個方向擴展,這裏需要一個next數組:
int next[4][2] = { //順時針方向 {0,1}; //向右走 {1,0}; //向下走 {0,-1}; //向左走 {-1,0}; //向上走 }
完整代碼如下
#include<stdio.h> struct note{ int x; //橫坐標 int y; //縱坐標 int f; //父親在隊列中的編號(本題不需要輸出路徑,可以不需要f) int s; //步數 }; int main() { struct note que[2501]; //因為地圖大小不超過50*50,因此隊列擴展不會超過2500個 int a[51][51] = {0}; //用來存儲地圖 int book[51][51] = {0}; //數組book的作用是記錄哪些點已經在隊列中了,防止一個點被重復擴展,並全部初始化為0 //定義一個用於表示走的方向的數組 int next[4][2] = { //順時針方向 {0,1}, //向右走 {1,0}, //向下走 {0,-1}, //向左走 {-1,0}, //向上走 }; int head,tail; int i,j,k,n,m,startx,starty,p,q,tx,ty,flag; scanf("%d %d",&n,&m); for(i=1;i<=n;i++) for(j=1;j<=m;j++) scanf("%d",&a[i][j]); scanf("%d %d %d %d",&startx,&starty,&p,&q); //隊列初始化 head = 1; tail = 1; //往隊列插入迷宮入口坐標 que[tail].x = startx; que[tail].y = starty; que[tail].f = 0; que[tail].s = 0; tail++; book[startx][starty] = 1; flag = 0; //用來標記是否到達目標點,0表示暫時沒有到達, 1表示已到達 while(head < tail){ //當隊列不為空時循環 for(k=0;k<=3;k++) //枚舉四個方向 { //計算下一個點的坐標 tx = que[head].x + next[k][0]; ty = que[head].y + next[k][1]; if(tx < 1 || tx > n || ty < 1 || ty > m) //判斷是否越界 continue; if(a[tx][ty] == 0 && book[tx][ty] == 0) //判斷是否是障礙物或者已經在路徑中 { book[tx][ty] = 1; //把這個點標記為已經走過。註意bfs每個點通常情況下只入隊一次,和深搜不同,不需要將book數組還原 //插入新的點到隊列中 que[tail].x = tx; que[tail].y = ty; que[tail].f = head; //因為這個點是從head擴展出來的,所以它的父親是head,本題不需要求路徑,因此可省略 que[tail].s = que[head].s+1; //步數是父親的步數+1 tail++; } if(tx == p && ty == q) //如果到目標點了,停止擴展,任務結束,退出循環 { flag = 1; //重要!兩句不要寫反 break; } } if(flag == 1) break; head++; //當一個點擴展結束後,才能對後面的點再進行擴展 } printf("%d",que[tail-1].s); //打印隊列中末尾最後一個點,也就是目標點的步數 //註意tail是指向隊列隊尾(最後一位)的下一個位置,所以這裏需要減1 getchar();getchar(); return 0; }
第一行有兩個數n m,n表示迷宮的行數,m表示迷宮的列數。
接下來n行m列為迷宮,0表示空地,1表示障礙物。
最後一行四個數,前兩個數表示迷宮入口的x和y坐標,後兩個為小哈的x和y坐標。
寫在最後
通過本次學習,我們知道一道問題的解決方法是多種多樣的,不光可以用深度優先搜索來解,也可以用寬度優先搜索。
個人感覺寬度優先搜索就像是一次病原體的擴散,目的是要以最短的速度擴散到最廣的範圍。
註:文章內容源自《啊哈算法》
層層遞進——寬度優先搜索(BFS)