随机选择范围内的k个不同数字

时间:2015-05-31 14:59:10

标签: java algorithm sorting random random-sample

我需要在k范围内随机选择0 to n-1个元素。 n最高可达10 ^ 9。 k的范围可以是1 to n-1。我可以在O(n)时间内通过对包含值0 to n-1的数组进行混洗并从中选择第一个k元素来执行此操作。但是当k很小时,这种方法既有时间也有内存效率低下。这个问题有没有O(k)解决方案?

注意:所选k个数字必须不同。

我正在考虑一个解决方案。我能想到两种方法。让R是要返回的集合。

  1. 选择范围内的随机值并将其添加到R。继续这样做,直到|R| = k。此过程需要sum(n/i) for n+1-k <= i <= n时间和O(k)空间。
  2. 在数组中插入0到n-1,随机播放,从中获取第一个k个元素。此过程需要O(n + k)时间和空间。
  3. 因此,对于给定的k,我可以在O(k)时间内选择更好的方法。

1 个答案:

答案 0 :(得分:8)

修改的Fisher-Yates算法

可以改进shuffle解决方案,因为你只需要对数组的第一个 k 元素进行洗牌。但这仍然是O( n ),因为天真的shuffle实现需要一个大小为 n 的数组,需要将其初始化为 n 值0到 n-1

Initialize value[n] to {0..n-1}
For i from 0 to k-1:
  swap(value[i], value[random_in_range(i, n)])
Result is value[0..k-1]

为了改进,我们可以使用一种虚拟数组,由两部分组成:

  1. value :第一个 k 元素的数组,它们将是结果选择。这被初始化为{0 .. k-1 }

  2. rest :容量为 k 条目的稀疏数据结构(例如哈希表),包含数组的所有剩余条目,这些条目并非简单他们自己的指数。最初是空的。

  3. 现在我们可以定义从数组交换元素 i j 的函数(注意: i &lt; k ,由上述算法保证):

    # To swap elements i and j
    If j < k:
      # Both elements to be swapped are in the selection
      tmp = value[i]; value[i] = value[j]; value[j] = tmp
    Else If j in rest:
      # Element j has been swapped before
      tmp = value[i]; value[i] = rest[j]; rest[j] = tmp
    Else:
      # The value at j is still j, we now add it to the virtual array
      rest[j] = value[i]; value[i] = j
    

    对于 k n 的任何值,使用O( k )空格和时间。

    三种算法策略

    使用O( k )内存的更简单的解决方案是保留所有选定值的哈希表,并生成值,直到哈希表包含 k 值,拒绝重复

    对于小 k ,随机选择的元素是重复的概率是无关紧要的,并且天真的哈希表肯定是最简单的解决方案。另一方面,如果 k n 的重要部分,则哈希表主要是浪费空间,因为大小为 n 足以记录已经看到的值。对于非常大的 k ,拒绝算法将在样本填满时花费太多时间,并且shuffle所需的完整向量并不比用于保持suff的向量多得多。样品

    因此,实用的解决方案可能是使用三种解决方案中较少的空间和时间:对于 k 的值足够大, n 位bitvector小于具有 k 条目的哈希表,但不是那么大,以至于拒绝的概率很大(例如, n /64≤ k n / 4),使用位向量。对于较小的 k 值,请使用简单哈希表算法,对于 k 的值接近 n ,请使用Fisher-Yates shuffle完整 n - 元素向量(但仅限于 k 步骤)。

    由于我们仅在 k &gt; c n 的情况下选择O( n )策略一些常量 c ,复合算法仍然是O( k )时间和空间,因为有了这个约束, n 在O( ķ)。