热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

探讨两种常数卷积的结果与一种常见的洗牌算法错误及其影响

在编程中,随机打乱数组元素的顺序(即“洗牌”)是一个常见的需求。标准的洗牌算法是Fisher-Yatesshuffle,但许多开发者在实现时容易犯错,导致结果不均匀。本文探讨了两种常数卷积的结果,并分析了一种常见的洗牌算法错误及其对随机性的影响。通过详细的实验和理论分析,我们揭示了这些错误的具体表现和潜在危害,为开发者提供改进的建议。
8d617b995357f3ac580637cda36a96ce.png

  「洗牌」,或者说随机打乱一个数组中元素的顺序,是编程中的一个常见需求。标准的洗牌算法是 Fisher-Yates shuffle,用 Javascript 实现如下:

function shuffle(A) {for (var i = A.length - 1; i > 0; i--) {var j = Math.floor(Math.random() * (i + 1));var t = A[i]; A[i] = A[j]; A[j] = t;}
}

其基本思路是,每次从未打乱的部分等可能地选一个元素,把它与未打乱部分的最后一个元素交换。

  Fisher-Yates 洗牌算法的实现十分简单,并且它可以保证均匀性,即元素的各种排列顺序出现的概率都相等。但是,很多人闭门造车地发明了一些「错误」的洗牌算法实现,它们不能保证均匀。例如,最常见的一种错误实现如下:

function shuffle(A) {for (var i = 0; i }

其原理是,在第 i 次循环中,从所有元素中等可能地选一个元素,与第 i 个元素交换。这种算法的错误可以如下证明:对于一个长度为

的数组,算法创造了
个等可能的基本事件,这些事件对应于
种排列顺序。在非平凡情况下,
不能被
整除,所以各种排列顺序不可能等概率。

  另一种错误的洗牌算法是 @Lucas HC 在这篇答案中指出的:

A.sort(function() {return .5 - Math.random();
});

Javascript 中数组自带的 sort 方法允许提供一个比较器,其返回值的正负号代表两个元素的大小关系。在上面的代码中,比较器返回的是 -0.5 到 0.5 之间的一个随机数,也就是说每次比较的结果是随机且均匀的。但是,基于随机比较的整个洗牌算法是不均匀的:它的各种运行结果的概率都形如

(
为算法执行过程中的比较次数),而我们希望每种顺序的概率都是
,在非平凡情况下,后者不能由前者通过加法组合出来。@Lucas HC 指出,当 sort 函数采用
插入排序的实现时,各个元素都有较大的概率留在初始位置,并通过统计多次运行的结果进行了验证。

  如果你不熟悉编程,我在此可以用大白话把 @Lucas HC 答案中错误算法的流程叙述一下:设想有

个人依次来到一个队伍。每个人到来之后,都想向前插队,但前面每一个人放他过去的概率都是 1/2。最终队伍的状态就是洗牌结果。
8c49178f3d89833ac56ba5acb88478f0.png
5 号元素插到了 3 号位置。这个事件要发生,需要队尾的两个元素放它过去,而前面的一个元素不放它过去,故概率为 1/8。

  我对这种算法的结果分布感到好奇,于是计算了一下洗牌后各个元素落在各个位置的概率。用

表示总共有
个元素时,第
个元素洗牌后落在第
个位置的概率(这里
的范围为 1 到
)。给定
,各个
可以排成一个
阶矩阵,记作
。这些矩阵可以用递推法计算:
  • 时,显然有
  • 时:
    • 首先考虑
      号元素。它有 1/2 的概率停在
      号位置,有 1/4 的概率向前插一位停在
      号位置,有 1/8 的概率向前插两位停在
      号位置 …… 一般地,若
      号元素要停在
      号位置(
      ),它就需要越过
      个元素,并被
      号位置的元素挡住,故概率为
      ,但停在 1 号位置的概率跟停在 2 号位置的概率相同,都是
    • 然后考虑
      (
      )号元素。它落在
      号位置有两种可能:
      • 在前
        个元素洗牌完毕后,
        号元素就已经落在
        号位置,并且
        号元素没能越过它。这种情况的概率为
        ;
      • 在前
        个元素洗牌完毕后,
        号元素落在
        号位置,但
        号元素越过了它,使它后移到了
        号位置。这种情况的概率为
      • 于是有递推式:

  上面的递推式用 Matlab 可以如下计算。计算过程使用了大量的矩阵运算,看不懂不必强求。

N = 100;
P = cell(1, N);
P{1} = 1;
for n = 2:Nx = 0.5 .^ [n-1:-1:1];P{n} = zeros(n, n);P{n}(1:n-1, 1:n-1) = bsxfun(@times, P{n-1}, 1 - x);P{n}(1:n-1, 2:n) = P{n}(1:n-1, 2:n) + bsxfun(@times, P{n-1}, x);P{n}(n, 1) = x(1);P{n}(n, 2:n) = x;
end

  上述代码算出的

如下。左图直接显示了整个矩阵,颜色深浅表示概率大小;右图则是画出了矩阵的每一行,即每个元素最终位置的分布。
20ea8b4d9004c3afdfc368762852d6fe.png
使用错误的算法洗牌后,10 个元素位于各个位置的概率

@Lucas HC 的结论,就是说

的对角线元素会显著大于
。上图说明,
的对角线元素都大于 0.2,印证了这个结论。从图中还能看出另外几个事实:
  • 是对称的,即
    号元素落在
    号位置的概率,等于
    号元素落在
    号位置的概率;
  • 的前两行、前两列分别相等,也就是说 1、2 号元素的地位相同,1、2 号位置的地位也相同。

  把

增大,可以发现一些更有趣的事情。例如,下图画出的是
:
2d72e2cbaccaaecdf71ef46916c6948f.png
使用错误的算法洗牌后,30 个元素位于各个位置的概率

从左图可以看到,矩阵中元素沿垂直于对角线的方向迅速衰减,所以「洗牌」后各个元素都倾向于留在初始位置或其附近。而右图则说明,除了开头和结尾几个元素,每个元素最终位置分布曲线的形状都几乎是一样的,它们的最大值(即留在原位的概率)都比 0.2 大一点,且这个值似乎与

无关!

  在 Matlab 中执行 format long 命令,可以让计算结果具有 15 位有效数字。用此方法可以观察到

的对角线中部元素趋向于一个常数
,它约等于 0.220643036096533。尽管
的绝大多数对角元都只比常数
大一丁点儿,不过如果要在矮子里面再挑矮子,那么最小的对角元大约出现在第
个位置。当
时,15 位有效数字就已经无法区分
与常数
了。

  网上并没有找到对常数

的讨论,所以我就暂且给它起个名字,叫「乱排常数」。这个常数是有理数还是无理数呢?它有什么样的数学表达式?这些有趣的问题,就留到下一篇中讨论。



推荐阅读
  • 本文将继续探讨前端开发中常见的算法问题,重点介绍如何将多维数组转换为一维数组以及验证字符串中的括号是否成对出现。通过多种实现方法的解析,帮助开发者更好地理解和掌握这些技巧。 ... [详细]
  • 本文介绍了如何在多线程环境中实现异步任务的事务控制,确保任务执行的一致性和可靠性。通过使用计数器和异常标记字段,系统能够准确判断所有异步线程的执行结果,并根据结果决定是否回滚或提交事务。 ... [详细]
  • Coursera ML 机器学习
    2019独角兽企业重金招聘Python工程师标准线性回归算法计算过程CostFunction梯度下降算法多变量回归![选择特征](https:static.oschina.n ... [详细]
  • 本次挑战涉及数组截断操作,初看似乎简单,但实际上考察了对数组切片方法的理解与应用。本文将详细解析该算法的实现逻辑,并提供多个示例以加深理解。 ... [详细]
  • 12月16日JavaScript变量、函数、流程、循环等***线上九期班
    12月16日JavaScript变量、函数、流程、循环等***线上九期班 ... [详细]
  • Redux入门指南
    本文介绍Redux的基本概念和工作原理,帮助初学者理解如何使用Redux管理应用程序的状态。Redux是一个用于JavaScript应用的状态管理库,特别适用于React项目。 ... [详细]
  • 历经三十年的开发,Mathematica 已成为技术计算领域的标杆,为全球的技术创新者、教育工作者、学生及其他用户提供了一个领先的计算平台。最新版本 Mathematica 12.3.1 增加了多项核心语言、数学计算、可视化和图形处理的新功能。 ... [详细]
  • 本文总结了优化代码可读性的核心原则与技巧,通过合理的变量命名、函数和对象的结构化组织,以及遵循一致性等方法,帮助开发者编写更易读、维护性更高的代码。 ... [详细]
  • 探讨如何修复Visual Studio Code中JavaScript的智能感知和自动完成功能在特定场景下无法正常工作的问题,包括配置检查、语言模式选择以及类型注释的使用。 ... [详细]
  • 优化网页加载速度:JavaScript 实现图片延迟加载
    本文介绍如何使用 JavaScript 实现图片延迟加载,从而显著提升网页的加载速度和用户体验。 ... [详细]
  • 本文回顾了2017年的转型和2018年的收获,分享了几家知名互联网公司提供的工作机会及面试体验。 ... [详细]
  • Python 工具推荐 | PyHubWeekly 第二十一期:提升命令行体验的五大工具
    本期 PyHubWeekly 为大家精选了 GitHub 上五个优秀的 Python 工具,涵盖金融数据可视化、终端美化、国际化支持、图像增强和远程 Shell 环境配置。欢迎关注并参与项目。 ... [详细]
  • 本章详细介绍SP框架中的数据操作方法,包括数据查找、记录查询、新增、删除、更新、计数及字段增减等核心功能。通过具体示例和详细解析,帮助开发者更好地理解和使用这些方法。 ... [详细]
  • 本文深入探讨了MySQL中常见的面试问题,包括事务隔离级别、存储引擎选择、索引结构及优化等关键知识点。通过详细解析,帮助读者在面对BAT等大厂面试时更加从容。 ... [详细]
  • Windows 环境下安装 Git 并连接 GitHub 的详细步骤
    本文详细介绍了如何在 Windows 系统中安装 Git 工具,并通过配置 SSH 密钥实现与 GitHub 的安全连接。包括下载、安装、环境配置及验证连接等关键步骤。 ... [详细]
author-avatar
骷髅怪天堂_821
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有