我需要在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
是要返回的集合。
R
。继续这样做,直到|R| = k
。此过程需要sum(n/i) for n+1-k <= i <= n
时间和O(k)空间。k
个元素。此过程需要O(n + k)时间和空间。因此,对于给定的k
,我可以在O(k)时间内选择更好的方法。
答案 0 :(得分:8)
可以改进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]
为了改进,我们可以使用一种虚拟数组,由两部分组成:
value :第一个 k 元素的数组,它们将是结果选择。这被初始化为{0 .. k-1 }
rest :容量为 k 条目的稀疏数据结构(例如哈希表),包含数组的所有剩余条目,这些条目并非简单他们自己的指数。最初是空的。
现在我们可以定义从值数组交换元素 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( ķ)。