【问题标题】:How would I find an infinite loop in an array of pointers?如何在指针数组中找到无限循环?
【发布时间】:2013-04-12 03:44:24
【问题描述】:

我有一个指针数组(这是算法,所以不要讨论语言细节)。大多数时候,这个数组指向数组之外的位置,但它会降级为数组中的每个指针都指向数组中的另一个指针。最终,这些指针形成了一个无限循环。

所以假设整个数组由指向数组中另一个位置的指针组成并且你从头开始,你怎么能找到在时间和空间上效率最高的循环长度?我相信最好的时间效率是 O(n),因为你必须遍历数组,而最好的空间效率是 O(1),虽然我不知道如何实现。

Index:  0  1  2  3  4  5  6
Value: *3 *2 *5 *4 *1 *1 *D

D 是在循环开始之前被指向的数据。在此示例中,循环为 1、2、5,它无限重复,但索引 0、3 和 4 不是循环的一部分。

【问题讨论】:

  • 难道你不只是遍历指针,当你点击一个与你的起始指针指向的元素相同的元素时,结束搜索并计算你遍历的元素数量吗?
  • 这些点是改变了还是从一开始就固定了?
  • 总是有一个“循环”吗?会不会是零?可能不止一个吗?如果不止一个,您需要找到全部还是只找到一个?
  • 不@Calpis,因为如果前两个或三个元素不是循环的一部分,但接下来的两个是?我将在我的问题中添加一个示例。
  • @MitchBukaner 我们将能够扫描阵列而无需担心更改。

标签: arrays algorithm pointers big-o infinite-loop


【解决方案1】:

这是循环检测问题的一个实例。 Robert W. Floyd 在 1960 年发现了一个优雅的O(n) time O(1) 空间解决方案;它通常被称为"Tortoise and Hare" 算法,因为它包括使用两个指针遍历序列,一个移动速度是另一个的两倍。

这个想法很简单:循环必须有一个长度为k的循环,对于一些k。在每次迭代中,兔子移动两步,乌龟移动一步,因此它们之间的距离比前一次迭代大一。因此,每一次k 迭代,它们都是k 步数的倍数,并且一旦它们都在循环中(一旦乌龟到达就会发生),如果它们是k 步数的倍数分开,它们都指向同一个元素。

如果你只需要知道周期的长度,你就等着兔子和乌龟到达同一个地方;然后你沿着循环走,数着步数,直到你再次回到同一个地方。在最坏的情况下,总步数将是尾部的长度加上循环长度的两倍,它必须小于元素数量的两倍。

注意:第二段经过编辑,可能使这个想法“更明显”,无论这意味着什么。正式证明很容易,实现也很容易,所以我都没有提供。

【讨论】:

  • 我实际上并不认为 Tortoise and Hare 的证明像 wiki 页面看起来那样微不足道(在数学/CS 的范围内微不足道,是的。在介绍类的范围内,不)。我也不明白为什么你的段落证明它们必须在k 步骤之后指向同一个元素(或者至少你需要写出表达式来显示它)。我用dist = n + (k - (n % k)) = yk 之类的东西向自己证明了一些整数y。算法的下一步需要类似的简短证明。
  • 我也可以在没有正式证明的情况下从视觉上证明它(展开兔子走过的路径,然后一遍又一遍地滚动,直到龟兔赛跑);但是您给出的解释确实使它们看起来像是从周期开始(这不是真的)或其他什么……老实说,我没有从中得到任何东西。至于这个笑话,我实际上参加了一套理论课程,教授实际上每次都证明了这一点。你会问他任何有一个众所周知的解决方案的随机集合论问题,他会立即把它背给你。真是可笑的东西。
  • 啊,当然。但是一旦乌龟到达循环,它们就不需要多个k 步骤来相交。事实上,它们相交的步骤总是少于k。那么您声称它们在多个k 步骤后相交有什么意义?
  • 我现在不知道你是否在拖钓。您声称如果尾部长度是 k 的倍数,那么一旦它们都到达循环,它们就会相交。真的。不确定它是如何相关的。然后你说了一些关于“单向确定性路径”以及它们如何达到循环的内容。凉爽的。然后你松散地说,“嗯,你知道,其余的对我来说很明显,YMMV”。这些都不等同于您刚刚提出的主张。你对“显而易见的事情”的正式证明的居高临下的态度是非常愚蠢的,我认为对 OP(或任何人?)根本没有好处。
  • 维基链接已经足够了,它有一个代码示例,谢谢@rici。
【解决方案2】:

制作数组中元素的有向图,其中一个节点指向另一个节点,如果该节点的元素指向其指向的节点的元素,并且对于每个节点。跟踪节点的入度(指向它的指针数。)在制作图表时,如果有一个入度 == 2 的节点,则该节点是无限循环的一部分。

如果第一个元素包含在无限循环中,则上述方法失败,因此在算法开始之前,将第一个元素加 1 indegree 以解决此问题。

【讨论】:

    【解决方案3】:

    正如您所描述的那样,该数组变成了一个图(更准确地说是福雷斯特),其中每个顶点的出度正好为 1。这种图的组件只能由链组成,每个链都可能以单个循环结束。也就是说,每个组件的形状要么像 O,要么像 6。(我假设没有指针为空,但这很容易处理。你最终会得到完全没有循环的 1 形组件。)

    您可以通过“访问”来跟踪所有这些组件,并使用“已访问”哈希或标志数组跟踪您去过的地方。

    这是一个算法。

    编辑 对于每个节点一个子节点的情况,它只是简化了 forrest 的 DFS,这消除了对堆栈(或递归)的需要,因为不需要回溯。

    Let A[0..N-1] be the array of pointers.
    Let V[0..N-1] be an array of boolean "visited" flags, initially false.
    Let C[0..N-1] be an array if integer counts, initially zero.
    Let S[0..N-1] be an array of "step counts" for each component trace.
    longest = 0  // length of longest cycle
    for i in 0..N-1, increment C[j] if A[i] points to A[j]
    for each k such that C[k] = 0 
      // No "in edges", so must be the start of a 6-shaped component
      s = 0
      while V[k] is false
        V[k] = true
        S[k] = s
        s = s + 1
        k index of the array location that A[k] points to
      end
      // Loop found. Length is s - S[k]
      longest = max(longest, s - S[k])
    end
    // Rest of loops must be of the O variety
    while there exists V[k] false
      Let k be such that V[k] is false.
      s = 0
      while V[k] is false
        V[k] = true
        s = s + 1
        k index of the array location that A[k] points to
      end
      // Found loop of length s
      longest = max(longest, s)
    end      
    

    空间和执行时间都与输入数组A 的大小成正比。如果您愿意跟踪 6 形组件两次,则可以摆脱 S 数组。

    加法我完全同意,如果不需要找到最大尺寸的循环,那么the ancient "two pointer" algorithm for finding cycles in a linked list 更好,因为它只需要恒定的空间。

    【讨论】:

    • 他实际上并没有指定他想要最长的周期。
    • 时间和空间的大O是什么?粗略一看,时间上是 O(n^2),空间上是 O(n^2),效率极低。
    • 不是。嵌套循环不会自动表示二次行为。正如我在文章中所说,“空间和执行时间都与输入数组 A 的大小成正比”。我想这就是你所说的 O(n) 虽然你没有说 n 是什么。该算法只是将 forrest 的 DFS 简化为每个节点只有一个子节点的情况,这意味着它不需要堆栈,因为它永远不需要回溯。
    猜你喜欢
    • 1970-01-01
    • 2013-06-26
    • 1970-01-01
    • 1970-01-01
    • 2018-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-24
    相关资源
    最近更新 更多