C语言 扑克牌洗牌发牌统计同花顺个数程序

题目:
一张扑克牌可用结构类型描述,一副扑克牌的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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值