洗牌算法分析总结

先定义出一个基本概念:究竟洗牌算法的本质是什么?也就是说,什么样的洗牌结果是“正确”的?一个比较确切的定义,在经过洗牌函数后,如果能够保证每一个数据出现在所有位置的概率是相等的,那么这种算法是符合要求的。在这个前提下,尽量降低时间复杂度和空间复杂度就能得到好的算法。


第一个算法


随机抽出一张牌,检查这张牌是否被抽取过,如果已经被抽取过,则重新抽取,直到找到没被抽出过的牌,然后把这张牌放入洗好的队列中,重复该过程,直到所有的牌被抽出。它符合我们对于洗牌算法的基本要求,但它的复杂度为O(N^2),而且需要额外的内存空间保存已经被抽出的牌的索引。所以当数据量比较大时,会极大降低效率。


第二个算法


设牌的张数为n,首先准备n个不容易碰撞的随机数,然后进行排序,通过排序可以得到一个打乱次序的序列,按照这个序列将牌打乱。这也是一个符合要求的算法,但是同样需要额外的存储空间,在复杂度上也会取决于所采用的排序算法。


第三个算法


每次随机抽出两张牌交换,重复交换一定次数次后结束。这是一个常见的洗牌方法,比较有意思的问题是其中的“交换次数”,我们该如何确定一个合适的交换次数?简单的计算,交换m次后,具体某张牌始终没有被抽到的概率为((n-2)/n)^m,如果我们要求这个概率小于1/1000,那么 m>-3*ln(10)/ln(1-2/n),对于52张牌,这个数大约是176次,需要注意的是,这是满足“具体某张牌”始终没有被抽到的概率,如果需要满足“任意一张牌”没被抽到的概率小于1/1000,需要的次数还要大一些,需要交换280次才能符合要求。
void shuffle_3(int data[], int length)
{
#define COUNT 54
     int i = 0, idx1 = 0, idx2 = 0;
     for(i = 0; i < COUNT; i++)
     {
          idx1 = fix_random(0, length - 1);
          idx2 = fix_random(0, length - 1);
          swap(&data[idx1], &data[idx2]);
     }
     return;
}


 

第四个算法


从第一张牌开始,将每张牌和随机的一张牌进行交换。很明显,这个算法是符合我们先前的要求的,时间复杂度为O(N),而且也不需要额外的临时空间,似乎我们找到了最优的算法,然而事实并非如此。
void shuffle_4(int data[], int length)
{
     int i = 0, idx = 0;
     for(i = 0; i < length; i++)
     {
          idx = fix_random(0, length - 1);
          swap(&data[idx], &data[i]);
     }
     return;
}


 

第五个算法

 
从第一张牌开始,将每张牌和这张牌之前的随机的一张牌进行交换。
void shuffle_5(int data[], int length)
{
     int i = 0, idx = 0;
     for(i = 0; i < length; i++)
     {
          idx = fix_random(0, i);
          swap(&data[idx], &data[i]);
     }
     return;
}


 

总结

 

实际排列的可能情况有N!种;算法四的输出有N*N中,算法五的输出有N!种,而且N*N不能被N!整除,所以经过算法四所定义的牌与牌之间的交换程序,很可能一张牌被换来换去又被换回到原来的位置,所以这个算法不是最优的。而算法五输出的可能组合恰好是n!种,所以这个算法才是完美的。

 

洗牌程序

 

将54张扑克牌按顺序编号,0和53为大王和小王,1~12为黑色,13~24为红色,25~36为花色,37~52为方片

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

inline void swap(int *a, int *b)
{
     int tmp = 0;
     tmp = *a;
     *a = *b;
     *b = tmp;
}

inline int fix_random(int start, int end)
{
     return start + rand()%(end -start + 1);
}


void init_card(int data[], int length)
{
     int i = 0;
     for(i = 0; i < length; i++)
     {
          data[i] = i;
     }
     return;
}

void out_card(int data[], int length)
{
#define NUMPERLINE 6
     int i = 0;
     for(i = 0; i < length; i++)
     {
          printf("%d\t",data[i]);
          if( (i+1) % NUMPERLINE == 0)
          {
               printf("\n");    
          }
     }
     printf("\n");
     return;
}

#define CARD_NUM 54
int main()
{
     int *data = (int *)malloc(CARD_NUM * sizeof(int));
     if(NULL == data)
     {
          printf("Failed to alloc memory for card.\n");
          return 0;
     }
     memset(data, 0x0, CARD_NUM * sizeof(int));
     init_card(data, CARD_NUM);
     printf("Out card:\n");
     out_card(data, CARD_NUM);

     shuffle_4(data, CARD_NUM);
     printf("Shuffle, out card:\n");
     out_card(data, CARD_NUM);

     shuffle_5(data, CARD_NUM);
     printf("Shuffle, out card:\n");
     out_card(data, CARD_NUM);

     shuffle_3(data, CARD_NUM);
     printf("Shuffle, out card:\n");
     out_card(data, CARD_NUM);
    
     free(data);
}


 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值