前言
本文需要有对数据结构一定的认识,如果不理解二叉树的读者可以先去学一下二叉树,有助于更好地理解这篇推文。
什么是二叉树?
二叉树是一种数据结构,指的是每个节点都有最多左右两颗子树的数据结构。也就是说,通过节点之间的相互连接(是有左右之分的),可以连成一棵倒立着的树。(图示树A结点的右子树是二叉树,但左子树不是)
什么是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层,以此类推。
这里我用一串图片来讲一下这个过程,一步步模拟DFS(其实是因为我不会做GIF):
这个时候:
然后我们发现
这个时候
这个时候我们发现,
这个时候我们发现,
那么我们就使用DFS成功地加入了一棵二叉树每一层的最右结点。
我们在前面说过,DFS是使用栈辅助的一种算法,但是我们在这里并没有使用栈啊?别急,在调用DFS函数的时候,本身就已经使用栈了。这个栈被称为函数栈。接下来我依然用一组图来看一下函数是怎么压栈的。
后面的
BFS如何解决层次遍历问题呢?
我回来啦!BFS如何解决层次遍历问题呢?
首先我们把树的图片放回来,免得大家再上去翻那个树的图片了。
那么我们该如何层次遍历这棵树呢?
首先我们先把这棵树的层次遍历结果写出: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常要配合一些剪枝的算法,以降低其时间复杂度。