我希望在可能的k
中随机选择n
个元素而不选择相同的数字两次。对此有两种简单的方法。
n
种可能性。洗牌他们(你不需要
通过执行第一个n
个数字k
来洗牌
Fisher Yates的k
步骤。选择第一个k
。这种方法
花费O(k)
时间(假设分配大小为n
的数组
O(1)
时间)和O(n)
空间。如果k
非常严重,则会出现问题
相对于n
而言较小。[0, n-1]
随机选择一个数字。当元素在集合中时,然后选择一个新数字。
这种方法占用O(k)
空间。运行时间稍微多一点
很难分析。如果k = theta(n)
那么运行时间是
O(k*lg(k))=O(n*lg(n))
,因为它是coupon collector's
problem。如果k
相对于n
较小,则需要稍微调整一下
超过O(k)
因为选择的概率(尽管很低)
两次相同的数字。这比上面的解决方案更好
空间条款,但在运行时方面更差。我的问题:
所有O(k)
和O(k)
是否有k
时间n
空间算法?
答案 0 :(得分:16)
使用O(1) hash table,可以使部分Fisher-Yates方法在O( k )时间和空间中运行。诀窍就是只在哈希表中存储数组的更改的元素。
以下是Java中的一个简单示例:
public static int[] getRandomSelection (int k, int n, Random rng) {
if (k > n) throw new IllegalArgumentException(
"Cannot choose " + k + " elements out of " + n + "."
);
HashMap<Integer, Integer> hash = new HashMap<Integer, Integer>(2*k);
int[] output = new int[k];
for (int i = 0; i < k; i++) {
int j = i + rng.nextInt(n - i);
output[i] = (hash.containsKey(j) ? hash.remove(j) : j);
if (j > i) hash.put(j, (hash.containsKey(i) ? hash.remove(i) : i));
}
return output;
}
此代码分配一个2× k 桶的HashMap来存储修改后的元素(这应该足以确保哈希表永远不会重新散列),并且只运行部分Fisher-Yates shuffle在它上面。
Here's a quick test on Ideone;它从三个30,000次中挑选出两个元素,并计算每对元素的选择次数。对于无偏差的随机播放,每个有序对应出现大约5,000(±100左右)次,除了两个元素相等的不可能的情况。
答案 1 :(得分:0)
您可以使用的是以下算法(使用javascript而不是伪代码):
var k = 3;
var n = [1,2,3,4,5,6];
// O(k) iterations
for(var i = 0, tmp; i < k; ++i) {
// Random index O(1)
var index = Math.floor(Math.random() * (n.length - i));
// Output O(1)
console.log(n[index]);
// Swap and lookup O(1)
tmp = n[index];
n[index] = n[n.length - i - 1];
n[n.length - i - 1] = tmp;
}
简而言之,您将所选值与最后一项交换,并在还原子集的下一次迭代样本中交换。这假定您的原始集合是完全唯一的。
存储为O(n),如果您希望将数字作为一组检索,只需参考n中的最后k个条目。
答案 2 :(得分:0)
你的第二种方法平均不占用Theta(k log k)时间,需要大约n /(n-k + 1)+ n /(n-k + 2)+ ... + n / n次运算因为你有k项,每个小于n /(nk),所以小于k(n /(nk))。对于k <= n / 2,平均需要不到2 * k的操作。对于k> n / 2,您可以选择大小为n-k的随机子集,并取补码。所以,这已经是一个O(k)平均时间和空间算法。