洗牌算法分析

问题概述

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

猜你喜欢

转载自blog.csdn.net/cpyname/article/details/77484647