我了解DFS和BFS之间的区别,但是我想知道什么时候使用另一种比较实用?
任何人都可以举例说明DFS将如何胜过BFS,反之亦然?
我了解DFS和BFS之间的区别,但是我想知道什么时候使用另一种比较实用?
任何人都可以举例说明DFS将如何胜过BFS,反之亦然?
Answers:
这在很大程度上取决于搜索树的结构以及解决方案(又名搜索项目)的数量和位置。
如果树很深并且解决方案很少,则深度优先搜索(DFS)可能会花费很长时间,但BFS可能会更快。
如果树很宽,则BFS可能需要太多内存,因此可能是完全不切实际的。
如果解决方案很常见但位于树的深处,则BFS可能不切实际。
但是,这些只是经验法则。您可能需要进行实验。
深度优先搜索通常用于模拟游戏(以及现实世界中类似游戏的情况)。在典型的游戏中,您可以选择几种可能的动作之一。每个选择都会导致进一步的选择,每个选择都会导致进一步的选择,依此类推成为不断扩大的可能性树形图。
例如,在象棋,井字游戏这样的游戏中,当您决定要做什么时,您可以在脑海中想象一个动作,然后是对手的可能反应,然后是反应,等等。您可以通过查看哪个步骤可以带来最佳结果来决定要做什么。
游戏树中只有一些路径才能赢得胜利。有些导致您的对手获胜,当您达到这样的结局时,您必须备份或回溯到上一个节点,然后尝试其他路径。通过这种方式,您可以探索树,直到找到成功结论的路径。然后,您将沿着此路径迈出第一步。
广度优先搜索具有一个有趣的属性:它首先查找距起点一个边缘的所有顶点,然后查找相距两个边缘的所有顶点,依此类推。如果您要查找从起始顶点到给定顶点的最短路径,这将很有用。启动BFS,然后找到指定的顶点,便知道到目前为止跟踪的路径是到该节点的最短路径。如果路径更短,BFS将会找到它。
广度优先搜索可用于在点对点网络(例如BitTorrent)中查找邻居节点,GPS系统用于查找附近位置,社交网站用于查找指定距离内的人等等。
来自http://www.programmerinterview.com/index.php/data-structures/dfs-vs-bfs/的不错的解释
BFS的一个例子
这是BFS外观的示例。这类似于“级别顺序树遍历”,在这里我们将使用“队列”和“迭代”方法(大多数“回溯”将以DFS结尾)。这些数字表示在BFS中访问节点的顺序:
在深度优先搜索中,您从树的根部开始,并尽可能沿树的一个分支移动,直到找到要查找的节点或击中叶节点(没有子节点的节点)为止。如果您命中了叶子节点,那么您将在最近的祖先中搜索未探索的孩子。
DFS的一个例子
这是DFS外观的示例。我认为二叉树中的后顺序遍历将首先从Leaf级别开始工作。这些数字表示在DFS中访问节点的顺序:
DFS和BFS之间的区别
比较BFS和DFS,DFS的最大优点是它的内存需求比BFS低得多,因为不必在每个级别存储所有子指针。根据数据和您要查找的内容,DFS或BFS可能都是有利的。
例如,给定一棵家谱,如果有人正在树上寻找一个还活着的人,那么可以安全地假设那个人在树的底部。这意味着BFS要花费很长时间才能达到最后一个级别。但是,DFS可以更快地找到目标。但是,如果要寻找一个很久以前就去世的家庭成员,那么这个人将离树顶很近。然后,BFS通常比DFS更快。因此,这两种方法的优势都取决于数据和所需的内容。
另一个例子是Facebook。对朋友之友的建议。我们需要直接的朋友提出建议,以便在哪里可以使用BFS。可能是找到最短路径或检测到循环(使用递归),我们才可以使用DFS。
DFS比BFS更节省空间,但可能会达到不必要的深度。
他们的名字揭示了:如果广度很大(即分支系数很大),但是深度非常有限(例如“动作”的数量有限),那么DFS可能比BFS更可取。
应该提到的是,有一个鲜为人知的变种,它结合了DFS的空间效率,但是(累积地)BFS的层级访问是迭代的加深深度优先搜索。该算法会重访某些节点,但它只会贡献一个渐近差异的恒定因子。
当您以程序员的身份处理此问题时,一个因素很明显:如果您使用的是递归,则深度优先搜索更易于实现,因为您不需要维护包含要探索的节点的其他数据结构。
如果要在节点中存储“已经访问过”的信息,这是对非定向图的深度优先搜索:
def dfs(origin): # DFS from origin:
origin.visited = True # Mark the origin as visited
for neighbor in origin.neighbors: # Loop over the neighbors
if not neighbor.visited: dfs(next) # Visit each neighbor if not already visited
如果将“已访问”信息存储在单独的数据结构中:
def dfs(node, visited): # DFS from origin, with already-visited set:
visited.add(node) # Mark the origin as visited
for neighbor in node.neighbors: # Loop over the neighbors
if not neighbor in visited: # If the neighbor hasn't been visited yet,
dfs(node, visited) # then visit the neighbor
dfs(origin, set())
与广度优先搜索相反,无论哪种情况,您都需要为尚未访问的节点列表维护单独的数据结构。
BFS的一个重要优点是可以用来查找未加权图中任意两个节点之间的最短路径。而我们不能将DFS用于相同的。
对于BFS,我们可以考虑Facebook示例。我们收到建议从FB个人资料中添加其他其他朋友个人资料中的朋友。假设A-> B,而B-> E和B-> F,那么A会得到E和F的建议。他们必须使用BFS读取直到第二级。DFS更基于我们要根据从源到目标的数据来预测某些内容的方案。如前所述,关于国际象棋或数独。一旦这里有所不同,我相信DFS应该用于最短路径,因为DFS首先会覆盖整个路径,然后我们才能决定最佳路径。但是由于BFS将使用贪婪的方法,因此看起来它是最短的路径,但最终结果可能会有所不同。让我知道我的理解是否错误。
有些算法取决于DFS(或BFS)的特定属性才能起作用。例如,用于查找2连接组件的Hopcroft和Tarjan算法利用了以下事实:DFS遇到的每个已经访问的节点都在从根到当前探索的节点的路径上。
简单来说:
广度优先搜索(BFS)算法从其名称“ Breadth”开始,通过节点的外边缘发现节点的所有邻居,然后通过其外边缘发现先前提到的邻居的未访问邻居,依此类推,直到所有可以访问原始来源可访问的节点(如果还有其他未访问的节点等,我们可以继续使用其他原始来源)。这就是为什么如果边缘的权重是均匀的,则可以使用它找到从一个节点(原始源)到另一个节点的最短路径(如果有)。
深度优先搜索(DFS)算法从其名称“深度”开始,通过其外边缘发现了最近发现的节点x的未访问邻居。如果没有来自节点x的未访问邻居,则该算法回溯以发现从中发现了节点x的节点的未访问邻居(通过其外部边缘),依此类推,直到访问了可从原始源到达的所有节点为止。 (如果还有其他未访问的节点等,我们可以继续并采用其他原始资源)。
BFS和DFS都可能不完整。例如,如果节点的分支因数是无限的,或者对于要支持的资源(内存)来说很大(例如,当存储下一个要发现的节点时),那么即使搜索的键距一定距离,BFS也不会完成原始来源的少数边缘。此无限分支因子可能是由于要从给定节点进行发现而进行的无限选择(相邻节点)。如果深度是无限的,或者对于要支持的资源(内存)来说很大(例如,当存储下一个要发现的节点时),则DFS不会完成,即使搜索到的键可以是原始数据源的第三个邻居。此无限深度可能是由于以下情况:对于算法发现的每个节点,至少存在一个以前未曾访问过的新选择(邻居节点)。
因此,我们可以得出何时使用BFS和DFS的结论。假设我们正在处理一个可管理的有限分支因子和一个可管理的有限深度。如果搜索到的节点较浅,即在从原始源经过某些边缘之后可以到达,则最好使用BFS。另一方面,如果搜索到的节点较深,即从原始源经过很多边缘后可以到达,则最好使用DFS。
例如,在社交网络中,如果我们要搜索与某个特定人具有相似兴趣的人,则可以将这个人的BFS用作原始来源,因为这些人大多是他的直接朋友或朋友的朋友,即一个人或两个边缘远。另一方面,如果我们要搜索与特定人的兴趣完全不同的人,则可以将此人的DFS用作原始来源,因为这些人大多与他相距很远,即朋友的朋友的朋友....即太多的边缘。
BFS和DFS的应用程序也可能会有所不同,这是因为每个搜索引擎的搜索机制不同。例如,当我们只想检查从一个节点到另一个节点的可达性而又不知道该节点的位置时,可以使用BFS(假设分支因子是可管理的)或DFS(假设深度是可管理的)。他们俩还可以解决相同的任务,例如图形的拓扑排序(如果有的话)。BFS可用于查找从一个节点(原始源)到另一个节点的最短路径(具有单位权重边缘)。鉴于DFS可以深入研究,因此可以用来穷尽所有选择,例如发现非循环图中两个节点之间的最长路径。DFS还可用于图形中的循环检测。
最后,如果我们具有无限的深度和无限的分支因子,则可以使用迭代加深搜索(IDS)。
这是一个很好的例子,可以证明BFS在某些情况下比DFS更好。https://leetcode.com/problems/01-matrix/
正确实施后,两种解决方案都应访问距离比当前单元+1更远的单元。但是DFS效率低下,并且反复访问同一单元,从而导致O(n * n)复杂性。
例如,
1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,
0,0,0,0,0,0,0,0,