Problem overview
Question: How to implement the shuffling algorithm?
Algorithm 1: According to the general idea, assume N cards, randomly exchange two of them at a time, cycle N times, and finally form a card sequence.
Algorithm 2: Simulate the shuffling strategy in real life, first shuffle the cards in two piles, then cut the cards, repeat this process a certain number of times, and finally form a card sequence.
Algorithm 3: Take a method similar to drawing lots, initialize an ordered deck, randomly draw one card from the deck each time, and do not put the drawn cards back until the deck is empty, and finally form the order in which the cards were drawn. A card sequence.
Algorithm implementation
#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;
}
operation result
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
Analysis of Algorithms
From the running results, the random effect of Algorithm 1 is not ideal, and it can even be said to be a wrong algorithm. Algorithm 2 has a good random effect, but the time and space complexity are not good enough. Algorithm 3 has both O(N time and space complexity. ), and the random effect is good.
Remark 1: Algorithm 3 As long as the method of drawing cards is randomly selected with equal probability, the algorithm is theoretically guaranteed in probability, that is, the probability of each card appearing in each position is the same, which will not be described in detail here.
Remark 2: The rand() function is a pseudo-random method and depends on the random number seed.