洗牌算法分析

问题概述

问题:如何实现洗牌算法?

  • 算法1:按一般的想法,假定N张牌,每次随机交换其中两张,循环N次,最后形成一个牌序。
  • 算法2:模拟现实生活中洗牌策略,先分两堆洗牌,再切牌,再重复这个过程一定次数,最后形成一个牌序。
  • 算法3:采取类似于抽签的方法,初始化一个有序牌堆,每次都从牌堆中随机抽取一张,抽出的牌不放回,直到牌堆为空,最后按抽取出牌的顺序组成一个牌序。

算法实现

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

#define		POKER_NUM		10 // 共有10张牌, 标号为: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

#define		TEST_NUM		1000000  //测试100万次

// 洗牌后结果, 其中g_aShuffleTestResult[i][j]表示标号为i+1的牌出现在数组j位置的次数  
int g_aShuffleTestResult[POKER_NUM][POKER_NUM] = {0};  

void print_shuffle_result()
{
	int i = 0;
	int j = 0;

	for(i = 0; i < POKER_NUM; i++)
	{
		if (0 == i)
			printf("   ");

		printf("%8d", i+1);
	}
	
	printf("\n");
	printf("    ---------------------------------------------------------------------------------\n");

	for (i = 0; i < POKER_NUM; i++){
		for(j = 0; j< POKER_NUM; j++){
			if (0==j)
				printf("%2d|", i+1);
			printf("%8d", g_aShuffleTestResult[i][j]);
		}
		printf("\n");
	}
	printf("\n");
}

// poker初始化  
void InitArray(int array[], int num)
{
	int i = 0;
	
	for(i = 0; i < num; i++)
		array[i] = i + 1; 
}

void PrintArray(int array[], int num)
{
	int i = 0;
	for(i = 0; i < num; i++)
		printf("%6d", array[i]);
	printf("\n\n");
}

// 一般洗牌算法
void shuffle_array_general(int array[], int num)
{	
	int i = 0;
	int temp = 0;
	int r1 = 0;
	int r2 = 0;

	// 随机交换
	for (i = 0; i < num; i++){
		r1 = rand()%num;
		r2 = rand()%num;
		temp = array[r1];
		array[r1] = array[r2];
		array[r2] = temp;
	}
}

// 模拟现实生活中洗牌
void shuffle_array_manual(int array[], int num)
{
	int i = 0;
	int j = 0;
	int temp = 0;
	int rstartnum = 0;
	int rdiynum = 0;
	int mid = num/2;
	int* pbuf = (int*)malloc(sizeof(int)*num);
	if (NULL == pbuf)
		return;
	
#define LOOP_NUM		5
	
	for (int index = 0; index < LOOP_NUM; index++){
		
		// 两手洗牌
		for (i = 1; i < mid; i+=2){
			temp = array[i];
			array[i] = array[mid+i];
			array[mid+i] = temp;
		}
		
		//随机切牌
		memset(pbuf, 0, sizeof(pbuf));
		
		for (j = 0; j < LOOP_NUM; j++){
			rstartnum = rand()%(num-1)+1; // 从开始到切牌位置的牌数
			rdiynum = rand()%(mid)+1; // 切牌数,每次切不超过一半
			
			if (rstartnum+rdiynum>num)
				rdiynum = num - rstartnum;  // 保证从起始位置的切牌数不越界
			
			memcpy(pbuf, array, rstartnum*sizeof(int)); // pbuf用于中转从开始到切牌位置的牌
			memcpy(array, array+rstartnum, rdiynum*sizeof(int));
			memcpy(array+rdiynum, pbuf, rstartnum*sizeof(int));
		}
	}
	
	free(pbuf);
}

// fisher_yates算法
void shuffle_array_fisher_yates(int array[], int num)
{
	int i = num;
	int r = 0;
	int temp = 0;

	if (0==i)
		return;

	while(--i)
	{
		r = rand()%(i+1);
		temp = array[i];
		array[i] = array[r]; // 每次循环随机取出一张牌,放在i位置
		array[r] = temp;
	}
}

int main()
{
	srand(time(NULL));
	clock_t cBegin, cTime = 0;

	int i = 0;
	int j = 0;
	int k = 0;
	int array[POKER_NUM]={0};

	cBegin = clock();
	printf("[shuffle_array_general]\n");
	memset(g_aShuffleTestResult, 0, sizeof(g_aShuffleTestResult));
	for (i=0; i<TEST_NUM; i++){
		InitArray(array, POKER_NUM);
//		PrintArray(array, POKER_NUM);
		shuffle_array_general(array, POKER_NUM);
//		PrintArray(array, POKER_NUM);

		for (j=0; j< POKER_NUM; j++){ // j遍历array数组
			for (k=0; k<POKER_NUM; k++){ // k+1表示牌号
				if (k+1 == array[j]) 
					g_aShuffleTestResult[k][j]++; // k+1这张牌出现在j位置,计数加1
			}
		}
	}
	cTime = clock() - cBegin;

	print_shuffle_result();
#ifndef  WIN32
	cTime=cTime/1000;
#endif
	printf("Method shuffle_array_general spend %d ms.\n\n", cTime);


	cBegin = clock();
	printf("\n[shuffle_array_manual]\n");
	memset(g_aShuffleTestResult, 0, sizeof(g_aShuffleTestResult));
	for (i=0; i<TEST_NUM; i++){
		InitArray(array, POKER_NUM);
		shuffle_array_manual(array, POKER_NUM);
		
		for (j=0; j< POKER_NUM; j++){ // j遍历array数组
			for (k=0; k<POKER_NUM; k++){ // k+1表示牌号
				if (k+1 == array[j]) 
					g_aShuffleTestResult[k][j]++; // k+1这张牌出现在j位置,计数加1
			}
		}
	}
	cTime = clock() - cBegin;
		
	print_shuffle_result();
#ifndef  WIN32
	cTime=cTime/1000;
#endif
	printf("Method shuffle_array_manual spend %d ms.\n\n", cTime);


	cBegin = clock();
	printf("\n[shuffle_array_fisher_yates]\n");
	memset(g_aShuffleTestResult, 0, sizeof(g_aShuffleTestResult));
	for (i=0; i<TEST_NUM; i++){
		InitArray(array, POKER_NUM);
		shuffle_array_fisher_yates(array, POKER_NUM);
		
		for (j=0; j< POKER_NUM; j++){ // j遍历array数组
			for (k=0; k<POKER_NUM; k++){ // k+1表示牌号
				if (k+1 == array[j]) 
					g_aShuffleTestResult[k][j]++; // k+1这张牌出现在j位置,计数加1
			}
		}
	}
	cTime = clock() - cBegin;
		
	print_shuffle_result();
#ifndef  WIN32
	cTime=cTime/1000;
#endif
	printf("Method shuffle_array_fisher_yates spend %d ms.\n\n", cTime);

	
	return 0;
}

运行结果

  • CPU:Intel Core i3 2120
  • RAM:2G
[shuffle_array_general]
          1       2       3       4       5       6       7       8       9      10
    ---------------------------------------------------------------------------------
 1|  196104   89271   89117   89314   89263   89533   89300   89484   89308   89306
 2|   89368  196844   89240   89468   89384   88985   89118   89202   89598   88793
 3|   88773   89346  197025   89132   89304   89831   89176   88605   89425   89383
 4|   89677   89065   89213  196401   89402   89204   89852   89096   88876   89214
 5|   89345   88894   89124   89259  196925   89680   89747   89523   89009   88494
 6|   89801   88930   89200   89194   89346  196636   89079   89286   89277   89251
 7|   89444   89763   88573   89144   88780   89040  196286   89490   89842   89638
 8|   89184   89481   89095   89679   89282   88864   89361  197035   88862   89157
 9|   89030   89152   89606   88720   89490   89133   89326   89297  196337   89909
10|   89274   89254   89807   89689   88824   89094   88755   88982   89466  196855

Method shuffle_array_general spend 454 ms.


[shuffle_array_manual]
          1       2       3       4       5       6       7       8       9      10
    ---------------------------------------------------------------------------------
 1|   99709  100129  100290   99846  100373   99925   99753  100397   99776   99802
 2|   99691  100237  101140   99815   99316  100126   99285   99904  100157  100329
 3|  100154  100540  100028  100117   99751   99816   99811   99809   99767  100207
 4|   99826  100248  100545   99586   99913  100392   99824   99796  100118   99752
 5|  100150   99857   99834   99810  100136  100476  100138  100008   99566  100025
 6|   99789   99942  100072  100074   99780   99416  100043  100178  100353  100353
 7|   99781   99931   98956  100351  100547   99343  100316  100542  100197  100036
 8|   99776   99720   99804   99846  100301  100625  100392   99664   99978   99894
 9|  100830   99764   99649  100320   99793   99821  100387   99524  100000   99912
10|  100294   99632   99682  100235  100090  100060  100051  100178  100088   99690

Method shuffle_array_manual spend 2246 ms.


[shuffle_array_fisher_yates]
          1       2       3       4       5       6       7       8       9      10
    ---------------------------------------------------------------------------------
 1|   99765  100052   99801  100327   99679  100406  100443   99911  100086   99530
 2|  100149  100223  100506   99888  100031   99846  100140   99822   99762   99633
 3|  100471  100452   99336   99855   99912  100071  100125  100494   99280  100004
 4|   99587  100195  100107  100390   99688  100116  100412   99954   99570   99981
 5|  100396   99570   99774   99744  100224   99897   99767   99961  100025  100642
 6|  100329   99813  100053  100299   99850   99814   99360  100094  100179  100209
 7|   99596  100165  100067   99558  100251   99983  100086   99964  100447   99883
 8|   99810   99731   99922  100125  100493   99690  100290   99845  100060  100034
 9|  100357   99470  100172  100062   99815  100148   99867   99875  100279   99955
10|   99540  100329  100262   99752  100057  100029   99510  100080  100312  100129

Method shuffle_array_fisher_yates spend 415 ms.

Press any key to continue

算法分析

从运行结果来看,算法1随机效果不够理想,甚至可以说是一种错误的算法,算法2随机效果不错,但是时间和空间复杂度不够好,算法3时间和空间复杂度均为O(N),且随机效果不错。

  • 备注1:算法3只要抽取牌的方法是等概率随机抽取,那该算法在概率学上是有理论保证的,即每张牌出现在每个位置的概率是一样的,在此不详述。
  • 备注2:rand()函数是伪随机方法,取决于随机数种子。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值