从正确性的角度来看,没有理由需要使用第二个。步长的任何选择都可以(当然,除了一个)。但是,选择大小为 2 的步长可以最大限度地提高效率。
为了看到这一点,让我们先来看看为什么弗洛伊德的算法会起作用。这个想法是考虑链表元素的序列 x 0 , x 1 , x 2 , ..., x n , ... 如果您从链表的开头开始访问这些元素,然后继续走下去,直到你到达终点。如果列表不包含循环,则所有这些值都是不同的。但是,如果它确实包含一个循环,那么这个序列将无休止地重复。
这是使弗洛伊德算法起作用的定理:
当且仅当存在一个正整数 j 使得对于任何正整数 k,x j = x jk时,链表才包含一个循环。
让我们证明这一点;这并不难。对于“如果”的情况,如果存在这样的 j,则选择 k = 2。那么对于某些正数 j,x j = x 2j和 j ≠ 2j,因此列表包含一个循环。
对于另一个方向,假设列表包含从位置 s 开始的长度为 l 的循环。令 j 是 l 大于 s 的最小倍数。那么对于任意k,如果我们考虑x j和x jk,由于j 是循环长度的倍数,我们可以认为x jk是从列表中的位置j 开始,然后走j 步k-1 形成的元素次。但是每次你走 j 步,你就会回到你在列表中开始的地方,因为 j 是循环长度的倍数。因此,x j = x jk。
这个证明向您保证,如果您在每次迭代中采取任何恒定数量的步数,您确实会碰到慢速指针。更准确地说,如果您在每次迭代中采取 k 步,那么您最终会找到点 x j和 x kj并检测到循环。直觉上,人们倾向于选择 k = 2 来最小化运行时间,因为您在每次迭代中采取的步数最少。
我们可以更正式地分析运行时如下。如果列表不包含循环,则快速指针将在 n 步后到达列表末尾 O(n) 时间,其中 n 是列表中元素的数量。否则,两个指针会在慢速指针经过 j 步后相遇。请记住,j 是 l 大于 s 的最小倍数。如果 s ≤ l,则 j = l;否则,如果 s > l,则 j 最多为 2s,因此 j 的值为 O(s + l)。由于 l 和 s 不能大于列表中元素的数量,这意味着 j = O(n)。然而,在慢速指针已经走了 j 步之后,对于慢速指针所走的每 j 步,快指针将走 k 步,因此它将走 O(kj) 步。由于 j = O(n),净运行时间最多为 O(nk)。请注意,这表示我们使用快速指针执行的步骤越多,算法完成所需的时间就越长(尽管只是成比例地如此)。因此,选择 k = 2 可以最小化算法的整体运行时间。
希望这可以帮助!