【问题标题】:What is the worst case time complexity of this rudimentary graph search?这个基本图搜索的最坏情况时间复杂度是多少?
【发布时间】:2013-04-30 18:25:05
【问题描述】:

前几天我被要求编写一个基本方法,当给定两个朋友时,如果他们被链接(我的意思是他们是朋友的朋友的朋友),则返回 true,如果他们是朋友,则返回 false不相关(他们的任何一个朋友列表中都没有关联)。

在和这个人合作之后,这是我们得出的基本算法:

def is_linked(friend_a, friend_b, visited = {}):

  is_found = False
  friends = friend_a.get_friends()

  # You can assume this method is O(1)
  if friends.contains(friend_b):
    return True

  visited[friend_a] = 1
  for friend in friends:

    # Prevents infinite recursion
    # Assume O(1)
    if friend in visited:
      next

    is_found = is_linked(friend, friend_b, visited)
    if is_found:
      last

  del visited[friend_a]
  return is_found

这个算法的最坏情况时间复杂度是多少?我们每个人都想出了自己的答案,由于我们无法达成协议,我希望在这里独立验证。

【问题讨论】:

  • 在我看来O(n^2)
  • @AaronMiller - 您介意将其放入带有解释的答案中吗?
  • 你在if friend in visited 上花了多少钱?如果O(1),整个事情就是O(n)。否则,我认为 Aaron 是对的。
  • @dlp - 假设 if friend in visited 的 O(1)。话虽如此,您能否发布 O(n) 的推理作为答案?老实说,即使 if 语句为 O(1),我也在想 O(n^2),所以我很想知道为什么。
  • 其实我收回了。我不知道。我的图论很弱,但这似乎是Breadth-first search 的变体,不是吗?正如它所说的那样,这真的让你在O(n^2)

标签: python complexity-theory


【解决方案1】:

我要把我的答案扔在那里。我认为 Wikipedia 上的 Breadth-first search 文章涵盖了该文章,该文章表明它确实取决于边数 E 和节点 N,并且具有不同的最佳和最坏情况值。您的代码带有注释的关键区域

def is_linked(friend_a, friend_b, visited = {}): # At worst, will call this N-1 times

    friends = friend_a.get_friends()

    # You can assume this method is O(1)
    if friends.contains(friend_b):
        return True

    visited[friend_a] = 1
    for friend in friends:

        # Prevents infinite recursion
        # Assume O(1)
        if friend in visited:
            next # At worst, this will happen N-2 times

        return is_linked(friend, friend_b, visited)

    del visited[friend_a]
    return False

绝对最坏的情况? O((N-1)(N-2)) 真的是 O(N^2)。如果你可以说你的图是稀疏连接的,你可以得到和O(N) 一样好,因为if friend in visited: next 很少发生。

【讨论】:

    【解决方案2】:

    如果if friend in visitedfriends.contains都是O(1),那么函数作为一个整体一定是O(n);如果对is_linked 的单个调用(假设没有递归)在恒定时间内完成(如果它所做的一切都在恒定时间内完成,则必须如此),那么递归调用的完成时间与发生的递归次数成线性关系,即,必须检查才能找到链接的朋友数量。我没有能力对此进行数学证明,但请考虑:

    a[friends] = [c, d, e]
    b[friends] = [e, f, g]
    [...]
    e[friends] = [a, b]
    

    在这种情况下,调用is_linked(a, b) 将导致总共四个调用:

    is_linked(a, b) # 原始调用 is_linked(c, b, {a: 1}) is_linked(d, b, {a: 1, c: 1}) is_linked(e, b, {a: 1, c: 1, d: 1}) # 真

    同样:

    a[friends] = [b, c]
    b[friends] = [c, d]
    c[friends] = [d, e]
    d[friends] = [e]
    

    调用is_linked(a, e) 也会导致四个调用:

    is_linked(a, e) # original call
    is_linked(b, e, {a: 1})
    is_linked(c, e, {a: 1, b: 1})
    is_linked(d, e, {a: 1, b: 1, c: 1}) # True
    

    同时:

    a[friends] = [b, c, d, e, f, g]
    [...]
    g[friends] = []
    

    is_linked(a, g) 会导致六个调用,&c。这一切的目的是证明,如果不是证明,在is_linked 花费的时间与它必须检查的项目数量成线性比例。

    我对 Python 的字典键查找实现不是很熟悉 (if friend in visited);一个朴素的实现,作为一个跨键数组的线性搜索,将具有 O(n) 复杂度,导致我最初为 is_linked 假设的 O(n^2) 复杂度。但是,根据this,他们将其归结为“O(1) 的平均复杂度”。如果这是真的,那么is_linked 确实是 O(n);如果我怀疑可能是这种情况,实际上它更接近 O(log n),那么is_linked 应该是 O(n log n),这还不算太糟糕。

    更新:dlp指出is_linked中有一个数组遍历(for friend in friends),还有一个key查找。该迭代总是 O(n),这将使is_linked 至少必然为 O(n^2)。 (可以是 O(n^2 log n) 吗?家里有计算机科学家吗?我的意思是,我不是……)

    【讨论】:

    • 我也是这么想的,但是for friend in friends 也可以与n 一起增长。这意味着当您浏览图表时,每个递归调用都可以让内部循环在返回之前迭代多达n-1 次。不过我可能错了。
    • 哦,废话,你说得对,in 是数组遍历,而另一个是键查找。血腥蟒蛇。
    • visited 的使用将有助于修剪该循环,但在最坏的情况下(例如琐碎的a[friends] = [g, h, i, j, k, l, b]; b[friends] = []; is_linked(a, b)n-1 迭代仍然需要,所以,是的,O(n)。
    • (而且,顺便说一句,这就是我喜欢 Perl 的原因:数组遍历是 for(;;) 和 C 中一样,或者 foreach my $item (@array),而键查找是 defined($hash{'key'})。也许语法更冗长,但是例如,当您遇到 in 时,您不会绊倒自己,这意味着在尽可能多的代码行空间中是两个完全不同的东西。)
    • @rdp,我同意in 本身并不意味着阵列扫描。 for element in iterable 表示扫描,在本例中代码为 for friend in friends:
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多