登陆 | 注册 设为首页 | 加入收藏 | 联系我们
太和养老网
热词老年艺术  助老机构  养老系统  

中心区域北京 天津 河北 山西 内蒙古 辽宁 吉林 黑龙江 上海 江苏 浙江 安徽 福建 江西 山东 河南 湖北 湖南 广东 广西 海南 重庆 四川 贵州 云南 西藏 陕西 甘肃 青海 宁夏 新疆 香港 澳门 台湾 全国城市养老院目录 全国县市养老院汇总目录 太和AI作品展 太和养老艺术网AI作品展示

您现在所在位置:首页 > 养老信息化 > 洗牌算法-飞
洗牌算法-飞

 

2023/7/6 17:15:22 ('互联网')

读完本文,你可以去力扣拿下如下题目:

384.打乱数组

-----------

我知道大家会各种花式排序算法,但是如果叫你打乱一个数组,你是否能做到胸有成竹?即便你拍脑袋想出一个算法,怎么证明你的算法就是正确的呢?乱序算法不像排序算法,结果唯(有哪些以lia开头的四字成语?没有lia字开头的成语,QQ接龙红包“一个顶俩”难倒了不少网友,根据成语词典提示,成语中含有俩(lia)的成语有:鬼蜮伎俩、鬼魅伎俩、有三有俩、鬼蜮技俩、一个顶俩。可是,按照规定,必须是俩(lia)开头的成语才可以。但是并没有俩(lia)开头的成语,所以这是一个无解的成语接龙呢。)一可以很容易检验,因为「乱」可以有很多种,你怎么能证明你的算法是「真的乱」呢?

所以我们面临两个问题:

    什么叫做「真的乱」?设计怎样的算法来打乱数组才能做到「真的乱」?

这种算法称为「随机乱置算法」或者「洗牌算法」。

本文分两部分,第一部分详解最常用的洗牌算法。因为该算法的细节容易出错,且存在好几种变体,虽有细微差异但都是正确的,所以本文要介绍一种简单的通用思想保证你写出正确的洗牌算法。第二部分讲解使用「蒙特卡罗方法」来检验我们的打乱结果是不是真的乱。蒙特卡罗方法的思想不难,但是实现方式也各有特点的。

PS:我认真写了 100 多篇原创,手把手刷 200 道力扣题目,全部发布在 labuladong的算法小抄,持续更新。建议收藏,按照我的文章顺序刷题,掌握各种算法套路后投再入题海就如鱼得水了。

一、洗牌算法

此类算法都是靠随机选取元素交换来获取随机性,直接看代码(伪码),该算法有 4 种形式,都是正确的:

// 得到一个在闭区间 [min, max] 内的随机整数int randInt(int min, int max);// 第一种写法void shuffle(int[] arr) {    int n = arr.length();    /******** 区别只有这两行 ********/    for (int i = 0  i < n; i++) {        // 从 i 到最后随机选一个元素        int rand = randInt(i, n - 1);        /*************************/        swap(arr[i], arr[rand]);    }}// 第二种写法    for (int i = 0  i < n - 1; i++)        int rand = randInt(i, n - 1);// 第三种写法    for (int i = n - 1  i >= 0; i--)        int rand = randInt(0, i);// 第四种写法    for (int i = n - 1  i > 0; i--)        int rand = randInt(0, i);

分析洗牌算法正确性的准则:产生的结果必须有 n! 种可能,否则就是错误的。这个很好解释,因为一个长度为 n 的数组的全排列就有 n! 种,也就是说打乱结果总共有 n! 种。算法必须能够反映这个事实,才是正确的。

我们先用这个准则分析一下第一种写法的正确性:

// 假设传入这样一个 arrint[] arr = {1,3,5,7,9};void shuffle(int[] arr) {    int n = arr.length(); // 5    for (int i = 0  i < n; i++) {        int rand = randInt(i, n - 1);        swap(arr[i], arr[rand]);    }}

for 循环第一轮迭代时,i = 0rand 的取值范围是 [0, 4],有 5 个可能的取值。

第一次

for 循环第二轮迭代时,i = 1rand 的取值范围是 [1, 4],有 4 个可能的取值。

第二次

后面以此类推,直到最后一次迭代,i = 4rand 的取值范围是 [4, 4],只有 1 个可能的取值。

最后一次

可以看到,整个过程产生的所有可能结果有 n! = 5! = 5*4*3*2*1 种,所以这个算法是正确的。

分析第二种写法,前面的迭代都是一样的,少了一次迭代而已。所以最后一次迭代时 i = 3rand 的取值范围是 [3, 4],有 2 个可能的取值。

// 第二种写法// arr = {1,3,5,7,9}, n = 5    for (int i = 0  i < n - 1; i++)        int rand = randInt(i, n - 1);

所以整个过程产生的所有可能结果仍然有 5*4*3*2 = 5! = n! 种,因为乘以 1 可有可无嘛。所以这种写法也是正确的。

如果以上内容你都能理解,那么你就能发现第三种写法就是第一种写法,只是将数组从后往前迭代而已;第四种写法是第二种写法从后往前来。所以它们都是正确的。

如果读者思考过洗牌算法,可能会想出如下的算法,但是这种写法是错误的:

void shuffle(int[] arr) {    int n = arr.length();    for (int i = 0  i < n; i++) {        // 每次都从闭区间 [0, n-1]        // 中随机选取元素进行交换        int rand = randInt(0, n - 1);        swap(arr[i], arr[rand]);    }}

现在你应该明白这种写法为什么会错误了。因为这种写法得到的所有可能结果有 n^n 种,而不是 n! 种,而且 n^n 不可能是 n! 的整数倍。

比如说 arr = {1,2,3},正确的结果应该有 3!= 6 种可能,而这种写法总共有 3^3 = 27 种可能结果。因为 27 不能被 6 整除,所以一定有某些情况被「偏袒」了,也就是说某些情况出现的概率会大一些,所以这种打乱结果不算「真的乱」。

上面我们从直觉上简单解释了洗牌算法正确的准则,没有数学证明,我想大家也懒得证明。对于概率问题我们可以使用「蒙特卡罗方法」进行简单验证。

二、蒙特卡罗方法验证正确性

洗牌算法,或者说随机乱置算法的正确性衡量标准是:对于每种可能的结果出现的概率必须相等,也就是说要足够随机。

如果不用数学严格证明概率相等,可以用蒙特卡罗方法近似地估计出概率是否相等,结果是否足够随机。

记得高中有道数学题:往一个正方形里面随机打点,这个正方形里紧贴着一个圆,告诉你打点的总数和落在圆里的点的数量,让你计算圆周率。

正方形

这其实就是利用了蒙特卡罗方法:当打的点足够多的时候,点的数量就可以近似代表图形的面积。通过面积公式,由正方形和圆的面积比值是可以很容易推出圆周率的。当然打的点越多,算出的圆周率越准确,充分体现了大力出奇迹的真理。

类似的,我们可以对同一个数组进行一百万次洗牌,统计各种结果出现的次数,把频率作为概率,可以很容易看出洗牌算法是否正确。整体思想很简单,不过实现起来也有些技巧的,下面简单分析几种实现思路。

第一种思路,我们把数组 arr 的所有排列组合都列举出来,做成一个直方图(假设 arr = {1,2,3}):

直方图

每次进行洗牌算法后,就把得到的打乱结果对应的频数加一,重复进行 100 万次,如果每种结果出现的总次数差不多,那就说明每种结果出现的概率应该是相等的。写一下这个思路的伪代码:

void shuffle(int[] arr);// 蒙特卡罗int N = 1000000;HashMap count; // 作为直方图for (i = 0; i < N; i++) {    int[] arr = {1,2,3};    shuffle(arr);    // 此时 arr 已被打乱    count[arr] += 1;}for (int feq : count.values())     print(feq / N + " "); // 频率

这种检验方案是可行的,不过



扫码加微信详细咨询太和智慧养老产品和平台服务!

 

 





版权声明:

---------------------------------------------------------------


所有信息来源于互联网,本文的版权归原作者所有,不代表本网观点和立场。

本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,请发送邮件至 81480447@qq.com 举报,一经查实,本站将立刻删除。



扫码加微信详细咨询太和智慧养老产品和平台服务!

 

养老资讯
助老机构介绍
评论
已有 0 条评论

最新评论

推荐养老院

您希望养老院位于
  • 不限
  • 东城
  • 西城
  • 崇文
  • 宣武
  • 朝阳
  • 丰台
  • 石景山
  • 海淀
  • 门头沟
  • 房山
  • 通州
  • 顺义
  • 昌平
  • 大兴
  • 怀柔
  • 平谷
  • 延庆
  • 密云
您希望的价格范围
  • 不限
  • 500以下
  • 500-1000
  • 1000-2000
  • 2000-3000
  • 3000-5000
  • 5000以上
老人的情况是
  • 不限
  • 自理
  • 半自理
  • 全护理
  • 特护

姓名

年龄

电话

全国城市养老院



关于我们 | 联系方式 | 网站地图 | 友情链接

Copyright 2010-2022 京ICP备18035644号-3 北京太和 版权所有