【问题标题】:Online algorithm for random permutation of N integersN个整数随机排列的在线算法
【发布时间】:2017-11-02 20:30:54
【问题描述】:

想象一个标准的置换函数,它接受一个整数并返回一个随机排列的前 N ​​个自然数的向量。如果您只需要其中的 k (

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

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

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

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

【问题讨论】:

标签: algorithm


【解决方案1】:

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

  • 策略 FY:Fisher-Yates shuffle 的一种变体,其中对每个所需数字执行一个 shuffle 步骤,并且

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

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

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

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

(以下描述使用基于 1 的索引;这似乎是问题所要寻找的。希望它没有完全错误。因此它生成范围 [1, N] 内的数字。从这里上,我使用k 表示迄今为止已请求的样本数量,而不是最终将被请求的数量。)

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

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

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

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

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

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

如果k 最终达到阈值点,则此解决方案比 FY 稍慢,如果 k 永远不会变得足够大以至于拒绝随机数,则它比 HT 稍慢。但在这两种情况下都不会慢很多,并且当k 具有尴尬的价值时,如果永远不会遭受病态的减速。

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

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

注意:N // 4 可能是悲观的;计算正确的值需要对哈希表实现了解太多。如果我真的关心速度,我会用编译语言编写自己的哈希表实现,然后我就会知道:)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-31
    • 1970-01-01
    • 1970-01-01
    • 2010-09-27
    相关资源
    最近更新 更多