---
# System prepended metadata

title: DFS & BFS
tags: [進階班]

---

---
tags: 進階班
---

# DFS & BFS

## 前言

在暴力搜尋法中(就是無腦窮舉)，除了常見的幾種用 `for` 迴圈就可以達成的以外，

比較困難而且重要的就是 `DFS` 和 `BFS` 了。

## DFS (Depth First Search)

中文翻譯為：深度優先搜尋法

假設今天要處理：給定一數 $n$，求 $1$ ~ $n$ 的所有排列方法，

顯然靠 `for` 迴圈不太夠，因為你需要 $n$ 個 `for` 迴圈，而且 $n$ 是不固定的。

這時，我們可以靠遞迴來實現這個問題。

釐清我們需要什麼:

1. 對於每一格，$1$ ~ $n$ 都要有機會排進去 $\Rightarrow$ `for loop`

3. 我們需要把 $n$ 格都遍歷過 $\Rightarrow$ `dfs(cur + 1)`
4. 在一組排列中，不能有數字重複 $\Rightarrow$ `bool check[n]` 達成 $O(1)$ 判斷
5. 遞迴的 `return` 機制: `cur == n` (此時可以順便輸出目前排列)
6. 在 `return` 同時，因為會回到遞迴函式中的 `for` 迴圈並準備填入下一個數字，
    
    因此要把 `check[i]` 還原，以利於後續 `i` 有機會再次被填入格子。

可以根據以上幾點寫出 `code`

```cpp=
#include<bits/stdc++.h>
using namespace std;
int arr[10], n; bool check[11];
void dfs(int cur) {
	if (cur == n) {
		for (int i = 0; i < n; i++) cout << arr[i] << ' ';
		cout << '\n';
		return;
	}
	for (int i = 1; i <= n; i++) {
		if (!check[i]) {
			check[i] = 1, arr[cur] = i;
			dfs(cur + 1);
			check[i] = 0, arr[cur] = 0;
		}
	}
}
int main() {
	while (cin >> n) dfs(0);
	return 0;
}
```

其實 `DFS` 的寫法有一定規則可循：

1. 先思考用什麼遞迴 (上述例子是數列第幾項)
2. `for loop` 視情況使用，有時候用不到
3. 遞迴時的 `check` 機制是什麼？
    
    很多時候可以建陣列達成 $O(1)$，但有一些需要 $O(N),\; O(lgN)$判斷，
    
    寫 `bool check()` 可能較美觀
4. 在遞迴時，判定可以繼續往下遞迴時，應該更新 `check` 再往下遞迴
5. 確定 `return` 條件，且 `return` 時，應把 `check` 還原

記起這些步驟，練個 $3$ 題左右，你就成為 `DFS, recursion` 大師了！

例題的小變化版：[a343: P(n, m) 的奧妙](http://203.64.191.163/ShowProblem?problemid=a343)

## BFS (Breadth First Search)

中文翻譯：廣度優先搜尋法

跟 `DFS` 的例題一樣：假設今天有一數 $n$，請你輸出所有 $1$ ~ $n$ 的排列方法。

雖然本題也是可以用 `DFS` 解，但是來聽聽別的思維：`BFS` 吧！

`BFS` 的概念是：先把第一格的所有可能記下來，

再把第二格填上去，

然後是第三格，以此類推直到結束。

最後再把所有解依序輸出即可。

在做 `BFS` 時，你需要：

1. 儲存單一種情況的容器 $\Rightarrow$ `vector`

2. 把所有 `vector` 儲存起來，而且需要存入及丟出：`deque`

3. 跟 `DFS` 一樣需要檢查那些數字已經被用過，不過這裡先用 $O(N)$ 檢查

```cpp=
#include<bits/stdc++.h>
using namespace std;
int main() {
	int n;
	while (cin >> n) {
		deque<vector<int>> dq = {{}};
		while (!dq.empty()) {
			vector<int> vec = dq.front(); dq.pop_front();
			if (vec.size() == n) {
				for (auto & i : vec) cout << i << ' ';
				cout << '\n';
                              break;
			}
			for (int i = 1; i <= n; i++) {
				bool flag = true;
				for (size_t j = 0; j < vec.size(); j++) {
					if (vec[j] == i) {flag = false; break;}
				}
				if (flag){
					vec.push_back(i);
					dq.push_back(vec);
					vec.pop_back();
				}
			}
		}
	}
	return 0;
}
```

顯然這題不是一個好的 `BFS` 例題 (空間、時間複雜度都堪憂)

真正會用 `BFS` 來解的是走迷宮最短路徑之類的題目 

(從起點開始往外走，最先碰到終點的步數即最短路徑)

例如：[a337: Maze Runner](http://203.64.191.163/ShowProblem?problemid=a337)

## DFS & BFS 重要技巧：剪枝

假設今天有一個題目：給定一字串 $s$，輸出 $s$ 的所有子序列。

可能可以用 `DFS` 無腦解：

```cpp=
string str; bool check[10];
void dfs(string s, int idx) {
	cout << s << '\n';
	for (size_t i = idx; i < str.size(); i++) {
		if (!check[i]) {
			s += str[i], check[i] = 1;
			dfs(s, i + 1);
			s.erase(s.end() - 1), check[i] = 0;
		}
	}
}
int main() {
	while (cin >> str) {
		dfs("", 0);
	}
	return 0;
}
```

但是，假設今天輸入的字串有重複字元呢？

~~把子序列排序後再用 `unique` 篩出唯一字串~~

如果今天的字串是 `AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`，那這樣子序列數量超過 $10^9$，顯然不太行。

這時要使用「剪枝」技術，讓產生的字串不重複。

小提示：在子序列 $str$ 中，若 `str.pos(i) < str.pos(j)`，則 `s.pos(i) < s.pos(j)`

1. 先把整個字串先 `sort` 過一遍，但是要記下字元原位置

2. 如果不想處理到重複字串，則 `dfs` 時 `s += str[i]` 之 `str[i]` 不可以重複 
    $\Rightarrow$ 可以設一個 `char pre` 記錄前一個字元取了什麼 (字串已經 `sort` 過了，因此重複字元相鄰)
3. 要判斷 `str.pos[s[s.size() - 1]] < str.pos[i]`
4. 第 2. 點 和 第 3. 點 就是這個剪枝 `DFS` 的 `check` 機制

```cpp=
string str;
vector<pair<char, int>> v(11);
void dfs(string s, int pre_pos) {
	cout << s << '\n';
	char pre_ch = 0; //NULL
	for (size_t i = 1; i <= str.size(); i++) {
		if (pre_pos < v[i].s && pre_ch != v[i].f) {
			s += v[i].f, pre_ch = v[i].f;
			dfs(s, v[i].s);
			s.erase(s.end() - 1);
		}
	}
}
int main() {
	while (cin >> str) {
		v[0].f = '#', v[0].s = -1;
		for (size_t i = 1; i <= str.size(); i++) {
			v[i].f = str[i - 1], v[i].s = i;
		}
		sort(v.begin() + 1, v.begin() + str.size() + 1);
		dfs("", -1);
	}
	return 0;
}
```

這題的 `BFS` 也是同樣道理，如果直接處理 `BFS`，空間一樣會爆。

那麼，`BFS` 剪枝方法是？

其實和 `DFS` 差不多，一樣是判斷 `pre_ch != i.ch && pre_pos < i.pos`

附上有 `struct` 的寫法或許可以看得比較懂(？

(變數名稱不是 `first` 和 `second`)

```cpp=
struct wd {
	char c; int idx;
	bool operator<(const wd& w) const{return c == w.c ? c < w.c : idx < w.idx;}
};
struct ans {string s; wd w;};
int main() {
	vector<wd> str(11);
	string s;
	while (cin >> s) {
		const int len = int(s.length());
		for (int i = 0; i < len; i++) str[i].c = s[i], str[i].idx = i;
		sort(str.begin(), str.begin() + len);
		deque<ans> dq{{"", {0, -1}}};
		while (!dq.empty()) {
			ans tmp = dq.front(); dq.pop_front();
			cout << tmp.s << '\n';
			char pre = 0;
			for (int i = 0; i < len; i++) {
				if (pre == str[i].c) continue;
				else if (str[i].idx > tmp.w.idx) {
					dq.push_back({tmp.s + str[i].c, str[i]});
					pre = str[i].c;
				}
			}
		}
	}
	return 0;
}
```