用java随机洗牌,你的随机算法,真的随机吗?——洗牌算法

get-article-detail-184439.html

写在前面

相信大家都写过很多排序算法,但是你对乱序算法熟悉吗?现在就让小白带你走进乱序算法的世界。

题目要求

假如给你一个数组

let arr = [0,1,2,3,4,5,6,7,8,9]

复制代码

请问你如何将这个数组随机打乱输出?

错误的解法

console.log(arr.sort(() => Math.random() - 0.5));

复制代码

乍一看,这个解法好像没什么问题,我们得到的结果好像也都是正确的,但是它真的是随机的吗?

随机的真正含义

首先,我们需要理解在这道题目中随机的真正含义是什么?随机的真正含义应该是每个数字,在每个位置出现的几率都相等。也就是说,0在这十个位置中出现的几率是相等的,1在这十个位置中出现的几率是相等的......

为何上面的解法有问题

对于上面的解法, 我们姑且用伪随机来称呼它。首先,我们先看看sort()方法在 MDN中的解释:

arr.sort([compareFunction]) compareFunction 可选

用来指定按某种顺序进行排列的函数。如果省略,元素按照转换为的字符串的各个字符的Unicode位点进行排序。

firstEl 第一个用于比较的元素。

secondEl 第二个用于比较的元素。

我的理解

a,b 表示正在进行比较的两个数

a-b 小于 0 ,那么 a 会被排列到 b 之前

a-b 等于 0 , a 和 b 的相对位置不变

a-b 大于 0 ,那么 a 会被排列到 b 之后

console.log(arr.sort((a, b) => a - b)); // 升序

console.log(arr.sort((a, b) => b - a)); // 降序

复制代码

如果上面的解法是正确的的,那么,如果我们随机10000次,每个位置上加起来的数的平均数应该约等于4.5,我们试验一下:

let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

let res = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];

function shuffle(arr){

return arr.sort(() => Math.random() - 0.5)

}

let t = 10000;

for (let i = 0; i < t; i++) {

// 对数组先进行浅拷贝

let sorted = shuffle(arr.slice(0));

for (let i = 0; i < sorted.length; i++) {

res[i] = sorted[i] + res[i];

}

}

console.log(res.map(sum => sum / t));

复制代码

这里每次对数组进行浅拷贝是为了保证每次随机的都是原数组。 多运行几次后输出:

get-article-detail-184439.html 我们发现出现了3.8,5.1这样的极端情况,这明显是不符合随机的定义的。那么是哪里出错了呢?

arr.sort(() => Math.random() - 0.5)

这里的含义是设置 两个数交换的概率为50%。也就是说,两个数可能交换也可能不交换,这明显不符合随机的定义。

正确的解法

如果我们将比较的两个数 100% 交换,那么我们是不是达到了随机的要求了呢?在这里我采用了洗牌算法。 代码如下:

let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

let res = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];

function shuffle(arr){

let len = arr.length;

for (let i = 0; i < len; i++) {

// -i 从后面

// 从前面随机取一个数的下标

// Math.floor(x) 返回小于等于x的最大整数 向下取整

let idx = Math.floor(Math.random() * (len - i));

[arr[len - 1 - i], arr[idx]] = [arr[idx], arr[len - 1 - i]];

}

return arr;

}

let t = 10000;

for (let i = 0; i < t; i++) {

// 对数组先进行浅拷贝

let sorted = shuffle(arr.slice(0));

for (let i = 0; i < sorted.length; i++) {

res[i] = sorted[i] + res[i];

}

}

console.log(res.map(sum => sum / t));

复制代码

思路

我们从后往前取一个基准数 a

然后从未洗牌的区间随机取一个数b(a也在未洗牌的区域内)

a b 交换

从后往前 重复上述步骤

验证结果

get-article-detail-184439.html 可以看到此时我们的结果已经趋向于4.5,证明我们成功的写出了正确的随机算法!

写在后面

此前在网上看到了这道题,笔者觉得挺有意思, 特意来与大家分享下,希望大家都能成功解决这道算法题。 笔者目前是一名大三学生,正在学习前端的有关知识,对于算法的理解也不是很透彻,如果有相应问题,欢迎指正。希望可以和大家一起成长!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值