2025年洗牌算法

洗牌算法目录 一 什么是洗牌算法 二 如何去打乱 三 例题 一 什么是洗牌算法 现在有一副扑克牌 让你去洗牌 怎么洗牌才能让每一种牌之间的组合出现的概率相等 简单的问题往往隐藏了重要信息 比如这里我们可以将洗牌理解为将这副牌打乱 那么什么才叫乱呢

大家好,我是讯享网,很高兴认识大家。

目录

一、什么是洗牌算法

二、如何去打乱

三、例题


一、什么是洗牌算法

现在有一副扑克牌,让你去洗牌,怎么洗牌才能让每一种牌之间的组合出现的概率相等?

简单的问题往往隐藏了重要信息,比如这里我们可以将洗牌理解为将这副牌打乱,那么什么才叫乱呢?

其中有两个要素:

  • 随机的结果要能够覆盖所有的情况,(譬如 n张牌,最后的结果有 n! 情况,结果所有顺序必须都能够出现)。
  • 所有出现的结果 概率 相等。

二、如何去打乱

首先,设定我们已经拥有了系统提供的rand函数,能够提供一个给定范围内的随机数,而且我们假定这个随机过程满足给定范围内的均匀分布。

以洗牌为例:

我们洗牌的细粒度最小的动作,应该是两张牌对换。(两堆对换,一张牌抽出放在顶部等动作都是O(n)的复杂度)

要达到打乱的效果,我们比较容易想到的方式是 n张牌组成的全新牌堆:

  • 洗1次,会得到n种可能的结果;
  • 洗2次,会得到n*(n-1)种结果(减一是因为当第一次和第二次为互逆过程,等于回到最开始,没有新的排列出现)
  • 洗3次,会得到n*(n-1)*(n-2)种结果;
  • ... ...
  • 洗n次,会得到n!种结果,此时,已经完全充满n!的空间,洗更多次,样本空间不扩充。

所以,到这里,可以知道,对于一副牌,最少只要随机的交换n次,才能在概率的意义上,让洗牌达到足够的“乱”,那么现在问题来了,如何选择一个好的算法?

一个比较容易想到的,简单粗暴的方法是,按照上面的文字,写出这样的伪代码:

 for(int i=0;i<suit.length;i++) { random1 = Random.next(1,n); random2 = Random.next(1,n); exchange(suit[random1],suit[random2]); }

讯享网

上面的算法在设计上能够充满整个样本空间,确实存在n!种可能性,但是不够好。

为什么不够好呢,因为这种算法不能够 确保 照顾到每一张牌。

随机洗牌的随机在于其不确定性,对于n张牌组成的有序排列,经过了n次随机选择,漏掉1只牌从未选过的概率不为0,而且,随着牌的张数数量增加,这个概率非常可观。

现在就是经典的Fisher–Yates算法登场的时候了。下面给出伪代码:


讯享网

讯享网 for(int i=suit.length-1;i>0;i--) { random1 = Random.next(1,i); exchange(suit[random1],suit[i]); }

这个算法和之前的算法最大的不同在于,每一次抽卡的范围在慢慢变小,具体的步骤可以看wiki给出的例子,我就直接照搬过来:

图标

img

img

img

img

这个算法在样本空间上,跟前面简单粗暴的随机抽取一样。

充满了n!的样本空间,好在哪里呢?

因为它利用了抽卡本身的顺序,【保证照顾】到了每一张原本序列中的卡,

而简单粗暴随机抽取存在出现重复位置的可能性,就等于浪费了一次排序的机会,

换句话说,其等效抽卡次数因为出现了过去相同的洗法,有效洗牌次数下降,样本空间缩小,无法充满整个n!空间,所以有效性会下降。

而Fisher–Yates算法在原理上保证了不会出现浪费次数,重复选择的情况,导致样本空间一直保持n!,没有坍缩,这就是其在数学意义上优秀的原因。

三、例题

给你一个整数数组 nums ,设计算法来打乱一个没有重复元素的数组。

实现 Solution class:

  • Solution(int[] nums) 使用整数数组 nums 初始化对象
  • int[] reset() 重设数组到它的初始状态并返回
  • int[] shuffle() 返回数组随机打乱后的结果

Demo 

 

小讯
上一篇 2025-04-05 23:26
下一篇 2025-04-07 13:59

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/127203.html