在小于O(M)存储器中从给定范围0..N-1生成M个不同的随机数(一次一个)

时间:2013-10-03 22:49:05

标签: performance algorithm random

有没有办法做到这一点?

我的意思是,我们甚至无法使用{0,1,..,N-1}的“in”数组(因为它至少是O(N)内存)。

M可以是= N.N可以> 2 ^ 64。结果应该是一致随机的,并且最好是每个可能的序列(但可能不是)。

同样全范围的PRNG(和朋友)也不合适,因为每次都会给出相同的序列。

时间复杂性并不重要。

3 个答案:

答案 0 :(得分:3)

如果你不关心随机选择出现的顺序,那么它可以在恒定的内存中完成。选择按顺序进行。

答案取决于估计每个可能的{0, ..., N-1}随机选择集i的M个不同值的最小值为i的概率。调用此值p(i, M, N)。由于数学比我有耐心输入不支持Latex的界面,你可以得出p函数的一些非常好的估计;在这里,我将展示简单,非时间效率的方法。

让我们只关注p(0, M, N),即MN对象中随机选择的概率将包含第一个对象。然后我们可以一次一个地遍历对象(即数字0...N-1);通过翻转加权硬币来决定是否包含它。我们只需要为每次翻转计算硬币的重量。

根据定义,MCN可能M - 选择一组N个对象。其中MCN-1不包括第一个元素。 (这是M的选择 - N-1个对象的选择,这是所有M - 选择缺少一个元素的集合。同样,M-1CN-1选项确实包含第一个元素(即所有M-1 - 选择N-1 - 集合,第一个元素添加到每个选择中。

这两个值加起来为MCN;用于计算C的众所周知的递归算法。

所以p(0, M, N)只是M-1CN-1/MCN。自MCN = N!/(M!*(N-M)!)起,我们可以将该分数简化为M/N。正如预期的那样,如果M == N,那就是1(N个对象中的M个必须包含每个对象)。

所以现在我们知道第一个对象在选择中的概率是多少。然后,我们可以减小集合的大小,或者减少剩余的选择大小,具体取决于硬币翻转是否确定我们是否包含第一个对象。所以这里是伪代码的最终算法,基于加权随机布尔函数的存在:

w(x, y) => true with probability X / Y; otherwise false.

我将为读者留下w的实现,因为它是微不足道的。

所以:

Generate a random M-selection from the set 0...N-1
Parameters: M, N

Set i = 0
while M > 0:
  if w(M, N):
     output i
     M = M - 1
  N = N - 1
  i = i + 1

这可能不是很明显,但是请注意:

  • output i语句必须完全M次执行,因为它与M的递减相关联,并且while循环执行直到M为{{1} }}
  • 0越接近MN将减少的概率越高。如果我们到达M的点,那么两者都将以锁步方式递减,直到它们都达到M == N
  • 0i递减时正好递增,因此它必须始终在N范围内。事实上,这是多余的;我们可以输出0...N-1而不是输出N-1,这会改变算法以按降序生成集合而不是增加顺序。我没有这样做,因为我觉得上面的内容更容易理解。

该算法的时间复杂度为i,必须为O(N+M)。如果O(N)很大,那不是很好,但问题陈述说时间复杂性并不重要,所以我会把它留在那里。

答案 1 :(得分:1)

未将状态空间映射到较低位数以进行输出的PRNG应该可以正常工作。例子包括线性同余发生器和Tausworthe发生器。如果您使用相同的种子启动它们,它们将给出相同的序列,但这很容易改变。

答案 2 :(得分:0)

蛮力: 如果时间复杂度无关紧要,那么0&lt; M <= N不变。 nextRandom(N)是一个在[0..N)中返回随机整数的函数:

init() {
        for (int idx = 0; idx < N; idx++) {
            a[idx] = -1;
        }
        for (int idx = 0; idx < M; idx++) {
            getNext();
        }
    }

    int getNext() {
        for (int idx = 1; idx < M; idx++) {
            a[idx -1] = a[idx];
        }
        while (true) {
            r = nextRandom(N);
            idx = 0;
            while (idx < M && a[idx] != r) idx++;
            if (idx == M) {
                a[idx - 1] = r;
                return r;
            }
        }
    }

O(M)解决方案:为简单起见,它是递归解决方案。它假设运行nextRandom(),它返回[0..1)中的随机数:

rnd(0, 0, N, M); // to get next M distinct random numbers

int rnd(int idx, int n1, int n2, int m) {
    if (n1 >= n2 || m <= 0) return idx;
    int r = nextRandom(n2 - n1) + n1;
    int m1 = (int) ((m-1.0)*(r-n1)/(n2-n1) + nextRandom()); // gives [0..m-1]
    int m2 = m - m1 - 1;
    idx = rnd(idx, n1, r-1, m1);
    print r;
    return rnd(idx+1, r+1, n2, m2);
}

这个想法是在第一步中选择[0..N]之间的随机r,它将两个子范围上的范围分成N1和N2元素(N1 + N2 == N-1)。我们需要为[0..r]重复相同的步骤,其中有N1个元素和[r + 1..N)(N2个元素)选择M1和M2(M1 + M2 == M-1),以便M1 / M2 == N1 / N2。 M1和M2必须是整数,但是这个比例可以给出实际结果,我们需要用概率来舍入值(1.2将给出1,其中p = 0.8,2则给出p = 0.2等)。