C语言经典算法之洗牌算法

目录

前言

A.建议

B.简介

一 代码实现

二 时空复杂度

A.时间复杂度:

B.空间复杂度:

C.总结:

三 优缺点

A.优点:

B.缺点:

C.总结:

四 显示中的应用


前言

A.建议

1.学习算法最重要的是理解算法的每一步,而不是记住算法。

2.建议读者学习算法的时候,自己手动一步一步地运行算法。

B.简介

在C语言中实现经典的洗牌算法,我们通常指的是Fisher-Yates(也称为Knuth-Shuffle)算法。该算法保证了随机均匀打乱一个数组中的元素顺序。

一 代码实现

以下是一个简单的Fisher-Yates洗牌算法在C语言中的实现:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void shuffle(int array[], int n) {
    srand(time(NULL)); // 设置随机种子,通常在程序开始时只执行一次
    for (int i = n - 1; i > 0; --i) {
        int j = rand() % (i + 1);
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
}

int main() {
    int deck[52]; // 假设我们有一个52张牌的数组,从0到51编号

    // 初始化牌组
    for (int i = 0; i < 52; ++i) {
        deck[i] = i; // 将牌编号从0到51依次放入数组,代表从第一张牌到最后一张牌
    }

    // 执行洗牌操作
    shuffle(deck, 52);

    // 输出洗牌后的结果
    printf("洗牌后的牌序:\n");
    for (int i = 0; i < 52; ++i) {
        printf("%d ", deck[i]);
        if ((i + 1) % 13 == 0) { // 每打印完一副牌的13张就换行
            printf("\n");
        }
    }

    return 0;
}

现在,这个程序首先会创建一个代表52张牌的数组并按顺序初始化。然后它会调用shuffle函数进行洗牌,最后输出洗牌后每张牌的位置。这里只是简单地打印出数字,实际应用中可以将这些数字与具体的牌面值对应起来显示。

解释:

  • 函数shuffle接受一个整数数组和它的大小作为参数。
  • 首先,使用srand(time(NULL))设置随机数生成器种子,通常只在程序启动时调用一次,以确保每次运行都能产生不同的随机序列。
  • 然后,从数组的最后一个元素开始向前遍历,对于每个元素,生成一个介于0(包含)和当前元素索引(包含)之间的随机数。
  • 使用临时变量交换数组中当前位置(i)的元素和随机选出的元素(j)。
  • 重复这个过程直到遍历完数组的所有元素,这样就完成了对整个数组的随机打乱。

请注意,上述实现假定rand()函数能够生成均匀分布的随机数,实际情况中,rand()的质量可能受限于具体的C语言实现,更高质量的随机数生成器可能需要使用其他库提供的功能。在现代编程实践中,建议使用<random>库提供的高质量随机数生成器替代标准库中的rand()函数。

二 时空复杂度

A.时间复杂度

Fisher–Yates shuffle的时间复杂度是O(n),其中n是数组中的元素数量。这是因为算法只需遍历数组一次,并在每个位置上执行一次随机选择和交换操作。

B.空间复杂度

Fisher–Yates shuffle是in-place算法,意味着它不需要额外的空间来存储数据,因此其空间复杂度是O(1)。也就是说,除了输入数组本身外,算法所需辅助空间并不随数组规模的增长而增长。

C.总结:

总结一下:

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

在上述算法实现中,正确版本的时间复杂度已经被修正为O(n),而不是一些早期资料中可能提到的O(n²)。这个算法因其高效性和能确保均匀打乱数组而广受欢迎。在给定的C语言代码实现中,shuffle函数即实现了这一算法。

三 优缺点

A.优点:

  1. 均匀随机性:Fisher-Yates(Knuth)洗牌算法保证了每个排列都是等可能的结果,因此生成的随机序列具有良好的随机性和公平性。

  2. 原地操作:该算法在原数组上直接进行操作,不需要额外的存储空间,空间复杂度为O(1)。

  3. 简单易懂:算法逻辑清晰,易于理解和实现,仅需要遍历数组一次并对数组元素进行随机置换即可。

  4. 高效性:时间复杂度为O(n),在处理大规模数据时依然保持较好的性能。

B.缺点:

  1. 随机数质量依赖:算法的有效性高度依赖于随机数生成器的质量。若随机数生成器不能产生均匀分布的随机数,可能会导致洗牌效果不佳。

  2. 无序性检查:算法没有内置机制来检查是否已经达到无序状态,如果数组已经处于随机状态再进行洗牌,将会浪费不必要的计算资源。

  3. 偶然的不均衡:在某些特定情况下,如果随机数生成器的质量不高或者种子选取不当,可能出现偶然的局部不均衡现象,即某些排列出现的概率稍高。

  4. 并发安全性:在多线程环境下,如果不采取同步措施,可能会产生竞争条件(race condition),导致洗牌结果不可预测。

C.总结:

总体而言,Fisher-Yates洗牌算法是实践中广泛应用的经典算法,尤其是在需要随机排列数组元素的各种场景中,其简单性和高效性使之成为首选方案。然而,必须确保所使用的随机数生成器具有足够的随机性和质量。在多线程或并发环境中使用时,应添加适当的锁或其他同步机制以保证正确性。

四 显示中的应用

洗牌算法在现实生活中的应用广泛而多样,以下是几个典型的应用实例:

  1. 游戏开发

    • 在许多桌面和在线游戏中,如扑克、麻将、UNO等卡牌游戏中,为了保证游戏的公正性和随机性,每当新的一局开始时,都需要对整副牌进行随机洗牌,确保每一轮发牌给玩家的牌都是随机排列的。
  2. 教育应用

    • 教育软件中,用于随机排列试题、练习题或者课堂测试题目,避免学生按照固定顺序记忆答案。
  3. 音乐播放器

    • 音乐播放器中的“随机播放”功能就是利用洗牌算法实现的,它可以让用户听歌列表中的歌曲以一种看似随机的顺序播放,提高用户体验。
  4. 广告轮播

    • 在网站或应用程序中展示广告时,为了避免广告曝光次数过于集中,可采用洗牌算法来决定广告的显示顺序,实现广告位的公平轮换。
  5. 数据分析和机器学习

    • 在数据预处理阶段,有时需要对样本集进行随机化处理,以减少训练模型时的数据偏见,这时会用到洗牌算法。
  6. 实验设计

    • 在社会科学、医学和其他领域做随机化实验时,需要将试验对象随机分配到不同的组别,确保实验组和对照组的构成是随机且公平的。
  7. 网站推荐系统

    • 推荐系统中,洗牌算法可以帮助生成个性化的商品、文章或者其他内容的随机推荐序列,增加用户的探索性和新鲜感。

总之,任何需要通过随机排列一组元素来增加多样性和公平性的场合,都可能是洗牌算法发挥作用的地方。

  • 17
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 下面是一个用Python实现扑克牌洗牌的程序示例: ```python import random # 初始化一副扑克牌 suits = ['♠', '♥', '♦', '♣'] ranks = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A'] deck = [(s, r) for s in suits for r in ranks] # 洗牌 random.shuffle(deck) # 输出洗好的牌 for card in deck: print(f'{card[0]}{card[1]}', end=' ') ``` 首先,我们初始化一副扑克牌,其中`suits`表示花色,`ranks`表示点数,通过列表生成式生成每张牌的元组`(s, r)`。然后,使用Python内置的`random.shuffle()`函数进行洗牌。最后,使用一个循环遍历每张牌并输出。这里使用了f-string格式化字符串的方法,将花色和点数拼接起来输出。 输出结果可能类似于: ``` ♣K ♣7 ♥10 ♠Q ♥2 ♠8 ♠A ♦J ♣J ♠9 ♠7 ♠6 ♣5 ♥6 ♦2 ♣Q ♥8 ♠2 ♦K ♦A ♠3 ♦10 ♣8 ♦Q ♣3 ♦9 ♣A ♠10 ♣6 ♥J ♦7 ♠5 ♥9 ♠J ♣10 ♦6 ♥K ♦8 ♣9 ♣4 ♥5 ♦4 ♦3 ♥4 ♠4 ♥7 ♣2 ♦5 ♠K ``` ### 回答2: 扑克牌洗牌程序可以使用Python进行编写。 首先,我们需要定义扑克牌的花色和点数。可以使用两个列表来存储花色和点数的信息: ```python suits = ['♠', '♥', '♦', '♣'] ranks = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'] ``` 接下来,我们可以使用嵌套循环来生成一副完整的扑克牌,并存储在一个列表中: ```python deck = [] for suit in suits: for rank in ranks: card = suit + rank deck.append(card) ``` 在洗牌之前,我们需要引入`random`库,以便对扑克牌进行随机排序: ```python import random ``` 现在,我们可以使用`random.shuffle()`函数对扑克牌进行洗牌: ```python random.shuffle(deck) ``` 洗牌完成后,可以通过打印列表中的元素来显示洗好的扑克牌: ```python for card in deck: print(card) ``` 完整的程序如下: ```python import random suits = ['♠', '♥', '♦', '♣'] ranks = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'] deck = [] for suit in suits: for rank in ranks: card = suit + rank deck.append(card) random.shuffle(deck) for card in deck: print(card) ``` 执行这段代码,将会按照洗牌后的顺序打印出一副完整的扑克牌。这就是用Python编写扑克牌洗牌程序的方法。 ### 回答3: 扑克牌洗牌是指将一副牌中的所有牌打乱顺序,重新排列成随机的顺序。以下是用Python编写的一个简单的扑克牌洗牌程序示例: ```python import random # 定义扑克牌的花色和点数 suits = ['♠️', '♥️', '♣️', '♦️'] ranks = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'] # 创建一副扑克牌 deck = [] for suit in suits: for rank in ranks: deck.append(suit + rank) # 洗牌 random.shuffle(deck) # 输出洗牌后的扑克牌 for card in deck: print(card, end=' ') ``` 这个程序首先定义了扑克牌的花色和点数。然后通过两个嵌套的循环生成了一副完整的扑克牌,将每张牌的花色和点数组合在一起后存入列表`deck`中。 接下来使用`random.shuffle()`函数对`deck`列表进行洗牌,该函数会随机打乱列表中的元素顺序。 最后,使用循环逐个输出洗牌后的扑克牌,每张牌之间用空格隔开。运行程序后,就能看到洗牌后的扑克牌序列。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JJJ69

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值