N个整数随机置换的在线算法

时间:2017-06-01 15:35:16

标签: algorithm

想象一个标准的置换函数,它取一个整数并以随机排列的形式返回前N个自然数的向量。如果你只需要k(< = N),但事先不知道k,你还需要进行O(N)代排列吗?是否有比以下更好的算法:

for x in permute(N):
    if f(x):
        break

我正在想象一个API,例如:

p = permuter(N)
for x = p.next():
    if f(x):
        break

初始化为O(1)(包括内存分配)。

1 个答案:

答案 0 :(得分:3)

这个问题通常被视为两种竞争算法之间的选择:

  • 策略风云:Fisher-Yates shuffle的变体,其中对每个所需数字执行一个洗牌步骤,

  • 策略HT:将所有生成的数字保存在哈希表中。在每个步骤中,产生随机数,直到找到不在哈希表中的数字。

根据kN之间的关系执行选择:如果k足够大,则使用策略FY;否则,策略HT。争论的焦点是,如果k相对于n较小,则维护大小为n的数组会浪费空间,并产生较大的初始化成本。另一方面,当k接近n时,需要丢弃越来越多的随机数,并且最终产生新值将非常缓慢。

当然,您可能事先不知道要求的样本数量。在这种情况下,你可能会悲观地选择FY,或乐观地选择HT,并希望最好。

事实上,没有真正需要权衡,因为可以使用哈希表有效地实现FY算法。无需初始化N整数数组。相反,哈希表用于仅存储其值与其索引不对应的数组的元素。

(以下描述使用基于1的索引;这似乎是问题所寻求的。希望它没有完整的错误。因此它会生成[1, N]范围内的数字。从这里开始,我使用k来获取迄今为止请求的样本数,而不是最终请求的数量。)

在增量FY算法的每个点,从范围r中随机选择单个索引[k, N]。然后交换索引kr的值,之后k递增以进行下一次迭代。

作为效率点,请注意我们并不需要进行交换:我们只需在r处生成值,然后将r处的值设置为{{{ 1}}。我们永远不会再查看索引k的值,因此没有必要更新它。

最初,我们使用哈希表模拟数组。要查找(虚拟)数组中索引k的值,我们会看到哈希表中是否存在i:如果是,那就是索引i处的值。否则,索引i的值本身为i。我们从一个空的哈希表开始(这节省了初始化成本),它表示一个数组,其每个索引的值都是索引本身。

要进行FY迭代,对于每个样本索引i,我们生成如上所述的随机索引k,在该索引处生成值,然后在索引r处设置值到索引r的值。这正是上面针对FY所描述的程序,除了我们查找值的方式。

这需要正好两个哈希表查找,一个插入(在已查找的索引中,理论上可以更快地完成),并且每次迭代生成一个随机数。这是一个比策略HT最好的情况下的查找,但我们有一点节省,因为我们永远不需要循环来产生一个值。 (当我们重新散列时,还有另一个小的潜在节省,因为我们可以删除任何小于k的当前值的键。)

随着算法的进行,哈希表将增长;使用标准指数重组策略。在某些时候,哈希表将达到k整数向量的大小。 (由于哈希表开销,此点的值将N-k远小于k,但即使没有开销,也会在N达到此阈值。)此时,散列用于创建现在非虚拟数组的尾部,而不是重新散列,这个过程花费的时间比重新散列更少,并且永远不需要重复;将使用标准增量FY算法选择剩余样本。

如果N/2最终达到阈值点,此解决方案略慢于FY,如果k永远不会变得足够大以致于随机数被拒绝,则它稍微慢于HT。但是在任何一种情况下它都不会慢得多,并且如果k具有尴尬的价值,从来不会遇到病态的减速。

如果不清楚,这是一个粗略的Python实现:

k

注意:from random import randint def sampler(N): k = 1 # First phase: Use the hash diffs = {} # Only do this until the hash table is smallish (See note) while k < N // 4: r = randint(k, N) yield diffs[r] if r in diffs else r diffs[r] = diffs[k] if k in diffs else k k += 1 # Second phase: Create the vector, ignoring keys less than k vbase = k v = list(range(vbase, N+1)) for i, s in diffs.items(): if i >= vbase: v[i - vbase] = s del diffs # Now we can generate samples until we hit N while k <= N: r = randint(k, N) rv = v[r - vbase] v[r - vbase] = v[k - vbase] yield rv k += 1 可能是悲观的;计算正确的值需要了解太多关于哈希表的实现。如果我真的关心速度,我会用编译语言编写自己的哈希表实现,然后我会知道:)