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()函数可以指定从哪个位置开始匹配,返回的仍然是在原始字符串中的位置。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章