---
# System prepended metadata

title: BFS(廣度優先搜尋)

---

# BFS(廣度優先搜尋)
## 複習：實作queue&pair
```cpp=
#include <iostream>
#include <queue>
using namespace std;

#define F first  //定義F為first S為second
#define S second //define可以使程式簡短

int main(){
    queue<pair<int, int>> q;

    for(int i = 1; i <= 5; i+=2){
        q.emplace(i, i+1); //相當於 q.push({i, i+1});
    }

    while(!q.empty()){
        auto tmp = q.front(); //自動設定資料型態  (此為pair<int, int>)
        cout << tmp.F << ' ' << tmp.S << '\n'; //輸出first跟second
        
        /*
        也可寫成：
        auto [a, b] = q.front(); //將a = q.front().first, b = q.front().second
        cout << a << ' ' << b << '\n';
        */

        q.pop();
    }

    return 0;
}
```
:::spoiler 輸出
```
1 2
3 4
5 6
```
:::

## BFS概念說明
BFS是一種演算法(解題的方法)，精隨為藉由慢慢擴展的方式，來找到起點對於每個點的最短路徑
*也有一種說法叫淹水，慢慢地淹過每一格*
實際運作過程如下圖所例  ||(source: trt4497)||
![image](https://hackmd.io/_uploads/H1PGptXxWx.jpg)

## BFS實作
實作範例：
#### 輸入
第一行輸入一正整數 $N \leq 20$表示$N * N$的地圖
接下來有$N$行，每一行有$N$個數字$w_1, w_2, ... , w_n \in (0, 1)$
$w_i = 0$ 代表該格不能通過

#### 輸出
輸出一個正整數 $s$ 代表從左上$(0, 0)$走到右下$(n-1, n-1)$的最短步數
### 思路
我們可以明顯地觀察到需要使用BFS來計算最短路徑
而BFS的實作順序為 新增起點 $\rightarrow$ 遍歷地圖```mp```$*_{(1)}$ $\rightarrow$ 輸出右下角```cnt```數值即為最短路徑

---
*$_{(1)}$：
1. 取得現在位置座標 $(y,\ x)$ 並從```queue```中移除 
2. 往四個方向擴展 $\rightarrow$ 另存新座標 $(ny,\ nx)$
3. 判斷是否超出地圖範圍 ($0 \leq x,\ y < 20$)、```used```是否走過、```mp[ny][nx]```是否不能通過
4. ```used```陣列標記走過```= 1```、路徑長```cnt```陣列```+ 1```、合法座標放入```queue```中
---
### 版本1 (四個if判斷)
:::spoiler Code
```cpp=
#include <iostream>
#include <queue>
using namespace std;


int mp[50][50];
int used[50][50] = {0}, cnt[50][50] = {0}; //all = 0

int main(){
    int n; cin >> n;
    for(int i = 0; i < n; i++){
        for(int j = 0; j < n; j++){
            cin >> mp[i][j];
        }
    }

    queue<pair<int, int>> q;
    q.emplace(0, 0); //start

    while(!q.empty()){
        auto [y, x] = q.front();
        q.pop();
        
        for(int i = 0; i < 4; i++){ //4 dir
            if(i == 0){//up
                int ny = y - 1, nx = x;
                if(ny < 0 || ny >= n || nx < 0 || nx >= n) continue; //out
                if(used[ny][nx]) continue; //used before
                if(mp[ny][nx] == 0) continue; //cant pass

                used[ny][nx] = 1; //used
                cnt[ny][nx] = cnt[y][x] + 1;
                q.emplace(ny, nx);
            }else if(i == 1){//down
                int ny = y + 1, nx = x;
                if(ny < 0 || ny >= n || nx < 0 || nx >= n) continue; //out
                if(used[ny][nx]) continue; //used before
                if(mp[ny][nx] == 0) continue; //cant pass

                used[ny][nx] = 1; //used
                cnt[ny][nx] = cnt[y][x] + 1;
                q.emplace(ny, nx);
            }else if(i == 2){//left
                int ny = y, nx = x - 1;
                if(ny < 0 || ny >= n || nx < 0 || nx >= n) continue; //out
                if(used[ny][nx]) continue; //used before
                if(mp[ny][nx] == 0) continue; //cant pass

                used[ny][nx] = 1; //used
                cnt[ny][nx] = cnt[y][x] + 1;
                q.emplace(ny, nx);
            }else if(i == 3){//right
                int ny = y, nx = x + 1;
                if(ny < 0 || ny >= n || nx < 0 || nx >= n) continue; //out
                if(used[ny][nx]) continue; //used before
                if(mp[ny][nx] == 0) continue; //cant pass

                used[ny][nx] = 1; //used
                cnt[ny][nx] = cnt[y][x] + 1;
                q.emplace(ny, nx);
            }
        }

        if(cnt[n-1][n-1] != 0){
            cout << cnt[n-1][n-1];
            break;
        }
    }
}
```
:::
### 版本2 ($dx, dy$ 陣列)
根據上個版本，我們可以發現一個問題：~~程式碼又臭又長~~，且易讀性差
所以我們可以針對四個方向的遍歷去改良
新增一個 $\Delta x$ 陣列與一個 $\Delta y$ 陣列，分別儲存改變方向時對於座標的加數(可能為負)
經過此操作後，我們只需要每次呼叫 $\Delta x$、$\Delta y$ 中的每一項資料，與 $x,\  y$ 做加法運算即可

:::spoiler Code
```cpp=
#include <iostream>
#include <queue>
using namespace std;

int mp[50][50];
int used[50][50] = {0}, cnt[50][50] = {0}; //all = 0

int dy[4] = {-1, 1, 0, 0};
int dx[4] = {0, 0, -1, 1}; //up down left right

int main(){
    int n; cin >> n;
    for(int i = 0; i < n; i++){
        for(int j = 0; j < n; j++){
            cin >> mp[i][j];
        }
    }

    queue<pair<int, int>> q;
    q.emplace(0, 0);

    while(!q.empty()){
        auto [y, x] = q.front();
        q.pop();
        
        for(int i = 0; i < 4; i++){
            int ny = y + dy[i], nx = x + dx[i]; //change dir

            if(ny < 0 || ny >= n || nx < 0 || nx >= n) continue;
            if(used[ny][nx]) continue;
            if(mp[ny][nx] == 0) continue;

            used[ny][nx] = 1;
            cnt[ny][nx] = cnt[y][x] + 1;
            q.emplace(ny, nx);
        }

        if(cnt[n-1][n-1] != 0){
            cout << cnt[n-1][n-1];
            break;
        }
    }
}
```
:::

## 練習題
### [Zerojudge- a982 迷宮問題#1](https://zerojudge.tw/ShowProblem?problemid=a982)
:::spoiler 起點? 終點?
注意題目的座標表示方式
:::

:::spoiler 到不了終點
圖跑完程式會接到哪? 輸出就放在那
前面如果到的了要直接終止程式
:::

:::spoiler AC Code
```cpp=
#include <iostream>
#include <queue>
using namespace std;

#define pii pair<int, int>

int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};

int main(){
    int n; cin >> n;
    char mp[105][105];
    int used[105][105] = {0}, cnt[105][105] = {0};

    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++){
            cin >> mp[i][j];
        }
    }

    queue<pii> q;
    q.emplace(2, 2);
    cnt[2][2] = 1;
    used[2][2] = 1;

    while(!q.empty()){
        auto [y, x] = q.front(); q.pop();
        if(y == n-1 && x == n-1){
            cout << cnt[y][x];
            return 0;
        }
        
        for(int i = 0; i < 4; i++){
            for(int j = 0; j < 4; j++){
                int ny = y + dy[i], nx = x + dx[i];
                if(ny < 1 || ny > n || nx < 1 || nx > n) continue;
                if(used[ny][nx]) continue;
                if(mp[ny][nx] == '#') continue;

                used[ny][nx] = 1;
                cnt[ny][nx] = cnt[y][x] + 1;
                q.emplace(ny, nx);
            }
        }
    }

    cout << "No solution!";
    return 0;
}
```
:::
### [Zerojudge- d406 倒水時間](https://zerojudge.tw/ShowProblem?problemid=d406)
:::spoiler 往上流
注意處理題目輸入，處理方向的時候排除```上```
:::
:::spoiler 起點位置
保證到水位置只會在 $y = 0$ 的時候 且只有一個$1$
:::
:::spoiler 若干筆測資
```cpp
while(cin >> s)
```
注意輸出格式
:::
:::spoiler AC Code
```cpp=
#include <iostream>
#include <queue>
using namespace std;

#define pii pair<int, int>

int dy[4] = {-1, 1, 0, 0};
int dx[4] = {0, 0, -1, 1};

int main(){
    int s, Case = 0;

    while(cin >> s){
        Case++;
        int n, m; cin >> n >> m;
        int mp[105][105];
        int used[105][105] = {0}, cnt[105][105] = {0};
        queue<pii> q;
        bool flag = false;

        for(int i = 0; i < n; i++){
            for(int j = 0; j < m; j++){
                cin >> mp[i][j];
                if(!flag && mp[i][j] == 1){
                    q.emplace(i, j);
                    cnt[i][j] = 1;
                    used[i][j] = 1;
                    flag = true;
                }
            }
        }

        while(!q.empty()){
            auto [y, x] = q.front();
            q.pop();

            for(int i = 0; i < 4; i++){
                if(i == 0 && s == 2) continue; //skip up(s = 2)

                int ny = y + dy[i], nx = x + dx[i];
                if(ny < 0 || ny >= n || nx < 0 || nx >= m) continue;
                if(used[ny][nx]) continue;
                if(mp[ny][nx] == 0) continue;

                used[ny][nx] = 1;
                cnt[ny][nx] = cnt[y][x] + 1;
                q.emplace(ny, nx);
            }
        }
        
        cout << "Case " << Case << ":\n";
        for(int i = 0; i < n; i++){
            for(int j = 0; j < m; j++){
                cout << cnt[i][j] << ' ';
            }
            cout << "\n";
        }
    }
}
```
:::
### [Zerojudge- d453 最短距離](https://zerojudge.tw/ShowProblem?problemid=d453)
:::spoiler 輸入方式
用字串或字元存取(無空格)
:::
:::spoiler 起點終點
不一定固定  需要調整
直接判斷是否到終點並輸出```cnt```
:::
:::spoiler AC Code
```cpp=
#include <iostream>
#include <queue>
using namespace std;

#define pii pair<int, int>
#define F first
#define S second

int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, 1, -1};

int main(){
    int N; cin >> N;
    while(N--){
        int n, m, y1, x1, y2, x2; cin >> n >> m >> y1 >> x1 >> y2 >> x2;
        pii bg = make_pair(y1-1, x1-1), ed = make_pair(y2-1, x2-1);

        queue<pii> q; q.push(bg);
        char mp[105][105];
        int used[105][105] = {0}, cnt[105][105] = {0};

        used[bg.F][bg.S] = 1;
        cnt[bg.F][bg.S] = 1;
        for(int i = 0; i < n; i++){
            for(int j = 0; j < m; j++){
                cin >> mp[i][j];
            }
        }

        int flag = 0;
        while(!q.empty()){
            auto [y, x] = q.front(); q.pop();
            if(y == ed.F && x == ed.S){
                cout << cnt[y][x] << "\n";
                flag = 1;
                break;
            }
            for(int i = 0; i < 4; i++){
                int ny = y + dy[i], nx = x + dx[i];
                if(ny < 0 || ny >= n || nx < 0 || nx >= m) continue;
                if(used[ny][nx]) continue;
                if(mp[ny][nx] == '1') continue;

                used[ny][nx] = 1;
                cnt[ny][nx] = cnt[y][x] + 1;
                q.emplace(ny, nx);
            }
        }

        if(!flag) cout << "0\n";
    }
    return 0;
}
```
:::
### [CSES- Counting Rooms](https://cses.fi/problemset/task/1192)
:::spoiler 題譯
有一個$N * M$的建築地圖，你需要計算其中有幾個房間，你可以往上下左右行動

*#輸入*
第一行有兩個正整數$N \ M(1 \leq N, M \leq 1000)$ 表示圖的長度和寬度
接下來有$N$行，每行$M$個字元$c_1, c_2, ...,c_m \in (.,\#)$，$.$ 為地板，$\#$ 為牆壁

*#輸出*
輸出一個正整數表示房間的數量
:::
:::spoiler AC Code
```cpp=
#include <iostream>
#include <queue>
using namespace std;
 
#define F first
#define S second
 
char mp[1005][1005];
bool used[1005][1005] = {0};
int n, m, ans = 0;
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};

int main(){
    cin.tie(0) -> sync_with_stdio(0);
    cin >> n >> m;
    vector<pair<int, int>> v;
    for(int i = 0; i < n; i++){
        for(int j = 0; j < m; j++){
            cin >> mp[i][j];
            if(mp[i][j] == '.'){
                v.emplace_back(i, j);
            }
        }
    }
    
    for(auto i : v){
        if(!used[i.F][i.S]){
            pair<int, int> tmp = i;
            ans++;
            queue<pair<int, int>> q;
            q.emplace(tmp);
            while(!q.empty()){
                auto [nx, ny] = q.front(); q.pop();
                for(int i = 0; i < 4; i++){
                    int x = nx + dx[i], y = ny + dy[i];
                    if(x < 0 || x >= n || y < 0 || y >= m) continue;
                    if(used[x][y]) continue;
                    used[x][y] = true;
                    if(mp[x][y] == '.') q.emplace(x, y);
                }
            }
        }
    }
 
    cout << ans;
    return 0;
}
```
:::
### 變體題：[CSES- Labyrinth](https://cses.fi/problemset/task/1193)
~~不想弄題譯~~
:::spoiler 起點終點
處理輸入時同時判斷
:::
:::spoiler 回溯?
找到一條最佳路徑後  要怎麼儲存行走的方向?
答案是再創一個陣列```parents```儲存來時的座標
:::
:::spoiler AC code
```cpp=
#include <bits/stdc++.h>
using namespace std;
 
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, -1, 0, 1};
pair<int, int> parents[1005][1005];
 
int main(){
    cin.tie(0) -> sync_with_stdio(0);
    char mp[1005][1005];
    bool used[1005][1005];
    memset(used, 0, sizeof(used));
    memset(parents, 0, sizeof(parents));
    queue<pair<int, int>> q;
    int n, m; cin >> n >> m;
    for(int i = 0; i < n; i++){
        for(int j = 0; j < m; j++){
            cin >> mp[i][j];
            if(mp[i][j] == 'A') {q.emplace(i, j); used[i][j] = 1; parents[i][j] = {-1, -1};}
        }
    }
 
    while(!q.empty()){
        auto [x, y] = q.front(); q.pop();
        for(int i = 0; i < 4; i++){
            int nx = x + dx[i], ny = y + dy[i];
            if(nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
            if(used[nx][ny] || mp[nx][ny] == '#') continue;
            used[nx][ny] = 1;
            parents[nx][ny] = {x, y};
            //cout << nx << " " << ny << "\n";
            q.emplace(nx, ny);
            if(mp[nx][ny] == 'B'){
                int cnt = 0;
                cout << "YES\n";
                string ans = "";
                while(true){
                    auto [tmpx, tmpy] = parents[nx][ny];
                    if(tmpx == -1) break;
                    cnt++;
                    
                    if(tmpx < nx) ans += "D";
                    else if(tmpx > nx) ans += "U";
                    else if(tmpy < ny) ans += "R";
                    else ans += "L";
 
                    nx = tmpx, ny = tmpy;
                }
                reverse(ans.begin(), ans.end());
                cout << cnt << "\n" << ans;
                return 0;
            }
        }
    }
 
    cout << "NO";
}
```
:::