回溯和深度优先搜索有什么区别?

时间:2009-08-18 15:40:27

标签: algorithm

回溯和深度优先搜索有什么区别?

14 个答案:

答案 0 :(得分:80)

Backtracking是一种更通用的算法。

Depth-First search是与搜索树结构相关的特定回溯形式。来自维基百科:

  

一个从根开始(在图形情况下选择一个节点作为根)并在回溯之前尽可能地沿着每个分支进行探索。

它使用回溯作为其处理树的方式的一部分,但仅限于树结构。

但是,回溯可以用于任何类型的结构,其中可以消除部分域 - 无论它是否是逻辑树。 Wiki示例使用棋盘和特定问题 - 您可以查看特定的移动,消除它,然后回溯到下一个可能的移动,消除它等等。

答案 1 :(得分:18)

我认为this answer对另一个相关问题提供了更多见解。

对我来说,回溯和DFS之间的区别在于回溯处理隐式树而DFS处理显式树。这似乎微不足道,但它意味着很多。当通过回溯访问问题的搜索空间时,隐式树在其中间被遍历和修剪。然而对于DFS来说,它处理的树/图是明确构造的,并且在任何搜索完成之前已经抛弃了不可接受的情况,即修剪掉了。

因此,回溯是隐式树的DFS,而DFS是回溯而不修剪。

答案 2 :(得分:3)

通常,深度优先搜索是迭代查找值的实际图形/树结构的一种方式,而回溯是在寻找解决方案的问题空间中迭代。回溯是一种更通用的算法,它甚至不一定与树有关。

答案 3 :(得分:2)

我想说,DFS是回溯的特殊形式;回溯是DFS的一般形式。

如果我们将DFS扩展到一般问题,我们可以将其称为回溯。 如果我们使用回溯到树/图相关的问题,我们可以称之为DFS。

他们在算法方面具有相同的想法。

答案 4 :(得分:1)

深度优先搜索中,您从树的根开始,然后沿着每个分支进行探索,然后回溯到每个后续父节点,遍历它的孩子

回溯是一个广义术语,用于从目标结束开始,逐步向后移动,逐步构建解决方案。

答案 5 :(得分:1)

深度优先是用于遍历或搜索树的算法。见here。回溯是一个更广泛的术语,无论何时形成解决方案候选者并随后通过回溯到前状态而被丢弃。见here。深度优先搜索使用回溯来首先搜索分支(解决方案候选者),如果不成功则搜索其他分支。

答案 6 :(得分:1)

根据Donald Knuth的说法,是相同的。 这是他的论文中有关“跳舞链接”算法的链接,该链接用于解决N-queens和Sudoku求解器等“非树”问题。

  

Backtracking, also called depth-first search

答案 7 :(得分:0)

IMO,在回溯的任何特定节点上,您尝试深入分支到其每个子节点,但在分支到任何子节点之前,您需要“消灭”前一个孩子的状态(此步骤是相当于后退到父节点)。换句话说,每个兄弟姐妹的状态都不应该相互影响。

相反,在正常的DFS算法中,您通常不会有此约束,您不需要消除(回溯)先前的兄弟姐妹状态以构建下一个兄弟节点。

答案 8 :(得分:0)

回溯通常实现为DFS加搜索修剪。您可以遍历搜索空间树深度优先构建部分解决方案。暴力DFS可以构建所有搜索结果,甚至是那些实际上没有意义的搜索结果。构建所有解(n!或2 ^ n)也是非常低效的。因此,实际上,当您执行DFS时,您还需要修剪部分解决方案,这些解决方案在实际任务的上下文中没有意义,并且关注部分解决方案,这可以导致有效的最优解决方案。这是实际的回溯技术 - 您尽可能早地丢弃部分解决方案,退后一步并尝试再次寻找局部最优解。

使用BFS遍历搜索空间树并沿途执行回溯策略没有什么停止,但在实践中它没有意义,因为你需要在队列中逐层存储搜索状态,并且树宽度增长指数到高度,所以我们会很快浪费很多空间。这就是为什么通常使用DFS遍历树的原因。在这种情况下,搜索状态存储在堆栈中(调用堆栈或显式结构),它不能超过树高。

答案 9 :(得分:0)

想法-从任意点开始,检查其是否是所需的终结点,如果是,那么我们找到了解决方案,否则转到所有下一个可能的位置,如果不能继续执行,则返回到先前的位置并寻找其他替代方法标记当前路径不会导致我们找到解决方案。

现在,回溯和DFS是2种不同的名称,它们分别应用于2种不同的抽象数据类型。

如果该想法应用于矩阵数据结构,我们称之为回溯。

如果将相同的想法应用于树或图,则我们将其称为DFS。

这里的陈词滥调是可以将矩阵转换为图,并将图转换为矩阵。因此,我们实际上采用了这个想法。如果在图形上,则称为DFS;在矩阵上,则称为回溯。

两种算法的思想都是相同的。

答案 10 :(得分:0)

DFS描述了您想要浏览或遍历图形的方式。它着重于在选择后尽可能深入的概念。

回溯通常通过DFS进行,但更多地集中在尽可能早地修剪无用的搜索子空间的概念上。

答案 11 :(得分:0)

恕我直言,大多数答案都不太准确和/或没有任何可验证的参考。因此,让我分享一个非常清晰的解释与参考

首先,DFS是一种通用的图形遍历(和搜索)算法。因此它可以应用于任何图形(甚至森林)。树是一种特殊的图,因此DFS也适用于树。从本质上讲,让我们停止说它仅适用于树木或类似树木。

基于[1],回溯是一种特殊的DFS,主要用于节省空间(内存)。我要提及的区别似乎令人困惑,因为在这种Graph算法中,我们习惯于使用邻接列表表示并使用迭代模式来访问所有直接邻居(对于树,是直接子代< / em>),我们通常会忽略 get_all_immediate_neighbors 的错误实现可能会导致底层算法在内存使用上有所不同。

此外,如果图节点的分支因子为b,直径为h(对于一棵树,这是树的高度),如果我们在访问节点的每一步都存储所有直接邻居,则内存要求将为 big-O(bh)。但是,如果我们一次只接受一个(立即)邻居,然后对其进行扩展,则内存复杂度将降低为 big-O(h)前一种实现称为 DFS ,后一种实现称为 Backtracking

现在您将看到,如果您使用的是高级语言,则很可能实际上是在以DFS的名义使用回溯。此外,针对非常大的问题集跟踪访问的节点可能确实会占用大量内存。要求仔细设计 get_all_immediate_neighbors (或可以处理节点重新访问而无需陷入无限循环的算法)。

[1] Stuart Russell和Peter Norvig,《人工智能:现代方法》,第三版

答案 12 :(得分:0)

回溯只是具有特定终止条件的深度优先搜索。

考虑走过一个迷宫,在每个迷宫中做出决定的地方,该决定就是对调用堆栈的调用(执行深度优先搜索)...如果到达终点,则可以返回路径。但是,如果达到死胡同,则想退出某个决定,实质上就是退出调用堆栈中的函数。

因此,当我想到回溯时,我会在意

  1. 状态
  2. 决定
  3. 基本案例(终止条件)

我在回溯here的视频中对此进行了解释。

以下是回溯代码的分析。在此回溯代码中,我希望所有将导致一定总和或目标的组合。因此,我有3个决策调用到我的调用堆栈,在每个决策中,我都可以选择一个号码作为到达目标num的路径的一部分,跳过该号码,或者重新选择它。然后,如果我达到终止条件,我的回溯步骤就是返回。返回是回溯步骤,因为它退出了调用堆栈中的该调用。

class Solution:    

"""

Approach: Backtracking 

State
    -candidates 
    -index 
    -target 

Decisions
    -pick one --> call func changing state: index + 1, target - candidates[index], path + [candidates[index]]
    -pick one again --> call func changing state: index, target - candidates[index], path + [candidates[index]]
    -skip one --> call func changing state: index + 1, target, path

Base Cases (Termination Conditions)
    -if target == 0 and path not in ret
        append path to ret
    -if target < 0: 
        return # backtrack 

"""

def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
    """
    @desc find all unique combos summing to target
    @args
        @arg1 candidates, list of ints
        @arg2 target, an int
    @ret ret, list of lists 
    """
    if not candidates or min(candidates) > target: return []

    ret = []
    self.dfs(candidates, 0, target, [], ret)
    return ret 

def dfs(self, nums, index, target, path, ret):
    if target == 0 and path not in ret: 
        ret.append(path)
        return #backtracking 
    elif target < 0 or index >= len(nums): 
        return #backtracking 


    # for i in range(index, len(nums)): 
    #     self.dfs(nums, i, target-nums[i], path+[nums[i]], ret)

    pick_one = self.dfs(nums, index + 1, target - nums[index], path + [nums[index]], ret)
    pick_one_again = self.dfs(nums, index, target - nums[index], path + [nums[index]], ret)
    skip_one = self.dfs(nums, index + 1, target, path, ret)

答案 13 :(得分:0)

在我看来,区别在于树木的修剪。回溯可以通过检查给定的条件(如果不满足条件)来停止(完成)搜索中间的某个分支。但是,在DFS中,你必须到达分支的叶子节点才能判断是否满足条件,所以你不能停止搜索某个分支,直到到达它的叶子节点。