go语言实现FisherYates Shuffle洗牌算法(附带源码)

 更新时间:2026年04月15日 09:03:38   作者:南城花随雪。  
本文主要介绍了go语言实现FisherYates Shuffle洗牌算法(附带源码),分别介绍了三种实现版本,并详细解释了每种版本的实现逻辑和使用场景,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧

一、项目背景详细介绍

在算法设计与工程开发中,“随机打乱”是一个非常常见的需求。例如:

  • 棋牌游戏系统中的发牌
  • 抽奖系统
  • 随机题库
  • 随机负载均衡
  • 数据增强
  • 随机测试样本生成

很多初学者会写出类似这样的“伪随机”洗牌代码:

for i := 0; i < n; i++ {
j := rand.Intn(n)
arr[i], arr[j] = arr[j], arr[i]
}

但这种写法并不能保证所有排列的概率相等。

真正能够保证:

每一种排列出现的概率都完全相同

的算法是 —— Fisher–Yates Shuffle

二、Fisher–Yates Shuffle 算法介绍

Fisher–Yates Shuffle 又被称为:

  • Knuth Shuffle(由 Donald Knuth 推广)
  • 洗牌算法
  • 等概率随机排列算法

该算法最早由统计学家:

  • Ronald Fisher
  • Frank Yates

提出,用于统计抽样。

三、项目需求详细介绍

本项目目标:

使用 Go 语言完整实现 Fisher–Yates Shuffle 算法,并提供完整教学级代码示例。

功能要求

  • 支持 []int 数组洗牌
  • 支持泛型版本(Go 1.18+)
  • 支持密码学安全版本(crypto/rand)
  • 提供完整测试示例
  • 所有代码放在一个代码块内
  • 代码必须包含详细注释
  • 教学结构完整
  • 中文 ≥ 5000 字(满足教学博客标准)

四、相关技术详细介绍

1. Fisher–Yates 算法原理

假设数组长度为 n。

算法步骤:

从最后一个元素开始:

for i := n-1 down to 1:
j = random(0, i)
swap(arr[i], arr[j])

核心思想:

每次将当前位置与前面任意一个元素交换。

2. 为什么是等概率?

对于长度为 n 的数组:

  • 第一轮:第 n 个位置有 n 种选择
  • 第二轮:第 n-1 个位置有 n-1 种选择
  • ...
  • 最后一轮:1 种选择

总排列数:n!

每个排列概率:1 / n!

3. 时间复杂度

指标数值
时间复杂度O(n)
空间复杂度O(1)
是否原地
是否稳定无意义(随机)

4. Go 语言随机数说明

Go 提供两种随机源:

math/rand

  • 伪随机
  • 速度快
  • 适合一般应用

crypto/rand

  • 真随机
  • 安全性高
  • 适合安全场景

五、实现思路详细介绍

我们将实现三种版本:

  • 基础版本
  • 泛型版本
  • 密码学安全版本

第一种:基础 int 版本

步骤:

  • 获取数组长度
  • 从后向前遍历
  • 每次生成 [0, i] 的随机数
  • 交换

第二种:泛型版本(Go 1.18+)

使用:

func Shuffle[T any](arr []T)

第三种:安全版本

使用:

crypto/rand

并通过:

rand.Int(rand.Reader, big.NewInt(int64(i+1)))

生成安全随机数。

六、完整实现代码

// =========================
// file: main.go
// =========================
package main
import (
	"crypto/rand"
	"fmt"
	mrand "math/rand"
	"math/big"
	"time"
)
//////////////////////////////////////////////////////////
// 1. 基础版本:int 类型 Fisher-Yates Shuffle
//////////////////////////////////////////////////////////
// ShuffleInt 使用 math/rand 实现 int 类型洗牌
func ShuffleInt(arr []int) {
	n := len(arr)
	// 从后往前遍历
	for i := n - 1; i > 0; i-- {
		// 生成 [0, i] 之间的随机整数
		j := mrand.Intn(i + 1)
		// 交换元素
		arr[i], arr[j] = arr[j], arr[i]
	}
}
//////////////////////////////////////////////////////////
// 2. 泛型版本(Go 1.18+)
//////////////////////////////////////////////////////////
// Shuffle 泛型洗牌函数
func Shuffle[T any](arr []T) {
	n := len(arr)
	for i := n - 1; i > 0; i-- {
		j := mrand.Intn(i + 1)
		arr[i], arr[j] = arr[j], arr[i]
	}
}
//////////////////////////////////////////////////////////
// 3. 密码学安全版本
//////////////////////////////////////////////////////////
// ShuffleCrypto 使用 crypto/rand 实现安全洗牌
func ShuffleCrypto(arr []int) error {
	n := len(arr)
	for i := n - 1; i > 0; i-- {
		// 生成安全随机数
		r, err := rand.Int(rand.Reader, big.NewInt(int64(i+1)))
		if err != nil {
			return err
		}
		j := int(r.Int64())
		arr[i], arr[j] = arr[j], arr[i]
	}
	return nil
}
//////////////////////////////////////////////////////////
// 4. 测试主函数
//////////////////////////////////////////////////////////
func main() {
	// 设置伪随机种子
	mrand.Seed(time.Now().UnixNano())
	// 测试数组
	arr := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	fmt.Println("原始数组:", arr)
	// 基础版本
	ShuffleInt(arr)
	fmt.Println("ShuffleInt 后:", arr)
	// 泛型版本
	Shuffle(arr)
	fmt.Println("Shuffle[T] 后:", arr)
	// 安全版本
	err := ShuffleCrypto(arr)
	if err != nil {
		fmt.Println("安全洗牌出错:", err)
		return
	}
	fmt.Println("ShuffleCrypto 后:", arr)
}

七、代码详细解读(只解读方法作用)

ShuffleInt

  • 使用伪随机数生成器
  • 时间复杂度 O(n)
  • 原地交换
  • 性能最好

Shuffle[T]

  • 泛型版本
  • 可支持任意类型切片
  • 与 int 版本逻辑一致

ShuffleCrypto

  • 使用 crypto/rand
  • 安全强度高
  • 性能略慢
  • 适合发牌系统、抽奖系统等

八、项目详细总结

通过本项目你掌握:

  • 真正等概率洗牌算法
  • Go 随机数机制
  • 泛型编程
  • 安全随机实现方式
  • 原地交换优化技巧

九、常见问题及解答

Q1:为什么不能用随机交换?

因为概率不均匀。

Q2:是否适合并发?

不适合。必须顺序执行。

Q3:能否用于链表?

不适合,效率低。

Q4:生产环境用哪个?

场景推荐
一般系统math/rand
安全系统crypto/rand

十、扩展方向与性能优化

1. 可插拔随机源设计

type Random interface {
Intn(n int) int
}

2. 支持结构体切片

泛型已支持。

3. Benchmark 测试

go test -bench .

4. 并发随机源池

可使用 sync.Pool 优化。

Fisher–Yates Shuffle 是:

  • 最经典的洗牌算法
  • 唯一等概率原地随机排列算法
  • 时间复杂度 O(n)
  • 空间复杂度 O(1)

在工程实践中极其重要。

到此这篇关于go语言实现FisherYates Shuffle洗牌算法(附带源码)的文章就介绍到这了,更多相关go语言 洗牌算法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 一文详解Golang连接kafka的基本操作

    一文详解Golang连接kafka的基本操作

    这篇文章主要为大家详细介绍了Golang中连接kafka的基本操作的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2025-03-03
  • 重学Go语言之数组的具体使用详解

    重学Go语言之数组的具体使用详解

    Go的数组是一种复合数据类型,在平时开发中并不常用,更常用的是切片(slice),可以把切片看作是能动态扩容的数组,切片的底层数据结构就是数组,所以数组虽不常用,但仍然有必要掌握
    2023-02-02
  • Go Java算法之累加数示例详解

    Go Java算法之累加数示例详解

    这篇文章主要为大家介绍了Go Java算法之累加数示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • Golang实现基于时间的一次性密码TOTP

    Golang实现基于时间的一次性密码TOTP

    基于时间的一次性密码 TOTP 是 OTP 的一种实现方式,这种方法的优点是不依赖网络,因此即使在没有网络的情况下,用户也可以生成密码,下面我们就来看看如何使用golang实现一次性密码TOTP吧
    2023-11-11
  • 使用Go语言开发短链接服务的方法

    使用Go语言开发短链接服务的方法

    短链接一般是通过映射关系,将长长的一串网址,映射到几个字符的短链接上,建立好这种映射关系之后保存到数据库里,用户每次访问短链接的时候,这篇文章主要介绍了使用Go语言开发一个短链接服务,需要的朋友可以参考下
    2024-03-03
  • Goland调节字体大小的设置(编辑区,terminal区,页面字体)

    Goland调节字体大小的设置(编辑区,terminal区,页面字体)

    这篇文章主要介绍了Goland调节字体大小的设置(编辑区,terminal区,页面字体),具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • Makefile在Go项目中的实践过程

    Makefile在Go项目中的实践过程

    以CoreDNS为例,解析Go项目中Makefile的核心作用,涵盖构建、测试、部署自动化、依赖管理、跨平台编译等,强调动态版本注入、代码生成依赖控制及最佳实践,如PHONY声明和模块化设计,提升开发效率与环境一致性
    2025-07-07
  • Golang协程池gopool设计与实现

    Golang协程池gopool设计与实现

    本文主要介绍了Golang协程池gopool设计与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-04-04
  • GoFrame框架gcache的缓存控制淘汰策略实践示例

    GoFrame框架gcache的缓存控制淘汰策略实践示例

    这篇文章主要为大家介绍了GoFrame框架gcache的缓存控制淘汰策略的实践示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • 基于HLS创建Golang视频流服务器的优缺点

    基于HLS创建Golang视频流服务器的优缺点

    HLS 是 HTTP Live Streaming 的缩写,是苹果开发的一种基于 HTTP 的自适应比特率流媒体传输协议。这篇文章主要介绍了基于 HLS 创建 Golang 视频流服务器,需要的朋友可以参考下
    2021-08-08

最新评论