天天看點

C語言 撲克牌洗牌發牌統計同花順個數程式一、2個算法關鍵點二、運作結果三、完整代碼

目錄

  • 一、2個算法關鍵點
    • 關鍵點1:洗牌算法
    • 關鍵點2:查找同花順算法
  • 二、運作結果
  • 三、完整代碼

題目:

一張撲克牌可用結構類型描述,一副撲克牌的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++;
           

二、運作結果

C語言 撲克牌洗牌發牌統計同花順個數程式一、2個算法關鍵點二、運作結果三、完整代碼

三、完整代碼

#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;
}
           

繼續閱讀