当前位置: 首页 > article >正文

洗牌算法(Shuffle Algorithm)Fisher-Yates 洗牌算法详细解读

洗牌算法(Shuffle Algorithm)用于将一组元素随机打乱,类似于洗牌过程中将牌随机排列的过程。Fisher-Yates 洗牌算法是最经典且高效的洗牌算法,能够确保生成的排列是等概率的(即每一种排列出现的概率相同),其时间复杂度为 O(n)

1. Fisher-Yates 洗牌算法的基本原理

Fisher-Yates 洗牌算法,也称为Knuth 洗牌算法,通过从后往前遍历数组,并将当前元素与之前未被处理的任意一个元素交换来实现随机排列。

具体步骤:

  1. 从数组的最后一个元素开始。
  2. 对于每一个元素 i,生成一个从 0 到 i 范围内的随机数 j,然后将 i 位置的元素与 j 位置的元素交换。
  3. 依次向前直到数组的第一个元素。

这种方法保证了每个元素都会与某个随机的、尚未处理的元素交换,从而能够保证结果是一个均匀分布的随机排列。

2. Fisher-Yates 洗牌算法的实现

下面是 Fisher-Yates 洗牌算法的 Java 实现:

import java.util.Random;

public class FisherYatesShuffle {

    // Fisher-Yates 洗牌算法
    public static void shuffle(int[] arr) {
        Random rand = new Random();
        
        // 从数组最后一个元素开始遍历
        for (int i = arr.length - 1; i > 0; i++) {
            // 在 [0, i] 范围内生成一个随机数 j
            int j = rand.nextInt(i + 1);
            
            // 交换 arr[i] 和 arr[j]
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    }

    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        
        System.out.println("Original array:");
        for (int num : arr) {
            System.out.print(num + " ");
        }
        System.out.println();
        
        shuffle(arr);  // 调用洗牌算法
        
        System.out.println("Shuffled array:");
        for (int num : arr) {
            System.out.print(num + " ");
        }
    }
}

代码解读:

  1. Random类:使用 Random 类来生成随机数。rand.nextInt(i + 1) 表示生成一个 [0, i] 之间的随机整数 j
  2. 数组遍历:从数组的最后一个元素开始,逐步往前遍历直到第一个元素,每次选取一个随机的索引 j,并将 i 位置的元素与 j 位置的元素交换。
  3. 交换过程:通过临时变量 temp 进行元素交换操作。

3. Fisher-Yates 洗牌算法的均匀性和正确性

Fisher-Yates 洗牌算法的核心优势在于它能够保证所有的排列都是等概率的。每一步生成的随机数都确保了当前元素与之前未处理的任何一个元素等概率交换。

  • 均匀性:由于每个元素在每次遍历时都有 1/(n-i) 的概率被选中进行交换,因此整个数组中的所有元素都能等概率地出现在任何一个位置上。最终生成的排列是等概率的。

  • 正确性:每个元素仅与之前尚未处理的元素进行交换,保证每次交换不会影响到已经被处理的元素,从而避免重复或遗漏情况。

4. Fisher-Yates 洗牌的时间复杂度

  • 时间复杂度:遍历数组一次,每次生成一个随机数并进行一次交换操作,因此总的时间复杂度为 O(n),其中 n 是数组的大小。
  • 空间复杂度:除了输入数组外,仅使用了常数级别的额外空间(即随机数生成器和临时变量),因此空间复杂度为 O(1)

5. Fisher-Yates 洗牌的应用场景

Fisher-Yates 洗牌广泛应用于需要随机排列元素的场景,常见的例子包括:

  • 扑克洗牌:随机生成一副扑克牌的顺序。
  • 游戏中的随机事件:如随机排列游戏关卡或生成游戏地图。
  • 抽样算法:从数据集或元素集合中随机抽取样本。
  • 随机排序:在需要对数据进行随机排序时使用。

6. Fisher-Yates 洗牌的变种

Fisher-Yates 洗牌的一个常见变种是从数组的第一个元素开始,而不是从最后一个元素开始。虽然这个变种看似不同,但在数学上也可以实现等概率的随机排列。

public static void shuffleVariant(int[] arr) {
    Random rand = new Random();
    
    // 从数组的第一个元素开始
    for (int i = 0; i < arr.length; i++) {
        // 在 [i, n-1] 范围内生成一个随机数 j
        int j = i + rand.nextInt(arr.length - i);
        
        // 交换 arr[i] 和 arr[j]
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

7. 常见问题与注意事项

  1. 随机数生成器的质量:洗牌的随机性依赖于随机数生成器的质量,通常使用 Random 类已经能提供足够的随机性,但在一些高需求场景(如密码学应用)中,可能需要更强的随机数生成器(如 SecureRandom)。
  2. 错误的洗牌算法:一些非等概率的洗牌算法可能会导致不均匀分布。例如,有些简单的洗牌算法会不断地从数组中随机选取一个元素并放入一个新的数组中,但这种方法会导致排列不是等概率的。
  3. 并行化:在并行计算中使用洗牌算法时,需确保各个线程间的随机数生成是独立的,否则会导致非均匀分布。

8. 洗牌算法的扩展

Fisher-Yates 洗牌算法不仅仅可以用于数组,也可以应用于其他线性结构,如链表、列表等。只要能够进行索引访问或进行元素交换,洗牌算法都可以被应用。

总结

Fisher-Yates 洗牌算法是一种经典的洗牌算法,能够在 O(n) 时间内将数组随机打乱,并确保所有排列的生成是等概率的。它广泛应用于随机排序、游戏设计和数据抽样等领域,是随机算法中的一个基础算法。


http://www.kler.cn/a/370087.html

相关文章:

  • ARM-V9 CCA/RME QEMU环境搭建
  • 62,【2】 BUUCTF WEB [强网杯 2019]Upload1
  • 第3天:阿里巴巴微服务解决方案概览
  • UE5 开启“Python Remote Execution“
  • 【整体介绍】
  • Linux-C/C++--深入探究文件 I/O (下)(文件共享、原子操作与竞争冒险、系统调用、截断文件)
  • 【ChatGPT】如何通过反向思维改进Prompt的编写
  • GAN原理及代码实现
  • 51单片机完全学习——DS18B20温度传感器
  • 医院信息化与智能化系统(12)
  • 极狐GitLab 发布安全补丁版本17.5.1, 17.4.3, 17.3.6
  • TextHarmony:视觉文本理解与生成的新型多模态大模型
  • 唤醒车机时娱乐屏出现黑屏,卡顿的案例分享
  • 深度学习(五):语音处理领域的创新引擎(5/10)
  • 106. 平行光阴影计算
  • springmvc请求源码流程解析(二)
  • 优先算法——移动零(双指针)
  • LVGL移植教程(超详细)——基于GD32F303X系列MCU
  • 人脸美颜 API 对接说明
  • 批量剪辑视频软件源码搭建全解析,支持OEM
  • 【瑞吉外卖】-day01
  • 使用 three.js 渲染个blender模型
  • 特定机器学习问题的基准测试数据
  • 【数据价值化】数据资产变现及管理规划
  • SQL语句优化之Sql执行顺序
  • 记录如何在RK3588板子上跑通paddle的OCR模型