暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

算法面试(七) 广度和深度优先算法

天澄技术杂谈 2019-04-05
421

系列文章导图:


全部系列文章请点击阅读原文


  1. 知识点

    • 1.1 二叉树的遍历

    • 1.2 BFS 广度优先搜索

    • 1.3 DFS 深度优先搜索

    • 1.4 BFS vs DFS

  • 2. 题目

    • 2.1 108. Convert Sorted Array to Binary Search Tree

      • 2.2 111. Minimum Depth of Binary Tree

      • 2.2.1 解题思路一: 递归

      • 2.2.2 解题思路二: DFS

      • 2.2.3 解题思路三: BFS

      • 2.2.4 思考题

    • 2.3 102. Binary Tree Level Order Traversal

      • 2.3.1 解题思路一: BFS

      • 2.3.2 解题思路二: DFS非递归实现

      • 2.3.3 解题思路三: DFS递归实现

      • 2.3.4 思考题

1. 知识点

本章节重点介绍二叉树的广度和深度优先搜索。

1.1 二叉树的遍历

前序遍历、中序遍历、后序遍历在之前将二叉树的时候已经提到过,这里不再重复,需要再次了解的请看下面链接。

二叉树的遍历

1.2 BFS 广度优先搜索

下面介绍一下BFS,即广度优先搜索,下面图解示例看BFS是如何遍历的:



上面四幅图,1-2-3-4,比较清晰的描述了BFS的是如何遍历的,可以理解为是按层进行扫荡。

1.3 DFS 深度优先搜索

跟BFS一样,同样给出图解示例,带大家理解DFS的遍历过程:

可以看出DFS,是一般先遍历左子树,并且将左子树的子孙全部遍历完,直到叶子节点,然后回到根节点,继续遍历右子树。

1.4 BFS vs DFS

根据上面对BFS和DFS的介绍,给出一张直观图来对比二者的区别:


2. 题目

2.1 108. Convert Sorted Array to Binary Search Tree

  • 题目要求
    将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。

  • Example

Given the sorted array: [-10,-3,0,5,9],

One possible answer is: [0,-3,9,-10,null,5], which represents the following height balanced BST:

0
/ \
-3 9
/ /
-10 5

  • 解题思路
    这题目主要考察对二叉搜索树的了解以及中序遍历的知识点。
    容易想到和理解的一条思路,就是利用二叉搜索树按中序遍历是有序的特性。而中序遍历的规则是 左-根-右。即数组的中间值则是根节点,左右两边则分别是左子树和右子树,按递归的思维,左子树和右子树也按照上面的逻辑进行树的转换。

  • 代码演示

class Solution(object):
def sortedArrayToBST(self, nums):
"""
:type nums: List[int]
:rtype: TreeNode
"""

if len(nums) == 0:
return None
mid = int(len(nums) / 2)
root = TreeNode(nums[mid])
root.left = self.sortedArrayToBST(nums[:mid])
root.right = self.sortedArrayToBST(nums[mid + 1:])
return root

2.2 111. Minimum Depth of Binary Tree

  • 题目要求
    Given a binary tree, find its minimum depth.找出二叉树的最小深度。

  • Example

    Given binary tree [3,9,20,null,null,15,7],
    3
    / \
    9 20
    / \
    15 7
    return its minimum depth = 2.

2.2.1 解题思路一: 递归

  • 思路
    上一篇讲了二叉树的最高的深度,用的就是递归思维,所以找出二叉树的最小深度,我们先来用递归解题,思路就是递归查找左子树和右子树的深度,然后取二者的小者即可。

  • 代码演示

class Solution(object):
def minDepth(self, root):
"""
:type root: TreeNode
:rtype: int
"""

if not root:
return 0
if root.left is None:
return self.minDepth(root.right) + 1
if root.right is None:
return self.minDepth(root.left) + 1
left = self.minDepth(root.right) + 1
right = self.minDepth(root.left) + 1
return min(left, right)

代码也比较清晰,可以跟找出二叉树的最大深度进行对比。也就是分别找出左子树和右子树的深度,取小者返回即可。

时间复杂度O(N)

2.2.2 解题思路二: DFS

  • 思路
    思路二则采用今天的主题,DFS来解,按照知识点的描述,DFS是先将左子树的自身遍历完毕,再回来遍历右子树。DFS非递归解法一般需要采用栈Stack数据结构,按层将右子树和左子树依次入栈,下一次先出左子树(后入),然后进行下一轮,判断左子树的子孙,直到左子树遍历完毕,获取到左子树的最高深度;右子树也是如此。如果该节点是叶子节点,则说明已经到底,将此深度与最小深度进行比较。

  • 代码演示

class Solution(object):
def minDepth(self, root):
"""
:type root: TreeNode
:rtype: int
DFS O(n)
"""

if not root:
return 0
stack = [(root, 1)]
min_depth = float("inf")
while stack:
cur_node, cur_depth = stack.pop()
children = [cur_node.right, cur_node.left]
if not any(children):
min_depth = min(min_depth, cur_depth)
for child in children:
if not child:
continue
stack.append((child, cur_depth + 1))
return min_depth

先定义一个min_depth,初始化为正无穷,python里正无穷的表达方式是float("inf")。然后检查该节点是否是叶子节点,any([])是表示该数组中是否全为空,如果是则返回True,否则返回False。如果左节点和右节点全为空,则表示是叶子节点,则取该深度与最小深度的小者。然后按先右后左的顺序依次入栈即可。

时间复杂度O(N)

2.2.3 解题思路三: BFS

  • 思路
    思路三采用BFS,逐层扫荡二叉树,BFS在此题中,还有一点优势是如果发现某一层出现叶子节点,则直接返回此节点所处的深度就是答案,因为逐层扫荡下去,最先出现叶子节点的层肯定也是深度最小,肯定越往下深度越高。BFS非递归解法则需要借助双端队列的数据结构,因为需要从首边出,尾部进,满足先进先出的特点。

  • 代码演示

from collections import deque

class Solution3(object):
def minDepth(self, root):
"""
:type root: TreeNode
:rtype: int
BFS O(n)
"""

if not root:
return 0
queue = deque([(root, 1)])
while queue:
cur_node, cur_depth = queue.popleft()
children = [cur_node.left, cur_node.right]
if not any(children):
return cur_depth
for child in children:
if not child:
continue
queue.append((child, cur_depth + 1))

deque则是Python中双端队列的实现。也是先检查该节点是否是叶子节点,如果是则直接返回结果,否则进行将下一层的左右节点分别入队列

时间复杂度 O(n)

2.2.4 思考题

尝试将上一篇中求二叉树的最高深度,leetcode第104题,也按照DFS和BFS的思路进行求解。如果能自由的写出来,说明你已经掌握DFS和BFS了。

2.3 102. Binary Tree Level Order Traversal

  • 题目要求
    Given a binary tree, return the level order traversal of its nodes' values. (ie, from left to right, level by level).
    给定一颗二叉树,按层进行遍历,要求先左后右.

  • Example

    Given binary tree [3,9,20,null,null,15,7],
    3
    / \
    9 20
    / \
    15 7
    return its level order traversal as:
    [
    [3],
    [9,20],
    [15,7]
    ]

2.3.1 解题思路一: BFS

  • 思路
    按层进行遍历,不就是BFS的特性嘛,所以最直接解法就是BFS了,上面题目已经讲解了BFS是如何实现,接下来看代码讲解.

  • 代码演示

from collections import deque

class Solution(object):
def levelOrder(self, root):
"""
:type root: TreeNode
:rtype: List[List[int]]
BFS
"""

if not root:
return []
queue = deque([root])
result = []
while queue:
cur_level = []
for _ in range(len(queue)):
cur_node = queue.popleft()
cur_level.append(cur_node.val)
if cur_node.left:
queue.append(cur_node.left)
if cur_node.right:
queue.append(cur_node.right)
result.append(cur_level)
return result

BFS非递归实现,首先初始化双端队列,然后判断一层的节点个数,循环每一个节点,进行遍历,同时将每一个节点的孩子进入队列,进行下一轮遍历。

2.3.2 解题思路二: DFS非递归实现

  • 思路
    如果此题按DFS是否可以实现了?答案肯定是可以的,麻烦在于需要记住每一层的level,这样遍历左子树后,遍历右子树时需要找到所属的层数。即需要知道每一个节点所属哪一层。

  • 代码实现

class Solution(object):
def levelOrder(self, root):
"""
:type root: TreeNode
:rtype: List[List[int]]
DFS
"""

if not root:
return []
stack, result = [], []
stack.append((root, 0))
while stack:
cur_node, level = stack.pop()
if len(result) < level + 1:
result.append([])
result[level].append(cur_node.val)
children = [cur_node.right, cur_node.left]
if not any(children):
continue
for child in children:
if not child:
continue
stack.append((child, level + 1))
return result

DFS非递归实现采用栈,栈中的元素是一个元组,第一个元素是节点,第二个元素是元素的层数,这样遍历到该节点时,可以将该节点的值放入所属的层次的数组中。
需要注意的是孩子们的压栈顺序是先右孩子再左孩子,因为需要先遍历左孩子,后进先出。

2.3.3 解题思路三: DFS递归实现

DFS这里演示如何使用递归实现,思路跟解题思路二一致。

  • 代码演示

class Solution(object):
def levelOrder(self, root):
"""
:type root: TreeNode
:rtype: List[List[int]]
DFS
"""

if not root:
return []
self.result = []
self.dfs(root, 0)
return self.result

def dfs(self, node, level):
if not node:
return
if len(self.result) < level + 1:
self.result.append([])

self.result[level].append(node.val)
self.dfs(node.left, level + 1)
self.dfs(node.right, level + 1)

这里需要借助一个函数dfs,传入2个参数,一个是节点,一个是节点所属的层数,这样可以知道该节点的值该加入哪一层。

2.3.4 思考题

leetcode 第107题,是该题的变种:107. Binary Tree Level Order Traversal II
Example:

    Given binary tree [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
return its bottom-up level order traversal as:
[
[15,7],
[9,20],
[3]
]

从底部按层遍历,解题思路也是DFS和BFS,欢迎大家在评论区交流。

可以看出DFS和BFS的解题思路是可以复制的,基本都是这个套路,如果你以完全理解,则相关题目是完全难不倒你。


全部系列文章请点击阅读原文。

文章转载自天澄技术杂谈,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论