如果循环的次数很少,这是一种算法,它将占用较少的空间,但终止时间会更长。
[编辑]我之前的运行时分析忽略了确定我们访问的节点是否在先前采样的节点中的关键成本;该答案已经过一些修改以纠正此问题。
我们再次遍历S的所有元素。在我们探讨的元素的轨道小号 ∈ 小号,我们从我们访问过的节点样,为了能够检查,如果我们再次遇到他们。我们还维护了以前访问过的“组成部分”(即以共同周期终止(因此等于周期)的轨道的并集)的样本列表。
初始化组件的空列表complist
。每个组件由该组件的样本集合表示;我们还维护了一个搜索树samples
,其中存储了所有被选作某个组件或其他组件样本的元素。令G为一个整数序列,最高可达n,可以通过计算一些布尔谓词有效地确定其隶属度;例如,2次或完美权力p 个某些整数幂p。对于每个小号 ∈ 小号,做到以下几点:
- 如果s在中
samples
,请跳至步骤#5。
- 初始化一个空列表
cursample
,一个迭代器j ←f(s)和一个计数器t ←1。
- 虽然Ĵ是不是在
samples
:
-如果牛逼 ∈ 摹,插入Ĵ到两个cursample
及samples
。
—递增t并设置j ← f(j)。
- 检查j是否在中
cursample
。如果不是,则我们遇到了先前探讨过的组件:我们检查j属于哪个组件,并将的所有元素插入cursample
到的适当元素中complist
以进行扩充。否则,我们会重新遇到当前轨道上的某个元素,这意味着我们至少经过了一个循环,而没有遇到以前发现的循环的任何代表:将cursample
作为新发现的分量的样本集合插入到中complist
。
- 继续到下一个元素小号 ∈ 小号。
对于n = | S |,令X(n)是描述预期循环数的单调递增函数(例如 X(n) = n 1/3),令Y(n) = y(n) log(n)∈Ω(X(n) log(n))是确定内存使用目标的单调递增函数(例如 y(n) = n 1/2)。我们需要y(n) ∈Ω(X(n)),因为它至少需要X(n) log(n)空间来存储每个分量的一个样本。
我们采样的轨道元素越多,我们越有可能在轨道末端的周期中快速选择一个样本,从而快速检测到该周期。从渐近论的角度来看,获得尽可能多的样本是有意义的:我们的内存范围允许:我们最好将G设置为具有小于n的期望y(n)元素。—如果期望S中轨道的最大长度为L,则可以让G为L / y(n)的整数倍。—如果没有期望的长度,我们可以简单地每n / y(n)采样一次
元素; 在任何情况下,这都是样本之间间隔的上限。
如果在寻找新组件时,我们开始遍历先前访问过的S元素(从正在发现的新组件或已经找到其终端循环的旧组件开始),则最多需要n / y( n)迭代以遇到先前采样的元素;这就是次数的上限,对于每次尝试寻找新组件的操作,我们都会遍历冗余节点。因为我们使Ñ这样的尝试中,我们将然后冗余访问的元素小号至多Ñ 2 / Y(N)中总次数。
测试成员资格所需的工作samples
为O(y(n) log y(n)),我们在每次访问时都会重复进行此工作:此检查的累积成本为O(n 2 log y(n))。将样本添加到其各自的集合中也存在成本,其累积值为O(y(n) log y(n))。最后,每次我们遇到一个先前发现的组件时,我们都必须花费最多X(n) log * y(n)的时间来确定我们重新发现了哪个组件。由于最多可能会发生n次,因此涉及的累计功由n X(n) log y(n)限制。
因此,在检查我们访问的节点是否在样本中时执行的累积工作支配了运行时间:这花费O(n 2 log y(n))。然后我们应该使y(n)尽可能小,即O(X(n))。
因此,可以枚举O(n 2 log X(n))来枚举O(X(n) log(n))空间中的循环数(与那些循环中结束的组件数相同)。这样做的时间,其中X(n)是预期的循环数。