有没有办法做到这一点?
我的意思是,我们甚至无法使用{0,1,..,N-1}的“in”数组(因为它至少是O(N)内存)。
M可以是= N.N可以> 2 ^ 64。结果应该是一致随机的,并且最好是每个可能的序列(但可能不是)。
同样全范围的PRNG(和朋友)也不合适,因为每次都会给出相同的序列。
时间复杂性并不重要。
答案 0 :(得分:3)
如果你不关心随机选择出现的顺序,那么它可以在恒定的内存中完成。选择按顺序进行。
答案取决于估计每个可能的{0, ..., N-1}
随机选择集i
的M个不同值的最小值为i
的概率。调用此值p(i, M, N)
。由于数学比我有耐心输入不支持Latex的界面,你可以得出p
函数的一些非常好的估计;在这里,我将展示简单,非时间效率的方法。
让我们只关注p(0, M, N)
,即M
个N
对象中随机选择的概率将包含第一个对象。然后我们可以一次一个地遍历对象(即数字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
越接近M
,N
将减少的概率越高。如果我们到达M
的点,那么两者都将以锁步方式递减,直到它们都达到M == N
。0
在i
递减时正好递增,因此它必须始终在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等)。