1. 程式人生 > >層層遞進——寬度優先搜索(BFS)

層層遞進——寬度優先搜索(BFS)

一個數 學習 情況 art 打印 能夠 blank alt .cn

問題引入

我們接著上次“解救小哈”的問題繼續探索,不過這次是用寬度優先搜索(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)