题目:
一张扑克牌可用结构类型描述,一副扑克牌的52张牌则是一个结构数组。
1、试编写洗牌函数和供4人玩牌的发牌函数。
2、统计出现同花顺的概率
提示:模拟发牌100000次(这个次数可用#define设置),程序最后统计如下数据
(1)出现同花顺的总个数;
(2)出现同花顺的发牌个数;
(3)发牌总次数;
(4)出现同花顺的发牌概率。
所使用的结构:
struct card{
char shape; // 花色首字母,即’S’, ‘H’, ‘C’, 'D’四种
int value; // 牌的大小,从2开始,JQKA分别为11, 12, 13, 14
};
5张同花连号算同花顺,连号规则为从2开始,到A结束。
如:7、8、9、10、J、Q、K只能算作7~J一个同花顺。
一、2个算法关键点
关键点1:洗牌算法
经典洗牌算法有很多,可以直接自行百度洗牌算法,理解一下不同的算法优劣。这里采用一种复杂度较小的算法Knuth-Durstenfeld Shuffle算法(算法2),它是Fisher-Yates Shuffle算法(算法1)的改进版。
问题描述:将一个有N个元素有序数组a[N]乱序,即洗牌。
算法1核心:从原始数组里随机抽取一个元素放在新的数组里,再从原数组剩下的元素里随机选一个元素依次放到新数组里,如此循环,直到原始数组里所有元素被抽取到新数组里,完成乱序(洗牌)。
算法复杂度:时间复杂度O(N*N),空间复杂度O(N)
优点:容易理解。
缺点:时间复杂度和空间复杂度都较大,抽取元素后还需在原数组中删去抽取掉的元素,较麻烦。
算法2核心:对算法1进行改进,不新建数组,而是在原数组内进行互换。从原始数组里随机选一个元素,与最后一个元素a[N-1]互换。然后在除最后一个元素a[N-1]外的其他元素里随机挑一个元素与倒数第二个元素a[N-2]互换。然后在除最后2个元素外的其他元素里随机挑一个元素与倒数第三个元素a[N-3]互换…直到抽取完毕,完成乱序(洗牌)。
算法复杂度:时间复杂度O(N),空间复杂度O(1)
优点:复杂度小,操作简单。
缺点:需知道数组大小N(不过这点较容易克服)。
洗牌程序:
采用算法2,对长为5的结构化数组c[len]进行洗牌,其中num_time是为在快速循环中为srand函数赋予随机数种子用的(因为在快速循环中程序运行很快,time()返回的时间秒数基本不变,若只采用time(NULL)作为srand的随机数种子是远远不够的)。
// 洗牌函数
// 算法:对于size为Num的数组array[Num],len=Num。每次随机产生一个[0, len)的数字x,
// 将array[x]与数组尾端array[len-1]调换,然后len减1,重复操作。
// 复杂度:时间复杂度O(N),空间复杂度O(1)
void Shuffle(struct card *c, int num_time)
{
int x, len = 52;
struct card temp;
// 初始化随机数种子
// srand((unsigned)time(NULL));
// 在循环中运算过快time可能不变,加上循环变量num_time保证随机数种子的不一样。
srand((unsigned)time(NULL) + num_time);
while (len > 1)
{
x = rand() % len;
temp = c[len - 1];
c[len - 1] = c[x];
c[x] = temp;
len--;
}
}
关键点2:查找同花顺算法
一种容易想到的算法是:
(1)发牌:洗牌后,按顺序发给四个人,每人13张牌,假设某人领到的牌存在结构数组a0[13]里。
(2)排序:先对a0[13]根据元素value值大小进行排序,冒泡、快排等算法均可,这里因元素较少(13个)采用冒泡,若想更快可采用快排。先不分花色就进行排序,后续操作就不用排序了,可使得进一步的操作——分花色和查找顺子更简单。
(3)分花色:因不知各花色各有多少张牌,所以取最大值13,,定义4个大小为13的int型数组(C语言必须在定义数组时规定大小)。然后遍历排序后的a0[13]元素,按花色分到4个数组array里,只存value值,并注意保存每个数组的元素个数。
(4)找顺子:因为此人手中的13张牌按花色存到了4个int数组里,所以问题变成了:在一个长为len的int数组array[len]里查找5顺子的个数。这里可新建一个函数解决这个问题。
算法核心:先确认数组元素是否大于5,然后从array[0]到array[len-5],确认每一个元素与其后4个元素构成公差为1的递增等差数列(5连顺子)。若出现一个顺子,将下标元素变到当前顺子后的第一个元素,在程序中为i+=4,因为i++还会再+1,所以是一共是+5。这样就可以保证满足题目中“7,8,9,10,J,Q,K中只有 7~J一个顺子”的要求。
(5)相加:分别计算此人4种花色数组里的5连顺子个数,并相加,即为此人手里的同花顺个数。同理,计算此次发牌后4人的同花顺个数相加,即为此次发牌的同花顺个数。
注意:
出现同花顺个数和出现同花顺的发牌次数并不相同,因为一次发牌可能有多个同花顺出现。这里的代码运用了一个技巧,先统计每次发牌后同花顺个数是否增加,若增加(改变)了,即证明此次发牌有同花顺出现,出现同花顺的发牌次数自增1即可。
if (Num_tong != Num_tong_org)
Num_tong_fa++;
二、运行结果
三、完整代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// 模拟发牌总次数
#define N 100000
// 统计个数
int Num_tong = 0; // 出现同花顺个数
int Num_tong_fa = 0; // 出现同花顺的发牌次数
double P_tong = 0.0; // 出现同花顺的概率
// 牌的花色数组
char shapes[4] = { 'S', 'H', 'C', 'D' };
// 定义牌的结构体
struct card {
char shape;
int value;
};
// 4人各自的牌
struct card a0[13], a1[13], a2[13], a3[13];
// 初始化一副牌函数
void Init_cards(struct card *c)
{
int i, j;
for (i = 0; i < 13; i++)
{
c[4 * i].value = c[4 * i + 1].value = \
c[4 * i + 2].value = c[4 * i + 3].value = i + 2;
for (j = 0; j <= 3; j++)
{
c[4 * i + j].shape = shapes[j];
}
}
}
// 打印一副牌函数
void Print_cards(struct card *c, int len)
{
int i;
for (i = 0; i < len; i++)
{
printf("%2d: %2d %c\n", i, c[i].value, c[i].shape);
}
}
// 洗牌函数
// 算法:对于size为Num的数组array[Num],len=Num。每次随机产生一个[0, len)的数字x,
// 将array[x]与数组尾端array[len-1]调换,然后len减1,重复操作。
// 复杂度:时间复杂度O(N),空间复杂度O(1)
void Shuffle(struct card *c, int num_time)
{
int x, len = 52;
struct card temp;
// 初始化随机数种子
// srand((unsigned)time(NULL));
// 在循环中运算过快time可能不变,加上循环变量num_time保证随机数种子的不一样。
srand((unsigned)time(NULL) + num_time);
while (len > 1)
{
x = rand() % len;
temp = c[len - 1];
c[len - 1] = c[x];
c[x] = temp;
len--;
}
}
// 排序函数:将发到每个人手上的牌按大小排序,便于归类花色
void Sort_cards(struct card *c, int len)
{
int i, j;
struct card temp;
for (i = len - 1; i > 0; i--)
{
for (j = 0; j < i; j++)
{
if (c[j].value > c[j + 1].value)
{
temp = c[j + 1];
c[j + 1] = c[j];
c[j] = temp;
}
}
}
}
// 发牌函数:洗牌后,按循环给4个人发牌
void Deal(struct card *c)
{
int i;
// 给4人发牌
for (i = 0; i < 13; i++)
{
a0[i] = c[4 * i];
a1[i] = c[4 * i + 1];
a2[i] = c[4 * i + 2];
a3[i] = c[4 * i + 3];
}
// 给4人的牌排序,以便后续分花色和查找同花顺
Sort_cards(a0, 13);
Sort_cards(a1, 13);
Sort_cards(a2, 13);
Sort_cards(a3, 13);
}
// 打印数组
void Print_array(int *array, int len)
{
int i;
for (i = 0; i < len; i++)
{
printf("%d ", array[i]);
}
printf("\n");
}
// 数组同花顺函数:判断数组中是否含有同花顺,返回同花顺个数
int Have_shunzi(int *array, int len)
{
int i, j;
int Num = 0, Num_org = 0;
if (len >= 5)
{
for (i = 0; i <= (len - 5); i++)
{
Num_org = Num;
for (j = i; j < i + 4; j++)
{
if (array[j + 1] == (array[j] + 1))
{
if (j == (i + 3))
{
Num++;
printf("%d %d %d %d %d\n", array[i], array[i + 1], array[i + 2], array[i + 3], array[i + 4]);
}
continue;
}
else break;
}
// 若出现同花顺,将遍历下标调到后面第5个
if (Num_org != Num)
i += 4;
}
}
return Num;
}
// 结构体同花顺函数:判断某人的牌里是否含有同花顺,返回同花顺个数
int Find_cards(struct card *c, int len)
{
int i, j, k, l, m;
int array[4][13];
j = k = l = m = 0;
// 把某人手中的牌按花色分开
for (i = 0; i < len; i++)
{
if (c[i].shape == 'S')
{
array[0][j] = c[i].value;
j++;
}
else if (c[i].shape == 'H')
{
array[1][k] = c[i].value;
k++;
}
else if (c[i].shape == 'C')
{
array[2][l] = c[i].value;
l++;
}
else
{
array[3][m] = c[i].value;
m++;
}
}
// 统计各花色中出现同花顺的个数
return Have_shunzi(array[0], j) + Have_shunzi(array[1], k) + Have_shunzi(array[2], l) + Have_shunzi(array[3], m);
}
// 主函数
int main(void)
{
int i;
int Num_tong_org = 0;
// 定义一副牌
struct card new_cards[52];
// 初始化牌
Init_cards(new_cards);
// 循环发牌N次,统计出现同花顺的数据
for (i = 0; i < N; i++)
{
Num_tong_org = Num_tong;
Shuffle(new_cards, i);
Deal(new_cards);
Num_tong += Find_cards(a0, 13) + Find_cards(a1, 13) + Find_cards(a2, 13) + Find_cards(a3, 13);
if (Num_tong != Num_tong_org)
Num_tong_fa++;
}
// 计算出现同花顺的发牌概率
P_tong = (double)Num_tong_fa / (double)N;
// 打印信息
printf("\n");
printf("每次发牌结果数据过多,以上为出现的同花顺情况。\n");
printf("\n");
printf("统计数据如下:\n");
printf("1、出现同花顺的总个数: %d\n", Num_tong);
printf("2、出现同花顺的发牌次数: %d\n", Num_tong_fa);
printf("3、发牌总次数: %d\n", N);
printf("4、出现同花顺的发牌概率: %f\n", P_tong);
return 0;
}