BFS/DFS解決隱式圖/樹問題

1.前言

隱式圖是僅給出初始結點、目標結點以及生成子結點的約束條件(題意隱含給出),要求按擴展規則應用於擴展結點的過程,找出其他結點,使得隱式圖的足夠大的一部分編程顯式,直到包含目標結點爲止。——百度百科

其中解決隱式圖問題的算法有盲目搜索方法(非啓發式搜索)啓發式搜索算法

盲目搜索方法又叫非啓發式搜索,是一種無信息搜索,一般只適用於求解比較簡單的問題,盲目搜索通常是按預定的搜索策略進行搜索,而不會考慮到問題本身的特性。常用的盲目搜索有廣度優先搜索深度優先搜索兩種。——百度百科

另外,由於DFS和BFS各有優劣,於是提出了一種結合兩種方法優點的算法——迭代加深搜索(IDDFS),讀者可自行百度。

啓發式搜索(Heuristically Search)又稱爲有信息搜索(Informed Search),它是利用問題擁有的啓發信息來引導搜索,達到減少搜索範圍、降低問題複雜度的目的,這種利用啓發信息的搜索過程稱爲啓發式搜索。 ——百度百科

啓發式算法包含A-star(A*)算法等,其中廣度優先搜索是A*算法的一種特例。

博主水平有限,以下只對DFS和BFS進行展開。


2.DFS與BFS的區別與優劣

2.1 區別

1.根本區別
以下這段話引自知乎,形象地闡述了兩者的根本區別。

  深度優先可以這樣想,一個人迷路,遇到很多分叉路口,他只有一個人,並且想走出去,所以只能一個個嘗試,一條道路走到黑,發現到頭了,然後再拐回去走剛纔這條路的其他分叉路口,最後發現這條路的所有分叉路口走完了,選擇另外一條路繼續以上操作,直到所有的路都走過了。
  廣度優先並不是這樣,一個人迷路,但是他有技能(分身術)它遇到分叉路口,不是選一個走,而是分身多個人都試試,比如有A、B、C三個分叉路口,它A路走一步,緊接着B路也走一步,然後C路也趕緊走一步,步伐整齊統一,直到所有的路走過了。

作者:一隻菜雞
鏈接:https://www.zhihu.com/question/28549888/answer/620787482
來源:知乎

2.個人總結的一些實現上的區別

(1)DFS基於棧實現,BFS基於隊列實現。
(2)BFS需要將結點的狀態保存到隊列中,因此一般BFS的空間開銷要大於DFS。

2.2 優劣

以下引自https://www.jiuzhang.com/qa/623/

1.BFS是用來搜索最短徑路的解是比較合適的,比如求最少步數的解,最少交換次數的解,因爲BFS搜索過程中遇到的解一定是離根最近的,所以遇到一個解,一定就是最優解,此時搜索算法可以終止。這個時候不適宜使用DFS,因爲DFS搜索到的解不一定是離根最近的,只有全局搜索完畢,才能從所有解中找出離根的最近的解。(當然這個DFS的不足,可以使用迭代加深搜索ID-DFS去彌補)

2.空間優劣上,DFS是有優勢的,DFS不需要保存搜索過程中的狀態,而BFS在搜索過程中需要保存搜索過的狀態,而且一般情況需要一個隊列來記錄。

3.DFS適合搜索全部的解,因爲要搜索全部的解,那麼BFS搜索過程中,遇到離根最近的解,並沒有什麼用,也必須遍歷完整棵搜索樹,DFS搜索也會搜索全部,但是相比DFS不用記錄過多信息,所以搜素全部解的問題,DFS顯然更加合適。

上面提到的迭代加深搜索(ID-dfs)充分吸收了BFSDFS各自的長處

另外,stackoverflow網站對這個問題一些IT行業大牛也有解答。
https://link.zhihu.com/?target=http%3A//stackoverflow.com/questions/3332947/when-is-it-practical-to-use-dfs-vs-bfs

例題

例題1

https://leetcode-cn.com/problems/perfect-squares/

描述

Given a positive integer n, find the least number of perfect square numbers (for example, 1, 4, 9, 16, ...) which sum to n.

Example 1:

Input: n = 12
Output: 3 
Explanation: 12 = 4 + 4 + 4.

Example 2:

Input: n = 13
Output: 2
Explanation: 13 = 4 + 9.

解析

class Solution {
public:
    unordered_set<int> sett;
    int numSquares(int n) {
        queue<int> q;
        q.push(0);
        int step=0;
        while(!q.empty()){
            step++;
            int cnt=q.size();
            while(cnt--){   //逐層入隊
                int num=q.front();
                q.pop();
                for(int i=1;i*i<=n-num;i++){
                    int a=num+i*i;
                    if(a>n) break;
                    if(a==n) return step;
                    if(sett.count(a)==0){  //剪枝
                        q.push(a);  //將平方數的和入隊以保存狀態
                        sett.insert(a);
                    }                    
                }
            }            
        }
        return 0;
    }
};

例題2

https://www.luogu.com.cn/problem/P1032

題目描述

在這裏插入圖片描述在這裏插入圖片描述

解析

BFS:每次取出隊首元素,對其按規則進行轉換,將所有可能的轉換結果都入隊。可以通過將已經入隊過的字符串加入到hashset中,入隊時不用將已經入隊過的字符串再入隊(因爲前面已經入隊過了,再入隊也不可能比前面一個更快地到達目標字符串),這就是一種“剪枝”。

這題有一個坑就是慎用size()-size(),具體見文章:這裏

#include<iostream>
#include<string>
#include<vector>
#include<unordered_set>
#include<map>
#include<queue>
using namespace std;
unordered_set<string> sett;
vector<pair<string, string>> v;
queue<string> q;
int main() {
	//freopen("1.txt", "r", stdin);
	string a, b, ta, tb;
	cin >> a >> b;
	while (cin >> ta >> tb) {
		v.push_back({ ta,tb });
	}
	q.push(a);
	sett.insert(a);
	int step = 0;
	while (!q.empty() && step < 10) {
		int cnt = q.size();
		while (cnt--) {
			string now = q.front(); q.pop();
			if (now == b) {
				printf("%d", step);
				return 0;
			}
			for (auto it : v) {
				string s1 = it.first, s2 = it.second, next;
				if (now.size() < s1.size()) continue;  //注意這裏
				for (int i = 0; i <= now.size() - s1.size(); i++) {
					auto pos = now.find(s1, i);
					if (pos != string::npos) {
						next = now;
						next.replace(pos, s1.size(), s2);
						if (!sett.count(next)) {
							q.push(next);
							sett.insert(next);
						}
					}
				}
			}
		}
		step++;
	}
	printf("NO ANSWER!");
	return 0;
}
小技巧:string.find()函數可以指定從哪個位置開始匹配,返回的仍然是在原始字符串中的位置。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章