少年,洗个牌吧

1,348 阅读2分钟

问题

如何利用已知random()函数,求一个1~N的全排列?

详细描述

已知:一个random()函数可以生成一个(0,1)范围内的浮点数。
要求:输入N,利用random()函数,生成一个1至N的全排列。
例如:当输入N = 4,生成结果可以为1324或3214等等,并保证等概率。

正文

Step 1

这个题最自然的想法莫过于连续产生随机数,然后如果产生的这个随机数之前没有产生过,就记录并且输出。

这样做显然是能得到结果的,并且能保证足够的随机。

但存在的问题就是会产生些许的浪费,每次产生随机数之后都要确认曾经是否产生过。尤其是对于这样的场景,比如 1 - 3 的全排列,如果已经产生了 3、2,那么我们一定能够确定的是这个全排列的最后一个数一定是 1 ,同样对于倒数第二个数也存在这样的浪费。

Step 2

换种想法,我们可以先做个循环或者其他的方法,产生这么多的数,然后如果能把这些数的顺序随机打乱,也就是一种很好的解法了。

那么问题是如何去随机的打算他们,保证概率呢?

《算法导论》这本书上给过相关的方法,并且有充分的证明。怎么做呢?

伪代码如下:

RANDOMIZE_IN_PLACE (A)

n = A.length
for i=1 to n
  swap A[i] with A[Random(i, n)]

那简单的来看如何去理解呢?我们都知道抓阄这个方法是公平的,所以同理,这个方法可以这样理解,有 n 个阄,从 n 个里抓一个,然后剩余 n - 1 个,然后继续从剩下的 n - 1 里面抓。这样保证每个阄都是公平出现的。

这段代码用 JavaScript 实现如下:

A = []

function rd(n,m){  
  return Math.floor(Math.random() * (m-n+1) + n);
}

for(let i = 0; i < 100; i++) {
  A[i] = i + 1;
}

for(let i = 0; i < 100 - 1; i++) {
  let index = rd(i+1, 100-1);

  A[i] = A[i] ^ A[index]
  A[index] = A[index] ^ A[i]
  A[i] = A[i] ^ A[index]
}

console.log(A.join("、"))

Step 3

这也就是著名的洗牌算法


版权声明

  • 请尊重辛勤劳动
  • 禁止不署名完全转载,可单独通过下面的捐赠付版权费
  • 建议署名并进行摘要性质转载

捐赠

写文不易,赠我一杯咖啡增强一下感情可好?

alipay