参考:
https://www.cnblogs.com/Tovi/articles/6194815.html
https://blog.csdn.net/dangzhangjing97/article/details/81477192
一般来说,dfs总是先学,然后再学bfs,因为dfs就是一个简单的递归思想,理解起来比较简单,思维过程如下,
1 dfs(要搜的点){ 2 将该点标记为已搜过 3 if(终止条件){ 4 如果找存在路径,就退出 5 如果找最短路径,就比较这个与最小值,回去接着找 6 } 7 else{ 8 case1{ 9 目标值变化//可以是n层循环 10 dfs(case1); 11 目标值恢复到原来 12 } 13 case2 ... 14 case3 ... 15 } 16 }
用stack来理解就是,每次”返回“的方式都是弹出之前那一个,即由ann->弹出,下一个是ann-1,再看看ann-1指向的地方有没有没到的
总共三个过程:
- 某节点到底了,还没找到满足条件的
- 弹出 stack ,销毁这个分支, 返回到上一个
- 检查当前往下的另一个方向。有,则再往下另一个分支,压入stack。没有,就继续 "弹出"当前stack
但是学完dfs之后,再学习bfs的时候总是把这两个搞混, 尤其是它加入了queue这个结构来实现"返回",
而且之前一直搞不懂, bfs虽然是"按层"搜索,但是也有 "返回" 的过程, 可是, dfs也有返回过程啊,这个"返回"和dfs的到底有甚区别??!!
后来又重新看了一些stack和queue的辨析(文章开头的链接),才慢慢理解到:
虽然 bfs 也有“返回”,但是,它的返回,其实是在 an这一层已经完全遍历完了之后,才考虑“返回”还是继续 “往下”,
它是已经把当前这一层的情况都记录下来了,才考虑后续的操作,所以常常说bfs耗空间,因为它要记录!!!它的“返回”只有两个过程!
- 某节点到底了,还没找到满足条件的
- 直接横向看,检查,当前这一层是否还有没有遍历完的“邻支”,有,则再往旁边另一个分支。没有,就 "返回"
总结就是,dfs返回 ,先要找到“上家” ,再看有没有分支,而 bfs,可以直接找“邻居”,比较粗暴,直接横着来,
那它凭什么这么做呢?因为它用的是 queue ,这样来理解就是,bfs 总是需要一个代表“第n层”的队列,每次都会将当前层 ,全部能继续 “往下“ 元素 压进队列去,
然后横向剪 “邻枝” 或者说,排除邻支,以及包括从邻支到底层的所有可能,所以效率特别高,
即:搜索时首先将初始状态加到队列里,此后队列的最前端不断取出状态,(某层第x个元素)
把从该状态可以转移到的状态中尚未访问过的部分,但是又能访问下去的,加入队列(同一层的),
如此反复,直至队列被取空或找到了问题的解。因此确定宽度也很重要。
观察这个队列,我们可以发现所有的状态都是按照距离初始状态由近及远的顺序进入的。
又因为它是queue实现,先进先出,一定按照从前到后的次序,来“剪枝”/记录“路径”/“遍历”
因此,如果是bfs 特征一定是,它判断“成功”的条件,不需要到最底层,在当前层就已经可以排除掉,可以“预判”,可以在上层剪枝
所以适合——迷宫/找最小全“1”/“0”或者某一条件的矩阵,又或者同样可以用dfs解决但是对“更高效率”有要求的题目
如下为一般结构:
1 bfs(){ 2 初始点入列 3 while(终止条件不满足 或 队列不为空){//层数不为0 4 队列头出列 //更新队列,更新新的扩散出发点 5 for() //从出列元素开始扩散,满足的就入列 ,一般是一个循环,满足的意思是,满足后面还可以继续往下的就入列 6 找个数组记下来目标值 7 } 8 找目标值 9 }
为了理解,局这样一个栗子,1,2,3,4,5,6,7 假设分支是1—2,4 ;2—3,5,7; 4—6,那么过程就是,
1先入列做为起点,
进入循环,出列,
开始以1为起点for扩散,检查到1的儿子有2,4,加入队列,此时队列为4,2,for循环结束,
此时队伍不为空,while循环继续,
此时队列为4,2,队列先进先出(也可以理解为栈结构栈尾出),所以2先出来,进入循环,以2为起点扩散,找到3,5,7
加进队列,此时队列为7,5,3,4,所以之后下一个起点的会是4,
4后面的6加入之后,3,5,7再依次做起点,直到6,检查完结束