1.什么是BFS
- 广度优先搜索BFS,是连通图的一种遍历策略
- 因为他是广度优先,所以每次迭代会以最广的返回辐射开来,往能走的方向(没走过的方向),各走一步。
2.BFS算法的基本思路
- 目标是从一个连通图的一个起点到一个终点,找一个最短路径
- 每次迭代都以辐射的形式,探索一步。
- 如果得不到目的解,那就返回事先定好的值,如果找到直接返回目的解。
- 辐射的过程中需要标记已走过的节点,不能重复走,直到达到终点。
- 【使用的是循环加队列实现的】👇
2.1伪代码
初始化队列Q
Q={起点s};
标记s为己访问;
while (Q非空) {
取Q队首元素u;
u出队;
if (u==1目标状态){
...
}
所有与u相邻且未被访问的点进入队列;
标记u相邻且进入队列的节点为已访问;
}
3.什么是DFS以及基本思路
- 深度优先搜索和广度优先的遍历方式相反,广度优先是先寻求最广的辐射,每个方向都只走一步。而深度优先是每次搜索一个方向指导把这个方向最深的位置搜索了,然后返回到一开始的节点,换下一个方向,直到把所有方向最深路径走一遍
- 注意DFS一次搜索经过的位置上,标记了visited,但是递归返回的时候可能需要进行消除标记。
- 【dfs一般借用递归完成整个算法的构造】
3.1伪代码
DFS(deep,...){ dep代表目前DFS的深度
if (找到解|走不下去了){
return;
}
枚举下一种情况
DFS(deep+1,...).
}
4.DFS和BFS的区别
DFS:
- 使用栈保存未被检测的结点,结点按照深度优先的次序被访问并依次被压入栈中,并以相反的次序出栈进行新的检测。
- 类似于树的先根遍历
- 例子:走迷宫,你没有办法用分身术来站在每个走过的位置。不撞南山不回头。
BFS:
- 使用队列保存未被检测的结点。结点按照宽度优先的次序被访问和进出队列。
- 类似于树的按层次遍历的过程
- 例子:你的眼镜掉到地上以后,你趴在地板上找。你总是先摸最接近你的地方,如果没有,再摸远一点的地方
适合场景
- BFS用来搜索最短路径的解法是比较合适的,比如求最少步数的解,最少交换次数的解,最快走出迷宫等等,
- 而如果用dfs,会搜一些其他的位置,需要花相对比较多的时间,需要搜很多次,然后如果找到还不一定是最优解,还要记录这次找的位置,与之后找到的答案进行比较,看看谁才是最优解,这样就比较麻烦。
- DFS用来搜索全部的解。
在记录路径的时候会简单一点,只需要把每一次找的点,放进去答案中就好;并且,相对而言dfs在做很多题目可以用上,比如分治、数位dp,其实就是递归,而dfs用的就比较少。
性能对比
-
BFS是浪费空间节省时间,DFS是浪费时间节省空间。
因为dfs要走很多的路径,可能都是没用的。(做有些题目的时候要进行剪枝,就是确定不符合条件的就可以结束,以免浪费时间,否则有些题目会超时TLE)
而bfs可以走的点要存起来,需要队列,因此需要空间来储存,便是浪费了空间,假设有十层,各个结点有2个子节点,那么储存到第10层就要存2^10-1个数据,而dfs只需要存10个数据,但是找到答案的速度相对快一点。 -
当遇到一些层次较深的树图时,dfs一读到底的风格就不太适合一些限制时间的题,因为针对这些内存巨大的,使用dfs就会比较耗时,比方说一个数有1000个树枝,每个树枝有100个子叶,第1000的第一颗叶子就是正解,但是在遍历的时候却选择从第一个树枝开始遍历且一读到底,遍历到正解得时候已经做了999*100次遍历了,这时只要把dfs换成bfs,就会节约许多时间,只需要1000次的遍历就可以找到答案,所以由上可得,在一些大的内存或者层次比较的大树,应选择使用bfs来解决;反之,则使用dfs。
-
还有一些根据题意就可以看出来是选择dfs还是bfs;比如题中需要的目的解是需要判断从最初的状态能否得到,这个时候就应该选择dfs了。像一些需要一步一步的完成就要选择bfs。
-
总的来说,dfs和bfs大都运用在图的处理上:如迷宫,八皇后,n皇后,油田,连通块,数独等,在这些类型的题上几乎全需要dfs或bfs来解,或者说有些难度更大的需要bfs+dfs或bfs+bfs才能完成。