随机洗牌算法

来源:互联网 发布:windows版icloud 编辑:程序博客网 时间:2024/05/18 08:54

 扑克牌洗牌是我们生活中比较喜欢玩的一个游戏。那么我们有没有什么办法自己设计一个扑克牌洗牌的方法呢?在c运行库当中有一个随机函数rand,它可以生成0~32767之间的任意数。那么有没有可能利用这么一个函数对我们扑克牌进行随即洗牌呢?

    在这里我抛砖引玉一下,谈一谈自己目前已经看到的两个算法。欢迎朋友们谈一谈其他的方法。

    (1)全局洗牌法

    步骤如下所示:

    a)首先生成一个数组,大小为54,初始化为1~54

    b)按照索引1到54,逐步对每一张索引牌进行洗牌,首先生成一个余数 value = rand %54,那么我们的索引牌就和这个余数牌进行交换处理

    c)等多索引到54结束后,一副牌就洗好了

    代码如下所示:

[cpp] view plaincopy
  1. void get_rand_number(int array[], int length)  
  2. {  
  3.     int index;  
  4.     int value;  
  5.     int median;  
  6.   
  7.     if(NULL == array || 0 == length)  
  8.         return ;  
  9.   
  10.     /* 每次发牌的时候任意分配待交换的数据 */  
  11.     for(index = 0; index < length; index ++){  
  12.         value = rand() % length;  
  13.   
  14.         median = array[index];  
  15.         array[index] = array[value];  
  16.         array[value] = median;  
  17.     }  
  18. }  


    (2)局部洗牌法

    上面的算法非常简单,但是有一个问题,我们发现每次洗牌之后原来洗好的牌都会进行二次操作,个人觉得有点说不过去,所以不妨加以改进:

    a)同样,首先我们生成一个大小为54的数组,数组排列为1~54

    b)索引牌从1开始,到54结束。这一次索引牌只和剩下还没有洗的牌进行交换, value = index + rand() %(54 - index)

    c)等到所有的索引牌都洗好之后,一副牌就弄好了

    代码如下所示:

[cpp] view plaincopy
  1. void get_rand_number(int array[], int length)  
  2. {  
  3.     int index;  
  4.     int value;  
  5.     int median;  
  6.       
  7.     if(NULL == array || 0 == length)  
  8.         return ;  
  9.       
  10.     /* 发牌的时候对于已经分配的数据不再修改 */  
  11.     for(index = 0; index < length; index ++){  
  12.         value = index + rand() % (length - index);  
  13.           
  14.         median = array[index];  
  15.         array[index] = array[value];  
  16.         array[value] = median;  
  17.     }  
  18. }  

最朴素的做法

       对于这个问题我们从最朴素的解法谈起。每次随机选出一个没有被选过的数放到一个队列中,如果随机出来的数已经被选过,那么继续随机直到遇到一个没有被选过的数放入到队列中;重复这样子操作直到所有的数都被选择出来。

       我们看看这样子作为什么是对的。首先选第一个数的时候有n个数可选,第2个数的时候有(n-1)个数可选,-----这样子看来我们的排列有n*(n-1)*(n-1)*-----*1种,也就是我们最后选出来的排列是n!的排列中的任意一个,这样子显然是符合随机洗牌的要求的。

       但是我们反观该方法的时间复杂度,由于我们每次随机选出一个数都有可能是之前选过的数,需要进行再次随机,因此对选出一个数的随机平均情况下需要随机O(n)次,因此该方法的时间复杂度是O(n^2)的。而且另外还要记一个队列,甚至需要记一个数是否被选出来过,因此该最朴素的做法的时间和空间复杂度都不是最好的,我们需要考虑更好的办法。

 

经典的洗牌算法

       很多面试官需要你会的洗牌算法当然是经典的洗牌算法,也就是我们考虑如何对上面的洗牌算法进行优化。

       我们考虑,当一个数被选之后,我们是没有必要在下一次随机的时候再考虑它的,因此,我们每次只从可选的数的集合中进行随机,也就不用考虑是否会碰到已经选过的数了,这样子直接将算法的复杂度降了一个数量级。

 

[cpp] view plaincopy
  1. void MySwap(int &x, int &y)  
  2. {  
  3.     int temp = x;  
  4.     x = y;  
  5.     y = temp;  
  6. }  
  7.   
  8. void Shuffle(int n)  
  9. {  
  10.     for(int i=n-1; i>=1; i--)  
  11.     {  
  12.         MySwap(num[i], num[rand()%(i+1)]);  
  13.     }  
  14. }  

 

        参照上面的代码,我们可以看到,我们每次都是随机的从没有选的数中选择一个,该洗牌算法的时间复杂度为O(n),空间复杂度为O(0)。正确性如上面一样证明


0 0
原创粉丝点击