1. 程式人生 > >BFS(廣度優先搜尋演算法)

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;
}