洗牌算法是一种将序列(如数组、列表)元素随机打乱的经典算法,核心目标是让每个元素在打乱后出现在任意位置的概率均等。在 C# 中,常用的洗牌算法有Fisher-Yates 洗牌算法(也称 Knuth 洗牌算法),它高效且公平,时间复杂度为 O (n),空间复杂度为 O (1)。

一、Fisher-Yates 洗牌算法原理

  1. 核心思想:从序列的最后一个元素开始,依次与前面的随机位置元素交换,直到处理完第一个元素。

  2. 公平性保证:每个元素被放置在任意位置的概率均为 1/n(n 为序列长度),避免了 “部分随机” 导致的分布不均问题。

二、C# 实现示例

以下是使用 Fisher-Yates 算法对整数数组、字符串列表进行洗牌的实现:

代码分块分析

这段代码是一个简化的斗地主游戏实现,主要包含扑克牌的生成、洗牌、发牌和排序功能。下面我将对代码进行分块分析。

1. 主程序结构与初始化

static void Main(string[] args)
{int[] ints1 = new int[54];ints1 = RandomUNorepeatArray(ints1);// 后续代码...
}

这部分代码首先创建了一个包含 54 个元素的整数数组ints1,并调用RandomUNorepeatArray方法生成 0-53 的随机不重复数组,用于作为扑克牌的随机索引。

2. 扑克牌对象模型

class Puke
{public string number;public char color;public override string ToString(){return $"[{number},{color}]";}
}

Puke类表示一张扑克牌,包含两个属性:

  • number:牌面数字(字符串类型,"1"-"13" 或 "joker")

  • color:花色(字符类型,' 黑 '、' 红 '、' 梅 '、' 方 ')

  • 重写的ToString方法用于格式化输出牌的信息

3. 扑克牌生成与初始化

Puke[] puke = new Puke[54];
int num = 1;
char[] str = new char[4] {'黑', '红', '梅', '方' };
int num2 = 3;
for (int i = 0; i < 52; i++)
{puke[i] = new Puke();if (num > 13){num = 1;num2--;}puke[i].number = num.ToString();num++;puke[i].color = str[num2];
}
puke[52] = new Puke { number = "joker", color = '黑' };
puke[53] = new Puke { number = "joker", color = '红' };

这段代码生成了 54 张扑克牌:

  • 前 52 张是四种花色的 A-K(用数字 1-13 表示)

  • 最后两张是大小王("joker")

4. 洗牌与发牌

Puke[] puke2 = new Puke[54];
for (int i = 0; i < 54; i++)
{puke2[i] = puke[ints1[i]];
}
​
// 发牌给三个玩家和底牌
Puke[] puke3 = new Puke[17];
Puke[] puke4 = new Puke[17];
Puke[] puke5 = new Puke[17];
Puke[] puke6 = new Puke[3];
for (int i = 0; i < 17; i++)
{puke3[i] = puke2[ints1[i]];puke4[i] = puke2[ints1[i + 17]];puke5[i] = puke2[ints1[i + 24]]; // 这里索引计算有问题!
}
for(int i = 0; i < 3; i++)
{puke6[i] = puke2[ints1[i+51]];
}

这部分代码实现了洗牌和发牌:

  • 使用随机索引数组ints1重新排列扑克牌数组

  • 将牌分发给三个玩家(各 17 张)和底牌(3 张)

5. 排序算法

Array.Sort(puke3, (a, b) =>
{int numA = a.number == "joker" ? 100 : int.Parse(a.number);int numB = b.number == "joker" ? 100 : int.Parse(b.number);int result = numA.CompareTo(numB);if (result == 0)return a.color.CompareTo(b.color);return result;
});
​
// 对puke4和puke5有相同的排序代码...

这部分代码对每个玩家的手牌进行排序:

  • 将牌面值转换为整数进行比较(Joker 设为 100)

  • 牌面值相同则比较花色

6. 辅助方法:生成随机不重复数组

static int[] RandomUNorepeatArray(int[] ints )
{int min = 0;int max = 53;int count = 54;
​List<int> pool = new List<int>();for (int i = min; i <= max; i++)pool.Add(i);
​Random rand = new Random();// 洗牌算法for (int i = pool.Count - 1; i > 0; i--){int j = rand.Next(0, i + 1);int temp = pool[i];pool[i] = pool[j];pool[j] = temp;}
​return pool.ToArray();
}

这个方法使用 Fisher-Yates 洗牌算法生成 0-53 的随机排列数组。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
​
namespace 斗地主
{internal class Program{static void Main(string[] args){int[] ints1 = new int[54];ints1 = RandomUNorepeatArray(ints1);
​// //Console.WriteLine(string.Join(" ", ints1));
​// string[] strings = new string[]//{//     "A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K",//     "A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K",//     "A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K",//     "A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K",//     "j1", "j2"//};
​// string[] strings2 = new string[60];
​// Random random = new Random();
​// for (int i = 0; i < ints1.Length; i++)// {//    // int ints3 = random.Next(ints1.Length);//     strings2[i] = strings[ints1[i]];// }// int num = 0;
​// foreach (string s in strings2)// {//     Console.Write($"{s,-4}");//     num++;//     if (num % 17 == 0) Console.WriteLine();// }
​Puke[] puke = new Puke[54];int num = 1;char[] str = new char[4] {'黑', '红', '梅', '方' };int num2 = 3;for (int i = 0; i < 52; i++){puke[i] = new Puke();if (num > 13){num = 1;num2--;}puke[i].number = num.ToString();num++;puke[i].color = str[num2];}puke[52] = new Puke { number = "joker", color = '黑' };puke[53] = new Puke { number = "joker", color = '红' };
​Puke[] puke2 = new Puke[54];for (int i = 0; i < 54; i++){puke2[i] = puke[ints1[i]];}
​int count = 0;foreach (var item in puke2){Console.Write($"{item,-4}");count++;if (count % 17 == 0) Console.WriteLine();
​//count++;//if (count%13 == 0) Console.WriteLine();}
​Puke[] puke3 = new Puke[17];Puke[] puke4 = new Puke[17];Puke[] puke5 = new Puke[17];Puke[] puke6 = new Puke[3];for (int i = 0; i < 17; i++){puke3[i] = puke2[ints1[i]];puke4[i] = puke2[ints1[i + 17]];puke5[i] = puke2[ints1[i + 24]];}for(int i = 0; i < 3; i++){puke6[i] = puke2[ints1[i+51]];}
​Array.Sort(puke3, (a, b) =>{int numA = a.number == "joker" ? 100 : int.Parse(a.number);int numB = b.number == "joker" ? 100 : int.Parse(b.number);int result = numA.CompareTo(numB);if (result == 0)return a.color.CompareTo(b.color);return result;});
​Array.Sort(puke4, (a, b) =>{int numA = a.number == "joker" ? 100 : int.Parse(a.number);int numB = b.number == "joker" ? 100 : int.Parse(b.number);int result = numA.CompareTo(numB);if (result == 0)return a.color.CompareTo(b.color);return result;});
​Array.Sort(puke5, (a, b) =>{int numA = a.number == "joker" ? 100 : int.Parse(a.number);int numB = b.number == "joker" ? 100 : int.Parse(b.number);int result = numA.CompareTo(numB);if (result == 0)return a.color.CompareTo(b.color);return result;});
​Console.WriteLine();Console.Write("=============================");Console.WriteLine();
​int c = 0;foreach (var item in puke3){Console.Write($"{item,-6}");//count++;//if (count%13 == 0) Console.WriteLine();}Console.WriteLine();foreach (var item in puke4){Console.Write($"{item,-6}");//count++;//if (count%13 == 0) Console.WriteLine();}Console.WriteLine();foreach (var item in puke5){Console.Write($"{item,-6}");//count++;//if (count%13 == 0) Console.WriteLine();}Console.WriteLine();foreach (var item in puke6){Console.Write($"{item,-6}");//count++;//if (count%13 == 0) Console.WriteLine();}
​//Array.Sort(puke2, (a, b) =>//{//    int result = a.number.CompareTo(b.number);//    if (result == 0)//    {//        return b.color.CompareTo(a.color);//    }//    return result;//});
​}
​//生成一定范围内随机不成重复数字的数组static int[] RandomUNorepeatArray(int[] ints ){int min = 0;int max = 53; // 生成1~20之间的不重复数字int count = 54; // 需要的数量
​List<int> pool = new List<int>();for (int i = min; i <= max; i++)pool.Add(i);
​//Fisher-Yates 洗牌算法Random rand = new Random();// 洗牌for (int i = pool.Count - 1; i > 0; i--){int j = rand.Next(0, i + 1);int temp = pool[i];pool[i] = pool[j];pool[j] = temp;}
​// 取前count个//for (int i = 0; i < count; i++)//{//    Console.Write(pool[i] + " ");//}//Console.WriteLine();
​
​return pool.ToArray();}}
​
​class Puke{// 牌的数字  A-K    用1-13表示public string number;// 牌的花色  黑红梅方   4321public char color;public override string ToString(){return $"[{number},{color}]";}
​}
​
}
​

三、代码说明

  1. 泛型方法Shuffle<T> 支持任意类型的数组和列表,通用性强。

  2. 随机索引生成random.Next(i + 1) 确保生成的索引 j[0, i] 范围内,避免越界。

  3. 元素交换:使用 C# 7.0 引入的元组交换语法 (a, b) = (b, a),简洁高效(也可使用临时变量交换)。

  4. Random 实例:在方法内创建单个 Random 实例,避免短时间内多次创建导致的随机序列重复问题。

四、算法优势

  • 公平性:每个元素在每个位置的概率严格相等,无偏差。

  • 高效性:仅需一次遍历和 n-1 次交换,时间复杂度 O (n),空间复杂度 O (1)(原地洗牌,无需额外空间)。

  • 适用性:适用于任何可索引的序列(数组、列表等),广泛应用于卡牌游戏、随机排序、数据打乱等场景。

五、注意事项

  • Random 的线程安全:若在多线程环境中使用,需确保 Random 实例的线程安全(可使用 Random.Shared 或加锁)。

  • 重复执行的随机性:若需每次运行生成不同的打乱结果,不要手动指定 Random 的种子(默认使用系统时间作为种子)。

示例输出

[8,梅][4,方][1,梅][3,梅][12,方][4,红][9,黑][2,方][13,梅][9,方][7,黑][joker,红][11,方][joker,黑][13,黑][9,红][6,红]
[10,红][12,黑][9,梅][11,黑][3,红][10,方][11,梅][12,梅][10,黑][6,梅][7,梅][2,红][5,梅][12,红][4,黑][3,黑][1,红]
[10,梅][8,红][6,方][5,红][11,红][1,黑][3,方][6,黑][5,黑][1,方][2,梅][13,红][8,方][4,梅][8,黑][5,方][2,黑]
[7,方][7,红][13,方]
=============================
[3,梅] [4,方] [4,梅] [4,黑] [5,梅] [7,方] [7,红] [7,黑] [9,红] [10,梅][10,黑][11,黑][13,方][13,梅][13,红][joker,红][joker,黑]
[2,红] [2,黑] [3,红] [5,方] [5,红] [5,黑] [6,梅] [6,黑] [7,梅] [8,红] [8,黑] [9,方] [9,梅] [10,红][11,梅][12,梅][12,黑]
[1,梅] [1,红] [1,黑] [4,红] [5,红] [5,黑] [6,方] [6,梅] [6,黑] [7,梅] [8,黑] [9,梅] [10,方][10,红][12,梅][12,红][12,黑]
[9,黑] [3,黑] [11,方]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/913951.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/913951.shtml
英文地址,请注明出处:http://en.pswp.cn/news/913951.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python PDFplumber详解:从入门到精通的PDF处理指南

一、PDFplumber核心优势解析 在数字化办公场景中&#xff0c;PDF文档处理是数据分析师和开发者的必备技能。相较于PyPDF2、pdfminer等传统库&#xff0c;PDFplumber凭借其三大核心优势脱颖而出&#xff1a; 精准表格提取&#xff1a;采用流式布局分析算法&#xff0c;支持复杂表…

Flutter 与 Android 的互通几种方式

Flutter 与 Android 的互通主要通过以下几种方式实现&#xff0c;每种方式适用于不同的场景&#xff1a;1. 平台通道&#xff08;Platform Channels&#xff09; Flutter 与原生 Android 代码通信的核心方式&#xff0c;支持双向调用。 类型&#xff1a; MethodChannel&#xf…

全新开源AI知识库系统!PandaWiki一键构建智能文档,支持AI问答、创作与搜索!

传统 Wiki 工具像一本厚重的“死书”&#xff0c;虽能存储信息&#xff0c;却无法主动「思考」。而在当今AI席卷各个行业的浪潮中&#xff0c;知识管理也迎来了智能化的巨大飞跃。最近开源圈悄然走红的 PandaWiki&#xff0c;就用 AI 大模型为知识库注入了 灵魂&#xff0c; 它…

Rust 结构体

Rust 结构体 引言 Rust 是一种系统编程语言,以其内存安全、并发支持和零成本抽象而闻名。结构体(struct)是 Rust 中用于创建自定义数据类型的工具。本文将深入探讨 Rust 结构体的概念、用法以及其在实际编程中的应用。 结构体的定义 在 Rust 中,结构体是一种复合类型,…

lstm 数据输入问题

lstm 我有 20*6 条数据&#xff0c;20个样本&#xff0c;每个样本6条历史数据&#xff0c;每条数据有5个值&#xff0c;我送给网络输入时应该是20*6*5 还是 6*20*5你的数据是&#xff1a;20 个样本&#xff08;batch size 20&#xff09;每个样本有 6 条历史数据&#xff08;s…

WPF打包exe应用的图标问题

目录 1、WPF打包方法 2、图标问题 1、WPF打包方法 使用Microsoft Visual Studio Installer Projects 2022工具打包&#xff08;成功&#xff09;&#xff0c;需要新建Setup Project项目进行打包 (46 封私信) [C#.net资料]visual studio打包可安装的exe程序(添加配置文件)&am…

Qt中处理多个同类型对象共享槽函数应用

一.Qt中处理多个同类型对象共享槽函数应用场景数字键盘按钮处理动态生成的控件管理工具栏按钮响应游戏中的网格点击处理使用时需特别注意对象生命周期管理和类型安全&#xff0c;现代Qt开发中更推荐使用Lambda表达式替代sender()机制。二.示例1.本文示例功能在ui界面添加5个&am…

康养休闲旅游服务实训室建设方案:理实一体化的产教融合方案

一、康养休闲旅游服务实训室建设方案建设原则对接行业真实场景&#xff1a;实训环境与设备设施严格参照健康咨询、旅行社及相关服务、住宿业、餐饮业等行业的真实职业场景搭建&#xff0c;确保实训项目与岗位工作内容高度匹配&#xff0c;实现工学结合、理实一体化教学。融合前…

微服务架构的演进:迈向云原生——Java技术栈的实践之路

随着云计算技术的快速发展&#xff0c;微服务架构正逐步向云原生&#xff08;Cloud Native&#xff09;演进。云原生不仅是一种技术体系&#xff0c;更是一种开发和运维理念的革新。本文将以Java技术栈为例&#xff0c;结合Kubernetes&#xff08;K8s&#xff09;、服务网格&am…

CVE-2025-32463复现

目录背景知识chroot环境chroot 环境的具体表现Name Service Switch (NSS)机制漏洞简介环境搭建复现POC分析防御方法参考文章&#xff1a;背景知识 chroot环境 &#xff08;全称“change root”&#xff09;是一种Unix/Linux系统中的隔离技术&#xff0c;其核心作用是将进程的…

原生微信小程序研发,如何对图片进行统一管理?

目标&#xff1a; 统一在配置文件中管理图片&#xff0c;用变量存储&#xff0c;换图标时只需修改链接即可&#xff0c;无需更改业务代码&#xff0c;且方便查找。tips: 不建议在 asset 中存储大量图片&#xff0c;原因是官方要求小程序内存要限制在2M以内&#xff0c;图片放多…

Escrcpy(手机投屏) v1.27.2 便携版

Escrcpy 是一款强大的工具&#xff0c;它允许用户通过图形化的 Scrcpy 界面来显示和控制他们的 Android 设备。这款应用程序由 Electron 作为其底层框架驱动。Escrcpy 无需任何账户就可以使用&#xff0c;无需担心隐私或安全问题。Escrcpy没有广告&#xff0c;完全免费开源。软…

element-plus表单校验失败问题

一、问题&#xff1a;做表单校验时&#xff0c;自定义校验和常规校验都失败&#xff0c;自定义校验时无法拿到value值。二、原因&#xff1a;1、变量名称那没有绑定prop。如果是常规校验&#xff0c;没绑定prop的话&#xff0c;在确定按钮时&#xff0c;valid都是true。2、自定…

jmeter做跨线程组

多线程通常会将不同的业务逻辑分配到不同的线程组中。为什么要做多线程&#xff1a;模拟真实世界场景&#xff1a;在实际应用中&#xff0c;服务器通常需要同时处理来自多个用户的请求。通过多线程&#xff0c;JMeter可以模拟这种并发用户的行为&#xff0c;更准确地反映出应用…

SQL实战:多表查询篇

文章目录多表查询创建练习用的数据库链接/连接查询交叉连接自然连接内连接(取交集)外连接左外连接/右外连接自连接子查询联合查询总结数据库的备份和恢复命令行操作多表查询 -- 获得 alice的 部门所在城市 select * from staff where namealice; -- 获得dept_id1 select city …

交通银行基于HarmonyOS数字盾服务,实现大额转账安全

在近日落幕的华为开发者大会2025&#xff08;6月20日-6月22日&#xff09;上&#xff0c;交通银行作为HarmonyOS安全合作的关键伙伴受邀出席。在大会的主题演讲上介绍了交通银行基于HarmonyOS SDK设备安全服务&#xff08;Device Security Kit&#xff09;中的数字盾服务&#…

加密狗硬复制的方法

加密狗硬复制方法概述&#xff1a;Greer82加密狗&#xff08;Dongle&#xff09;是一种硬件加密设备&#xff0c;用于软件版权保护。硬复制是指通过物理手段复制加密狗的硬件信息&#xff0c;通常涉及破解或仿制。需要注意的是&#xff0c;未经授权的复制可能涉及法律风险&…

家庭网络中的服务器怎么对外提供服务?

家庭网络中的服务器怎么对外提供服务&#xff1f;方案1 DDNS&#xff08;家庭网络需要有公网ip&#xff09;方案2 内网穿透&#xff08;需要有一台公网ip的服务器&#xff09;方案1 DDNS&#xff08;家庭网络需要有公网ip&#xff09; 怎么判断是否有公网ip&#xff1f;大致的流…

UnrealEngine5游戏引擎实践(C++)

目录 目录 目录 Unreal Engine 是什么? Unreal Engine 5 简介 核心技术特性 应用场景扩展 兼容性与生态系统 Unreal Engine安装 下载 Epic Games Launcher 启动 Unreal Engine 选择安装版本和路径 选择组件 开始安装 验证安装 配置项目模板(可选) 更新和插件…

web渗透sql注入4之PostgreSQL

web渗透sql注入4之PostgreSQLPostgreSQL数据库特性&#xff1a;基于角色的访问控制&#xff0c;支持超级用户&#xff0c;需安装 plpgsql 扩展方可执行命令&#xff0c;可通过 COPY命令或自定义函数实现权限判断白盒看代码&#xff0c;黑盒通过构造特殊查询语句探测数据库权限权…