C#洗牌算法

洗牌算法是一種將序列(如數組、列表)元素隨機打亂的經典算法,核心目標是讓每個元素在打亂后出現在任意位置的概率均等。在 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;黑盒通過構造特殊查詢語句探測數據庫權限權…