python dfs算法_搜索算法浅析——从二叉树一窥BFS和DFS

前言

本文需要有对数据结构一定的认识,如果不理解二叉树的读者可以先去学一下二叉树,有助于更好地理解这篇推文。

什么是二叉树?

二叉树是一种数据结构,指的是每个节点都有最多左右两颗子树的数据结构。也就是说,通过节点之间的相互连接(是有左右之分的),可以连成一棵倒立着的树。(图示树A结点的右子树是二叉树,但左子树不是)

v2-d299a985f34d1b1fc3e21873f156f637_b.jpg
一棵树的例子:图自:https://www.jianshu.com/p/bf73c8d50dc2

什么是BFS?

BFS,全称Breadth First Search,中文名为广度优先搜索。是一种以宽度方向搜索某种数据结构的一种方法,常用队列辅助BFS算法。广度优先搜索并不是某一个固定的算法,它是一类符合上述所说的算法。

什么是DFS?

DFS,全称Depth First Search,中文名为深度优先搜索。是一种以深度方向搜索某种数据结构的方法,常用栈来辅助DFS算法。深度优先搜索大多要以递归实现,所以要考虑递归爆栈的可能性。

二叉树和BFS/DFS有什么关系呢?

关系可大了!我给大家出两道题,你们就知道了。

问题1:如何打印一棵二叉树的每一层的最右结点?

这里说的每一层,是指在同一深度上。什么叫深度呢?拿上面的那棵二叉树来说,A结点的深度为0,B、C结点的深度为1,以此类推。

那么这个问题的答案我们手写出来,就是:A、C、F、J

那么对于一棵给定的二叉树来说,我们怎么通过代码实现找到每一层的最右的结点呢?

这里先放一放,我们来看第二个问题。

问题2:如何层次遍历一棵二叉树?

(读者:你怎么老说术语!)

层次遍历,指的是按照每一层从左到右(或者从右到左)的顺序,串起来遍历这一棵树。

对于上述的二叉树,答案就是:A、B、C、D、E、F、G、H、I、J

好,那么这两个问题和BFS/DFS有什么关系呢?我们要使用DFS解决问题1,用BFS解决问题2。

DFS如何解决每一层最右结点问题呢?

首先,我们先放出Python语言代码:

class 

我们可以看到,DFS函数是解决这个问题的本质函数,它是一个递归函数,终止条件为当前结点为空结点的时候,退出这个函数。level是当前的层数,A结点位于第0层,B、C结点位于第1层,以此类推。

指的是,如果发现当前的层已经是探索过的最深的层了,就把当前的head结点放进列表中。这个head结点为什么一定是当前层的最右结点呢?

这里我用一串图片来讲一下这个过程,一步步模拟DFS(其实是因为我不会做GIF):

v2-58797ba58ee27d41079deaac0641547a_b.jpg
初始的状态是这样的

v2-e94c92cef614998b921d0085f9f4fcff_b.jpg
执行完DFS(A,0,L)后

v2-a6d3ac27db86d9950b1de16b1d789d2a_b.jpg
执行DFS(C.right,level+1,L)后

这个时候:

然后我们发现

已经没有左右结点了,所以
都会返回。这个时候,函数的执行会回到:

v2-e94c92cef614998b921d0085f9f4fcff_b.jpg

这个时候

已经执行完了,就会执行

v2-5c483ccf576f63ab56b4e2eb5f420bf6_b.jpg
执行DFS(C.left,level+1,L)后

这个时候我们发现,

,但这个时候
,所以
结点不会被加入到
中。这个时候将会继续执行

v2-7ae5da2e7a196af23b6d9887462e9f93_b.jpg
执行DFS(E.right,level+1,L)后

这个时候我们发现,

结点满足加入L的条件,将
结点加入到
中,此时的
。这个时候算法没有结束,但是后面所有的DFS操作都不会再往L里加入新的结点了,因为
恒成立。

那么我们就使用DFS成功地加入了一棵二叉树每一层的最右结点。

我们在前面说过,DFS是使用栈辅助的一种算法,但是我们在这里并没有使用栈啊?别急,在调用DFS函数的时候,本身就已经使用栈了。这个栈被称为函数栈。接下来我依然用一组图来看一下函数是怎么压栈的。

v2-d50169d5510274cce4e68823a9d2e4a6_b.png
先压入DFS(A,0,L)准备执行,此时L=[]

v2-c9119724592c552461928f2d88eed7b9_b.png
DFS(A,0,L)被弹出执行,压入DFS(B,1,L)和DFS(C,1,L),此时L=[A]

v2-662d19d4c824e32491f3f78331f91479_b.png
DFS(C,1,L)被弹出执行,压入DFS(E,2,L)和DFS(F,2,L),此时L=[A,C]

v2-24993f3732911b7b73e87f5193a1a156_b.png
DFS(F,2,L)和DFS(E,2,L)都被弹出执行,DFS(E,2,L)压入DFS(null,3,L)和DFS(J,3,L),此时L=[A,C,F]

v2-25cbe8ec8b39a93da42289103e5bbe66_b.png
最后DFS(J,3,L)和DFS(null,3,L)被弹出执行,此时L=[A,C,F,J]

后面的

的压栈弹栈过程我就不再赘述了,读者可以自己通过纸笔模拟。

BFS如何解决层次遍历问题呢?

我回来啦!BFS如何解决层次遍历问题呢?

首先我们把树的图片放回来,免得大家再上去翻那个树的图片了。

v2-45611ed823074b7f926101eb493288b8_b.jpg

那么我们该如何层次遍历这棵树呢?

首先我们先把这棵树的层次遍历结果写出:FCEADHGBM

然后我们再看看它们之间的顺序:

F的左儿子是C,右儿子是E。C的左儿子是A,右儿子是D。E的左儿子是H,右儿子是G……

有没有看出来一点什么端倪?想必没有,我们把空结点加上去。

层次遍历的结果:FCEADHG(NULL)(NULL)B(NULL)(NULL)(NULL)M(NULL)

我们可以看到,如果以入队出队的角度来看,这个序列是可以这样形成的:

F入队,然后出队。

然后F的左孩子入队,右孩子入队。

F的左孩子出队,F的左孩子的左孩子入队,F的左孩子的右孩子入队。

以此类推。

其实就是:

Queue Q;
Q.enqueue(head);
while Q.isempty() is not True:
    node = Q.dequeue();
    if (node->left) Q.enqueue(node->left);
    if (node->right) Q.enqueue(node->right);
    print(node->val);

这样就是BFS,就是把任务分层,先搜完一层的所有可能,再去搜这些可能到下一层的可能。BFS之所以叫广搜,是因为它具有全面性和盲目性,如果不配合剪枝算法,它可以搜索完所有的可能。这在我们执行寻路等算法的时候,是耗时耗力的。所以BFS常要配合一些剪枝的算法,以降低其时间复杂度。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值