随机数&随机排序&Fisher-Yates Shuffle

498 阅读4分钟

原文发布于语雀,欢迎关注

允许转载, 但请不要断章取义且不贴出原文链接. 不仅无法解决读者问题,还影响检索到原文的可能. 曾深受其害😡


包含三个和随机相关的重要知识点,仅需阅读本文和本文所含的所有链接文字即可掌握(本文主要为前辈文章的整合,均贴出原文链接以节约读者的检索时间) 🥳🥳🥳

随机数

Math.random() 不能提供像密码一样安全的随机数字。不要使用它们来处理有关安全的事情。使用Web Crypto API 来代替, 和更精确的window.crypto.getRandomValues() 方法.

Why Math.random()不安全? 由于 Math.random() 的底层算法是公开的(xorshift128+ 算法),V8 源码可见,因此,是可以使用其他语言模拟的,这就导致,如果攻击者知道了当前随机生成器的状态,那就可以知道缓存中的所有随机数,那就很容易匹配与破解。 例如抽奖活动,使用 Math.random() 进行随机,那么就可以估算出一段时间内所有的中奖结果,从而带来非常严重且致命的损失

function rnd( seed ){
    seed = ( seed * 9301 + 49297 ) % 233280; 
    return seed / ( 233280.0 );
};

function rand(number){
    today = new Date(); 
    seed = today.getTime();
    return Math.ceil( rnd( seed ) * number );
};

myNum=(rand(5)); 

随机排序

形如arr.sort(() => Math.random() - 0.5)得到的并不是真正的随机排序
具体的结果取决于你所用环境的Javascript 引擎
比如Chrome V8中,出于对性能的考虑,sort方法使用了两种方案: 插入排序和快排. 当目标数组长度小于10时,使用插入排序;反之,使用快排

不管用什么排序方法,大多数排序算法的时间复杂度介于O(n)到O(n2)之间,元素之间的比较次数通常情况下要远小于n(n-1)/2,也就意味着有一些元素之间根本就没机会相比较(也就没有了随机交换的可能),这些 sort 随机排序的算法自然也不能真正随机。 ​
怎么理解上边这句话呢?其实我们想使用array.sort进行乱序,理想的方案或者说纯乱序的方案是数组中每两个元素都要进行比较,这个比较有50%的交换位置概率。这样一来,总共比较次数一定为n(n-1)。而在sort排序算法中,大多数情况都不会满足这样的条件。因而当然不是完全随机的结果了 👉array.sort方法底层如何实现

so,要实现真正意义上的乱序,需要规避不稳定的sort方法
通常, 可以使用Fisher-Yates shuffle算法(洗牌算法)
其思路是: 逆向遍历数组,并将每个元素与其前面的随机的一个元素互换位置
性能方面, Fisher — Yates 算法要好得多, 没有"排序"开销
下面是该算法的具体实现

{
  Array.prototype.shuffle = function () {
    let input = this;
    for (let i = input.length - 1; i >= 0; i--) {
      let randomIndex = Math.floor(Math.random() * (i + 1));
      [input[randomIndex], input[i]] = [input[i], input[randomIndex]];
    }
    return input;
  };
  let count = {
    123: 0,
    132: 0,
    213: 0,
    231: 0,
    321: 0,
    312: 0,
  };
  for (let i = 0; i < 1000000; i++) {
    let array = [1, 2, 3];
    array.shuffle();
    count[array.join("")]++;
  }

  // 显示所有可能排列的出现次数
  for (let key in count) {
    console.log(`${key}: ${count[key]}`);
  }
}

👆随机性的数学归纳法证明 对 n 个数进行随机: 首先我们考虑 n = 2 的情况,根据算法,显然有 1/2 的概率两个数交换,有 1/2 的概率两个数不交换,因此对 n = 2 的情况,元素出现在每个位置的概率都是 1/2,满足随机性要求。 ​
假设有 i 个数, i >= 2 时,算法随机性符合要求,即每个数出现在 i 个位置上每个位置的概率都是 1/i。 对于 i + 1 个数,按照我们的算法,在第一次循环时,每个数都有 1/(i+1) 的概率被交换到最末尾,所以每个元素出现在最末一位的概率都是 1/(i+1) 。而每个数也都有 i/(i+1) 的概率不被交换到最末尾,如果不被交换,从第二次循环开始还原成 i 个数随机,根据 2. 的假设,它们出现在 i 个位置的概率是 1/i。因此每个数出现在前 i 位任意一位的概率是 (i/(i+1)) * (1/i) = 1/(i+1),也是 1/(i+1)。 ​
综合 1. 2. 3. 得出,对于任意 n >= 2,经过这个算法,每个元素出现在 n 个位置任意一个位置的概率都是 1/n


拓展资料