问题概述
问题:如何实现洗牌算法?
- 算法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()函数是伪随机方法,取决于随机数种子。