从n中选择k

时间:2015-04-25 17:41:50

标签: algorithm shuffle

我希望在可能的k中随机选择n个元素而不选择相同的数字两次。对此有两种简单的方法。

  1. 列出所有n种可能性。洗牌他们(你不需要 通过执行第一个n个数字k来洗牌 Fisher Yates的k步骤。选择第一个k。这种方法 花费O(k)时间(假设分配大小为n的数组 O(1)时间)和O(n)空间。如果k非常严重,则会出现问题 相对于n而言较小。
  2. 存储一组看到的元素。从[0, n-1]随机选择一个数字。当元素在集合中时,然后选择一个新数字。 这种方法占用O(k)空间。运行时间稍微多一点 很难分析。如果k = theta(n)那么运行时间是 O(k*lg(k))=O(n*lg(n)),因为它是coupon collector's problem。如果k相对于n较小,则需要稍微调整一下 超过O(k)因为选择的概率(尽管很低) 两次相同的数字。这比上面的解决方案更好 空间条款,但在运行时方面更差。
  3. 我的问题:

    所有O(k)O(k)是否有k时间n空间算法?

3 个答案:

答案 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)平均时间和空间算法。