【LeetCode.384打乱数组】Knuth洗牌算法详解
前两天看网易面筋得知网易云的随机歌曲播放使用了这个算法,遂找题来做做学习一下
打乱数组
https://leetcode.cn/problems/shuffle-an-array/
给你一个整数数组 nums ,设计算法来打乱一个没有重复元素的数组。打乱后,数组的所有排列应该是 等可能 的。
实现 Solution class:
Solution(int[] nums) 使用整数数组 nums 初始化对象
int[] reset() 重设数组到它的初始状态并返回
int[] shuffle() 返回数组随机打乱后的结果
示例 1:
输入
["Solution", "shuffle", "reset", "shuffle"]
[[[1, 2, 3]], [], [], []]
输出
[null, [3, 1, 2], [1, 2, 3], [1, 3, 2]]
解释
Solution solution = new Solution([1, 2, 3]);
solution.shuffle(); // 打乱数组 [1,2,3] 并返回结果。任何 [1,2,3]的排列返回的概率应该相同。例如,返回 [3, 1, 2]
solution.reset(); // 重设数组到它的初始状态 [1, 2, 3] 。返回 [1, 2, 3]
solution.shuffle(); // 随机返回数组 [1, 2, 3] 打乱后的结果。例如,返回 [1, 3, 2]
提示:
1 <= nums.length <= 50
-106 <= nums[i] <= 106
nums 中的所有元素都是 唯一的
最多可以调用 104 次 reset 和 shuffle
题目要求的输入是一个整数数组nums,要调用shuffle()函数将其打乱后返回一个乱序数组
暴力法
(本方法不是重点,想了解直接看后面的代码,LeetCode官解)
一种很古朴的思路是:
再定义一个数组nums4shuffle,把nums中的所有数都存进nums4shuffle,然后遍历nums4shuffle
假设当前循环变量是i,此时从nums4shuffle中随机选取一个数,作为打乱后的nums的第i个元素
那么要解决的问题有以下几个:
1、如何再次获取数组的初始顺序
2、如何从nums4shuffle中随机取值
Fisher-Yates 洗牌算法
在上述问题中,所谓的“打乱”(或者说随机洗牌),其实可以理解为:让每一个元素都等概率地出现在每一个位置
即每个位置都能等概率的放置每个元素
听上去有点耳熟?Knuth 洗牌算法实际上就是一种将数组中元素随机排列的组合问题。
假设有一个长度为
n
的数组arr
,我们需要对其进行随机化操作,使得其中的每个元素都具有相等的可能性出现在任意位置上。这可以理解为是从n
个元素中选择n
个元素不重复地排列的问题,即全排列。因此,根据组合数学的知识,共有n!
种不同的可能性,每一种可能性出现的概率应该是相等的,即为1/n!
。
因此,Knuth 洗牌算法的正确性在于它能够保证每个排列出现的概率相等,并且所有可能的排列构成了一个大小为 n!
的集合。这与概率论中的组合问题有着相似的思路和方法。
算法实现
该算法使用代码实现起来很简洁,就是一个for循环即可
void knuth_shuffle(vector<int>& arr) {
int n = arr.size();
for (int i = 0; i < n; ++i) {
// 随机选择一个位置 j,其中 i <= j < n
int j = rand() % (n - i) + i;
// 交换 arr[i] 和 arr[j] 的值
swap(arr[i], arr[j]);
}
}
knuth_shuffle
函数是用于执行 Knuth 洗牌算法的函数,它接受一个整数类型的数组 arr
作为输入参数,使用该算法对数组进行随机化操作。
函数中首先获取数组的长度 n
,然后开始遍历数组。在每一轮遍历中,函数会随机选择一个位置 j
,其中 i <= j < n
,也就是从 i 开始到数组末尾之间随机选择一个位置。
这里使用了
rand()
函数来生成随机数,并将其除以取模运算的余数与i
相加,得到最终的位置j
,rand()
函数默认生成的随机数范围是 0 到 RAND_MAX(通常为 32767)。假设n=5,i=2,此时已经到了第二轮循环,前两个数已经被随机交换,现在要在剩下的3个数中进行交换
rand()函数会生成0到32767之间的一个随机整数,我们将它除以(n-i)=3,然后取余数
假设rand()生成的随机整数为10000,则它除以3的结果是3333余1。以此类推,我们就可能得到0~2之间的余数
当前遍历到的位置是2,那么只要在加上2就可以得到一个2~4之间的随机数
根据上面的分析,j
的结果就是n - i
之间的一个随机数
一旦选定了位置 j
,函数就会交换 arr[i]
和 arr[j]
这两个元素的值。
循环结束
这样,每一次遍历都会使得数组中的某个元素被随机地交换到前面的位置上,从而实现了 Knuth 洗牌算法的效果
代码
洗牌算法
class Solution {
public:
Solution(vector<int>& nums) {
nums4save = nums;//初始化nums4save
}
vector<int> reset() {
return nums4save;//重置数组时只要返回保存的初始数组即可
}
vector<int> shuffle() {
vector<int> nums4shuffle = nums4save;//定义一个新数组用于打乱顺序
int numsLen = nums4shuffle.size();
//洗牌算法
for(int i = 0; i < numsLen; ++i){//通过for循环选取一个数
//在(i,numsLe]间再随机选择一个数与for循环选择的数进行交换
int random = rand() % (numsLen - i) + i;//计算numsLen - i之间的一个随机数
swap(nums4shuffle[i], nums4shuffle[random]);//交换
}
return nums4shuffle;//返回打乱后的数组
}
private:
vector<int> nums4save;//定义nums4save用于保存初始数组
};
暴力法
class Solution {
public:
Solution(vector<int>& nums) { // 构造函数
// 将传入的nums保存到成员变量this->nums中
this->nums = nums;
// 创建一个与nums等长的vector original,并将nums的值复制到original中
this->original.resize(nums.size());
copy(nums.begin(), nums.end(), original.begin());
}
vector<int> reset() { // 还原为原始顺序
// 将original中的元素值复制到nums中
copy(original.begin(), original.end(), nums.begin());
// 返回nums
return nums;
}
vector<int> shuffle() { // 随机打乱顺序
// 创建一个新的vector shuffled,用于保存随机打乱后的nums
vector<int> shuffled = vector<int>(nums.size());
// 创建一个list lst,并将nums中的元素值复制到lst中
list<int> lst(nums.begin(), nums.end());
// 遍历nums
for (int i = 0; i < nums.size(); ++i) {
// 在lst的元素个数范围内生成一个随机索引j
int j = rand()%(lst.size());
// 获取lst中索引为j的元素,并将其赋值给shuffled[i]
auto it = lst.begin();
advance(it, j);//将迭代器 it 向前移动 j 个位置,就可以获得对应的随机元素
shuffled[i] = *it;
// 从lst中删除索引为j的元素
lst.erase(it);
}
// 将shuffled中的元素值复制到nums中
copy(shuffled.begin(), shuffled.end(), nums.begin());
// 返回nums
return nums;
}
private:
vector<int> nums; // 原始数组
vector<int> original; // 原始顺序
};
【LeetCode.384打乱数组】Knuth洗牌算法详解的更多相关文章
- knuth洗牌算法
首先来思考一个问题: 设计一个公平的洗牌算法 1. 看问题,洗牌,显然是一个随机算法了.随机算法还不简单?随机呗.把所有牌放到一个数组中,每次取两张牌交换位置,随机 k 次即可. 如果你的答案是这样, ...
- Java实现 LeetCode 384 打乱数组
384. 打乱数组 打乱一个没有重复元素的数组. 示例: // 以数字集合 1, 2 和 3 初始化数组. int[] nums = {1,2,3}; Solution solution = new ...
- 随机洗牌算法Knuth Shuffle和错排公式
Knuth随机洗牌算法:譬如现在有54张牌,如何洗牌才能保证随机性.可以这么考虑,从最末尾一张牌开始洗,对于每一张牌,编号在该牌前面的牌中任意一张选一张和当前牌进行交换,直至洗到第一张牌为止.参考代码 ...
- js 随机数 洗牌算法
function shuffle(arr){ var len = arr.length; for(var i = 0;i<len -1;i++) { var idx = Math.floor(M ...
- 洗牌算法shuffle
对这个问题的研究始于一次在群里看到朋友发的洗牌面试题.当时也不知道具体的解法如何,于是随口回了一句:每次从剩下的数字中随机一个.过后找相关资料了解了下,洗牌算法大致有3种,按发明时间先后顺序如下: 一 ...
- 519. Random Flip Matrix(Fisher-Yates洗牌算法)
1. 问题 给定一个全零矩阵的行和列,实现flip函数随机把一个0变成1并返回索引,实现rest函数将所有数归零. 2. 思路 拒绝采样 (1)先计算矩阵的元素个数(行乘以列),记作n,那么[0, n ...
- 《Algorithms算法》笔记:元素排序(3)——洗牌算法
<Algorithms算法>笔记:元素排序(3)——洗牌算法 Algorithms算法笔记元素排序3洗牌算法 洗牌算法 排序洗牌 Knuth洗牌 Knuth洗牌代码 洗牌算法 洗牌的思想很 ...
- 【算法】331- JS洗牌算法
点击上方"前端自习课"关注,学习起来~ 最近的一个塔罗牌项目中,有一个洗牌的需求,其实也就是随机打乱数组,遂网上搜了下,再此做个整理- 塔罗牌 举例来说,我们有一个如下图所示的数组 ...
- 洗牌算法及 random 中 shuffle 方法和 sample 方法浅析
对于算法书买了一本又一本却没一本读完超过 10%,Leetcode 刷题从来没坚持超过 3 天的我来说,算法能力真的是渣渣.但是,今天决定写一篇跟算法有关的文章.起因是读了吴师兄的文章<扫雷与算 ...
- 洗牌算法Fisher_Yates原理
1.算法 http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle 简单的原理如下图所示: 2.原理 总结下,洗牌算法Fisher_Yates ...
随机推荐
- 在Vue中发起axios请求成功,却被catch捕捉返回Network Error
前端发起请求成功,后台接收处理返回,却被axios的catch捕获,没有走then函数. 最后添加了headers配置成功解决,如上,附上axios接口配置中文文档:axios中文文档|axios中文 ...
- Cannot read properties of undefined (reading 'toUpperCase')
无法读取 JS 中未定义的属性"toUpperCase"|鲍比哈兹 (bobbyhadz.com) 根据其中的内容找到了答案:使用了未定义的属性去使用toUpperCase()函数 ...
- 2.Web开发基础
Web开发基础 目录 Web开发基础 1.网络基础 2.OSI模型 应用层: 表示层: 会话: 传输层: 网络层: 数据链路层: 物理层: 3.通信子网:(数据通信) 4.资源子网:(数据处理) 5. ...
- 四月二十五号java基础知识
1.注意:无论哪个构造方法,在创建文件输入输出流时都可能银给出的文件名不对.路径不对文件的属性不对等,不能打开文件而造成错误,此时系统会抛出FileNotFoundException异常执行read( ...
- vue自定义密码输入框解决浏览器自动填充密码的问题
pre { overflow-y: auto; max-height: 300px } img { max-width: 500px; max-height: 300px } 问题描述 浏览器对于ty ...
- 介绍一个.Net远程日志组件
对于软件开发的阶段和正式运行阶段,我们都需要查看日志来诊断出现的问题.不过,在查看日志时需要登录服务器,找到特定的日志文件,再查看其中的内容,这显然不是很方便. 为了解决这个问题,我们可以使用远程日志 ...
- SMBMS超市管理系统(Javaweb项目)
SMBMS超市管理系统(Javaweb项目) 项目环境 jdk 1.8.0_261 mysql 8.0.21 IDEA 2021.2.2 项目架构 数据库 项目源代码 filer 字符编码过滤器 pa ...
- ES6教程笔记
ES介绍 什么是ES ES全称 EcmaScript 是一种脚本语言的规范 Javascript为ES的实现 Ecma 是一个组织 Ecma组织 为什么要学习ES6? ES6的版本变动内容最多,具有里 ...
- vue项目PC端如何适配不同分辨率屏幕
配置前言 项目构建:基于vue-cli3构建,使用postcss-px2rem px2rem-loader插件进行rem适配实现原理:每次打包,webpack通过使用插件postcss-px2rem, ...
- Python-BeautifulReport的简单使用
一.简介 BeautifulReport.report report ( filename -> 测试报告名称, 如果不指定默认文件名为report.html description -> ...