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
)充分吸收了BFS
和DFS
各自的长处
另外,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;
}