一 简介
1.1 说明
此算法与Floyd和Brent算法一样,都是寻找由函数F(X)产生的序列S中是否存在循环,而且多用在非循环序列比循环序列长的序列中。上面提到的两个算法已经有很多人科普,唯独这个算法没有人科普,我就献丑了。
与前面所提到的两个算法不同,此算法需要维护一个堆栈结构Stack,该栈结构存储着目前序列出现的最小点(distinguished points),而这些点很可能会出现在循环里。当然,事关效率与资源问题,这些点不能多也不能少。为了增加效率,Stack中的序列必须有序(递增),才能使用二分查找算法进行比对以减少时间复杂度。
1.2 结论
假设非循环序列长度为s,循环序列为l。
- 算法发现碰撞时,相遇点(不是碰撞点)在第一轮循环之后,范围在[s+l,s+2l-1];
- 内存消耗很少;
- 该算法找碰撞点与计算s和l的大小都比前面提到的两个算法简单且快速。
二 算法描述
2.1 文字描述
- 初始化栈S,并将第一个产生的X0以(X0, 0)的形式放到栈里。
- 生成Xi,并在S中进行二分搜索,若没有发现Xi则进行3,若发现已存在Xi则进行4。
- 将S中值大于Xi的值剔除,并添加(Xi, i)入S,重复2。
- 发现已存在点(Xi, j)。发现存在循环,算法结束。
2.2 伪代码
Require: Input initial sequence value X0, max. iterations M
Create empty stack S
Let x ←− X0
Add pair (x, 0) to the stack
for i from 1 to M do
Let x ←− F(x)Search x by dichotomy in the (sorted) first component
if (x, j) is found then
Output ‘Collision between i and j’
Exit
else
We know that Sk < x < Sk+1
Truncate S after Sk
Add pair (x, i) at the end of S
end if
end for
Output Failed
2.3 计算非循环序列长度s以及碰撞点
已发现(Xi, j)存在在S中,则Xi则是S中从左到右第一个在循环里面的点,记录该点a,则只需遍历a~j即可到达碰撞点,得到s。
2.4 计算循环序列长度l
不像上面提到的两个算法,这里的i-j=t,t=l,而不是l的倍数关系!(i为后Xi,j为前面的Xi)
2.5 例子
假设函数F产生的序列为:
1 4 2 5 6 3 6
no.(i) | S | F(x) | 是否能在S中找到F(X) |
---|---|---|---|
1 | <(1,0)> | 4 | no |
2 | <(1,0),(4,1)> | 2 | no |
3 | <(1,0),(2,2)> | 5 | no |
4 | <(1,0),(2,2),(5,3)> | 6 | no |
5 | <(1,0),(2,2),(5,3),(6,4)> | 3 | no |
6 | <(1,0),(2,2),(3,5)> | 6 | no |
7 | <(1,0),(2,2),(3,5),(6,6)> | 3 | yes |
得到:(3,7)与(3,5)相遇,则环的长度l=7-5=2,碰撞点(环开始的地方)在2~5之间。