微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

O(v + e) 时间复杂度广度优先搜索

如何解决O(v + e) 时间复杂度广度优先搜索

在这里查看了其他答案,但所有内容要么写得乱七八糟,要么代码的操作方式与我的不同。我正在寻找的只是一个清晰的解释,说明为什么下面描述的广度优先搜索的时间复杂度是 O(v + e)。

class Node {
  constructor(name) {
    this.name = name;
    this.children = [];
  }

  breadthFirstSearch() {
    const array = []
    const queue = [this]
        
        while(queue.length) {
            const node = queue.shift()
            array.push(node.name)
            queue.push(...node.children)
        }
        
        return array
  }
}

下面的每个顶点都是上面描述的每个 Node 类。您可以想象 breadthFirstSearch 方法被“A”节点调用

           A
        /  |  \
       B   C   D
      / \     / \
     E   F   G   H
        / \
       I   J

我理解 O(v) 部分,因为很清楚我们访问了每个节点。 O(e) 部分是我无法理解的部分。提前致谢。

解决方法

不是 e = v-1,因为除了根之外,每个顶点都连接到 1 个父节点吗?如果是这样,那么 e 项就只是迂腐,它可以重写为 O(v)。

如果您查看广度优先搜索 (https://en.wikipedia.org/wiki/Breadth-first_search) 的维基百科页面,它给出了一般图形的时间复杂度为 O(|V| + |E|),其中边数 | E|如果每对顶点都由一条边连接,则可以高达 |V|^2。在最坏的情况下,BFS 必须检查每条边。树是边数为|V|-1的图的特例。

,

在一般图上运行广度优先搜索的时间复杂度为 O(v + e),其中同一对节点之间可以存在循环和多条路径。您可能经常看到的复杂参数主要用于证明附加的“+ e”项而不是 v 项的合理性。

在树上运行 BFS 的情况下,运行时间是 O(v)。有几种方法可以看到这一点:

  1. 树中的每个节点最多接触两次:第一次是在处理其父节点时将其推入队列,第二次是将其本身从队列中取出并处理其子节点。这意味着完成的工作与树中的节点数成正比,这就是 O(v) 项的来源。
  2. 如果忽略将子节点推入队列的成本,则循环显然将在树中的每个节点最多运行一次,因为节点最多只能入队一次。这给了我们 O(v) 基线工作量,忽略推送成本。因此,我们必须计算出的部分是将所有推入队列需要多少工作。每个节点最多可以被推送一次,所以在外循环的所有迭代中,推送东西完成的总工作是 O(v),所以完成的总工作是 O(v)。
  3. 如果您相信在具有 v 个节点和 e 个边的一般图中广度优先搜索的成本是 O(v + e),那么在树上进行 BFS 的成本必须是 O(v),因为在一棵树,我们有 e = v - 1 的规则。一种看待这个的方法:除了根之外的每个节点都有一条边进入它。
,

如果图是一棵树,那么 O(?) = O(?+?),就像在树中一样,我们有等式 ? = ?−1。

如果图是连通并且有(即它的边比它的生成树多),那么 BFS 是 O(?)。这是因为必须访问每条边(即使只是意识到它连接到已经访问过的节点)。

如果图可能断开,即多个连通分量的集合,那么节点可能多于边,我们知道BFS将不得不访问所有 节点,所以 O(?) 不会覆盖它,而是 O(?)。

为了涵盖所有这些可能性,我们必须说 O(?+?) 或 O(max(?,?)),这是相同的复杂度。但如果你只对树感兴趣,你也可以说 O(?) 或 O(?),因为它们都是等价的。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。