深度优先搜索
深度优先搜索是对图的一种遍历方式,如命所示,只要有可能,就尽可能的“深入”。以下为《算法导论》中对深度优先搜索的解释:
深度优先搜索总是对最近才发现的结点 v 的出发边进行探索,直到该结点的所有出发边都被发现为止。一旦结点 v 的所有出发边都被发现,搜索则“回溯”到 v 的前驱结点(v 是经过该结点才被发现的),来搜索该前驱结点的出发边。
该过程一直持续到从源结点可以达到的所有结点都被发现为止。如果还存在尚未发现的结点,则深度优先搜索将从这些未被发现的结点中任选一个作为新的源结点,并重复同样的搜索过程。该算法重复整个过程,直到图中的所有结点都被发现。
深度优先搜索的实现
实现深度优先搜索,通常使用递归的方式,也可以是利用栈来实现。
而深度优先搜索可以分为先序,中序,后序,也就是我们所熟悉的树的遍历的方式。这三种方式本质上还是遵循于深度优先搜索的原则,即尽可能的“深入”,不同的是对结点的处理时机,如先序遍历总是先对当前结点进行了处理,再继续遍历,而后序遍历则是遍历到叶子结点才逐步对结点进行处理。
关于树的遍历的知识可以参考 Construct Binary Tree From Preorder and Inorder Traversal Solution 文章中 How to traverse the tree 一节。
以下为参考自《算法导论》的深度优先搜索的伪代码:
# 用于记录时间
time = 0
def dfs_visit(graph, u):
time = time + 1
# 被发现的结点标记为灰色,d 为结点的发现时间
u.color = GRAY
u.d = time
for v in graph.Adj[u]:
if v.color == WHITE:
v.p = u
dfs_visit(graph, v)
# 完成处理的结点标记为黑色,f 为结点的结束时间
u.color = BLACK
time = time + 1
u.f = time
def dfs(graph):
# 图的初始化
for vertex in graph.V:
vertex.color = WHITE
# p 为结点的前驱属性
vertex.p = None
for vertex in graph.V:
if vertex.color == WHITE:
dfs_visit(graph, vertex)
需要注意的是,我们是对图进行深度优先搜索,在初学深度优先搜索的时候,总是会将问题局限为对树的搜索,树是图的一种,局限思维往往会导致无法解决问题。
深度
深度优先搜索可以将结点按层划分,也就是遍历的深度,每进行一次递归,就是对下一层结点的访问。
在搜索时记录深度,可以在搜索时确认当前遍历到的结点的一些具体信息。
有以下例题:Find Largest Value in Each Tree Row
该题目就是找出树的每一层的最大值。在遍历时记录了深度,然后让同层结点进行比较即可。以下为解题源码:
# Definition for a binary tree node.
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
def largestValues(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
MIN_VALUE = -2**31
def dfs(node, depth, res):
if depth not in res:
res[depth] = MIN_VALUE
res[depth] = max(res[depth], node.val)
if node.left is not None:
dfs(node.left, depth + 1, res)
if node.right is not None:
dfs(node.right, depth + 1, res)
if root is None:
return []
res = dict()
dfs(root, 0, res)
return list(res.values())
再看以下例题:Binary Tree Right Side View
这条题目的要求就是找出树每一层的最右结点。同样这需要在遍历时记录深度,而且需要我们能够有逆向思维。
通常对树的深度优先搜索都是采取于先序遍历的方式,也就是在遍历时优先访问左子结点。在这种情况下,树每一层的最右结点可以看作为该层的最后一个结点。
但是这样的问题在于,在递归的情况下,“最后一个”是难以知悉的,当然深度优先搜索可以利用栈去完成,用栈的话可以控制到“最后一个”。
这里就需要逆向的思维,在深度优先搜索中优先访问左子结点,那么每次递归时每一层最先发现的结点即为的最左结点,这时我们反过来,优先访问右子节点即可。
以下为解题源码:
# Definition for a binary tree node.
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
def rightSideView(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
def dfs(node, depth, res):
if depth not in res:
res[depth] = node.val
if node.right is not None:
dfs(node.right, depth + 1, res)
if node.left is not None:
dfs(node.left, depth + 1, res)
if root is None:
return []
res = {}
dfs(root, 0, res)
return list(res.values())
在深度优先搜索中,对深度信息的掌握其实就是对遍历中所在结点的“位置”的掌握,而有时候就需要围绕这个“位置”的信息来进行解决问题。