【问题标题】:Why is depth-first search claimed to be space efficient?为什么深度优先搜索声称空间有效?
【发布时间】:2013-12-24 02:58:09
【问题描述】:

在我正在学习的算法课程中,据说深度优先搜索 (DFS) 比广度优先搜索 (BFS) 更节省空间。

这是为什么呢?

虽然它们基本上做同样的事情,但在 DFS 中,我们堆叠当前节点的后继节点,而在 BFS 中,我们将后继节点加入队列。

【问题讨论】:

    标签: algorithm graph-algorithm breadth-first-search depth-first-search


    【解决方案1】:

    您的困惑源于您显然假设可以通过将 FIFO 队列替换为 LIFO 堆栈从 BFS 算法中获得 DFS 算法。

    这是一个普遍的误解——它根本不是真的。经典的 DFS 算法无法通过将 BFS 队列替换为堆栈来获得。这些算法之间的差异更为显着。

    如果您采用 BFS 算法并简单地将 FIFO 队列替换为 LIFO 堆栈,您将获得可以称为 pseudo-DFS 算法的东西。这种伪 DFS 算法确实会正确复现 DFS 顶点前向遍历序列,但不会有 DFS 空间效率,也不支持 DFS 后向遍历(回溯)。

    同时,通过这种幼稚的队列到堆栈替换,无法从 BFS 获得真正经典的 DFS。经典的 DFS 是一种完全不同的算法,具有明显不同的核心结构。真正的 DFS 是一种真正的递归 算法,它使用堆栈回溯 目的,而不是用于存储顶点发现“前端”(如 BFS 中的情况)。最直接的结果是,在 DFS 算法中,最大堆栈深度等于在 DFS 遍历中与原点顶点的最大距离。在 BFS 算法中(如在前面提到的伪 DFS 中),最大队列大小等于最大顶点发现前沿的宽度。

    说明 DFS 和 BFS(以及伪 DFS)之间的峰值内存消耗差异的最突出和极端的示例是星图:单个中心顶点被大量数字包围(例如,1000 ) 的外围顶点,每个外围顶点通过边连接到中心顶点。如果你在这个图上使用中心顶点作为原点运行 BFS,队列大小将立即跳转到1000。如果您使用伪 DFS(即,如果您只是用堆栈替换队列),同样的事情显然会发生。但是经典的 DFS 算法只需要 1 (!) 的堆栈深度来遍历整个图。看到不同? 10001。这就是 DFS 更好的空间效率的意思。

    基本上,找一本关于算法的书,找到对经典 DFS 的描述,看看它是如何工作的。您会注意到,BFS 和 DFS 之间的区别远比单纯的队列与堆栈的区别要广泛得多。

    附:还应该说,可以构建一个在 BFS 下峰值内存消耗较小的图示例。因此,关于 DFS 更好的空间效率的陈述应该被视为可能“平均”适用于某些隐含的“漂亮”图类的东西。

    【讨论】:

    • 值得注意的一点 - 伪 DFS 应该为您提供 O(depth * branching factor) 空间,而不是 O(depth) 以获得正确的 DFS,这仍然比 O(width) 更好。似乎您的回答说了类似的话,尽管我认为这更简洁明了。
    • 请注意,虽然真正的 DFS 通常使用递归更好地实现,但这并不是真正的 DFS 的必要特征。您当然可以在没有任何递归的情况下实现真正的 DFS。区分真正的 DFS 和所谓的伪 DFS 的定义特征是它们如何使用堆栈。真正的 DFS 使用它来存储回溯信息,而伪 DFS 使用堆栈来存储顶点发现前端。
    • @Lie Ryan:我不想将算法本身和算法的实现混为一谈。当我说“递归”时,指的是算法递归,而不是语言递归。 IE。递归是指任何基于分治策略并使用非常量大小堆栈 (LIFO) 来存储推迟任务的算法。这种算法本质上是“递归的”,无论是通过语言递归还是手动堆栈实现。
    • 递归仍在使用堆栈,即使它不是显式的......(如果您的语言需要,也可以使用堆)
    • 根据人工智能 - 现代方法,DFS 实际上是具有 LIFO 队列而不是 FIFO 队列的 BFS...您所描述的算法称为回溯搜索。
    【解决方案2】:

    在 DFS 中,您只需要与完全平衡树上的深度 O(log(n)) 线性相关的空间,而 BFS(广度优先搜索)需要 O(n)(树的最宽部分是二叉树中的最低深度树 n/2 个节点)。

    例子:

                   1
                  / \  
                 /   \  
                /     \ 
               /       \
              /         \  
             /           \  
            /             \ 
           /               \
           2               2 
          / \             / \ 
         /   \           /   \  
        /     \         /     \  
       /       \       /       \  
       3       3       3       3
      / \     / \     / \     / \ 
     /   \   /   \   /   \   /   \  
     4   4   4   4   4   4   4   4
    / \ / \ / \ / \ / \ / \ / \ / \ 
    5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 
    

    DFS 需要空间:4
    BFS 需要在倒数第二行空间 8

    如果分支因子更高,情况会变得更糟

    【讨论】:

    • 对于BFS,你也需要把最后一行的每个元素都存储起来(因为在访问完倒数第二行的所有元素后,队列会包含最后一行的所有元素),所以它将是 16,而不是 8。如果您事先知道树的最大深度,您可能可以优化它,但在标准实现中不会发生。
    • @Dukeling:取决于您的实施。如果您存储仍然要访问的节点或您已经访问过的节点但不存储它们的子节点。但在O-Notation O(n/2) = O(n/4) = O(n) 中没有区别。
    • "你访问过的节点,但没有访问过它们的子节点" - 我认为我以前从未见过这样做,但它确实非常有意义(实际上,比替代)。
    【解决方案3】:

    在 DFS 中,使用的空间是 O(h),其中 h 是树的高度。

    在 BFS 中,使用的空间为 O(w),其中 w 是树的“宽度”。

    在典型的二叉树(即随机二叉树)中,w = Omega(n) 和 h = O(sqrt(n))。

    在平衡树中,w = Omega(n) 和 h = O(log n)。

    【讨论】:

    • 除此之外,另请参阅this 比较 DFS 和 BFS 的答案。
    • this Q&A 建议随机二叉树的预期高度为 O(log n),而不是 O(sqrt(n))。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-28
    • 2011-01-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多