前两天看网易面筋得知网易云的随机歌曲播放使用了这个算法,遂找题来做做学习一下

打乱数组

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洗牌算法详解的更多相关文章

  1. knuth洗牌算法

    首先来思考一个问题: 设计一个公平的洗牌算法 1. 看问题,洗牌,显然是一个随机算法了.随机算法还不简单?随机呗.把所有牌放到一个数组中,每次取两张牌交换位置,随机 k 次即可. 如果你的答案是这样, ...

  2. Java实现 LeetCode 384 打乱数组

    384. 打乱数组 打乱一个没有重复元素的数组. 示例: // 以数字集合 1, 2 和 3 初始化数组. int[] nums = {1,2,3}; Solution solution = new ...

  3. 随机洗牌算法Knuth Shuffle和错排公式

    Knuth随机洗牌算法:譬如现在有54张牌,如何洗牌才能保证随机性.可以这么考虑,从最末尾一张牌开始洗,对于每一张牌,编号在该牌前面的牌中任意一张选一张和当前牌进行交换,直至洗到第一张牌为止.参考代码 ...

  4. js 随机数 洗牌算法

    function shuffle(arr){ var len = arr.length; for(var i = 0;i<len -1;i++) { var idx = Math.floor(M ...

  5. 洗牌算法shuffle

    对这个问题的研究始于一次在群里看到朋友发的洗牌面试题.当时也不知道具体的解法如何,于是随口回了一句:每次从剩下的数字中随机一个.过后找相关资料了解了下,洗牌算法大致有3种,按发明时间先后顺序如下: 一 ...

  6. 519. Random Flip Matrix(Fisher-Yates洗牌算法)

    1. 问题 给定一个全零矩阵的行和列,实现flip函数随机把一个0变成1并返回索引,实现rest函数将所有数归零. 2. 思路 拒绝采样 (1)先计算矩阵的元素个数(行乘以列),记作n,那么[0, n ...

  7. 《Algorithms算法》笔记:元素排序(3)——洗牌算法

    <Algorithms算法>笔记:元素排序(3)——洗牌算法 Algorithms算法笔记元素排序3洗牌算法 洗牌算法 排序洗牌 Knuth洗牌 Knuth洗牌代码 洗牌算法 洗牌的思想很 ...

  8. 【算法】331- JS洗牌算法

    点击上方"前端自习课"关注,学习起来~ 最近的一个塔罗牌项目中,有一个洗牌的需求,其实也就是随机打乱数组,遂网上搜了下,再此做个整理- 塔罗牌 举例来说,我们有一个如下图所示的数组 ...

  9. 洗牌算法及 random 中 shuffle 方法和 sample 方法浅析

    对于算法书买了一本又一本却没一本读完超过 10%,Leetcode 刷题从来没坚持超过 3 天的我来说,算法能力真的是渣渣.但是,今天决定写一篇跟算法有关的文章.起因是读了吴师兄的文章<扫雷与算 ...

  10. 洗牌算法Fisher_Yates原理

    1.算法 http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle 简单的原理如下图所示: 2.原理 总结下,洗牌算法Fisher_Yates ...

随机推荐

  1. SQL优化---慢SQL优化

    于2023.3.17日重写,之前写的还是太八股文太烂了一点逻辑都没有,这次重新写了之后,感觉数据库优化还是很有必要的,之前觉得不必要是我年轻了. 一.如何定位慢SQL语句 1.通过慢查询日志查询已经执 ...

  2. Clion 连接 WSL 编译Unix环境

    Clion 连接 WSL 编译Unix环境 安装 WSL Ubuntu 18版本,创建后不要换源,upgrade后安装CMake.g++.gcc 安装 Clion,创建项目 进入setting 在 B ...

  3. react抽离配置文件、配置@符号、调整src文件夹---配置scss、编写项目的页面结构、创建各个页面 src/views、开始路由、入口文件处修改代码、修改App.js布局文件、添加底部的导航布局、构建个人中心。。。声明式跳转路由、使用React UI库请求渲染首页数据、

    1.回顾 2.react项目的配置 react默认创建的项目配置文件在 node_modules/react-scripts 文件夹内部 2.1 抽离配置文件 cnpm run eject cnpm ...

  4. Helm 安装 Kubernetes 监控套件

    Helm 安装 Grafana Prometheus Altermanager 套件 安装helm # 安装helm工具 curl -fsSL -o get_helm.sh https://raw.g ...

  5. Java Stream流

    Java8 Stream流编程 Stream 使用一种类似于SQL语句从数据库查询数据的直观方式来提供对 Java 集合运算和表达的高阶抽象.得益于 Lambda 所带来的函数式编程,Stream A ...

  6. Python property、setter、deleter

    面向对象封装特点之一就是通过实现好的方法来访问,限制对数据的不合理访问,把对象状态私有化,仅供类的内部进行操作 下方示例,Test方法的number属性类实例的时候传递1,number是一个公开属性, ...

  7. 帝国ECMS静态生成为一行代码/静态页面打乱教程

    一.内容页变成一行修改1.打开文件e/class/functions.php2.找到以下函数 function GetHtml($classid,$id,$add,$ecms=0,$doall=0) ...

  8. 使用 Transformers 进行图分类

    在之前的 博文 中,我们探讨了图机器学习的一些理论知识.这一篇我们将探索如何使用 Transformers 库进行图分类.(你也可以从 此处 下载演示 notebook,跟着一起做!) 目前,Tran ...

  9. for of 和 for in 的区别

    1 var arr = ["f", "6", 3, "a", 7]; 2 var obj = { name: "shun" ...

  10. vue将页面(dom元素)转换成图片,并保存到本地

    1 npm install html2canvas --save <template> <div class="QRCode-box"> <img i ...