洗牌算法汇总

编程珠玑提到将一个数组随机重新洗牌,目的即打乱原有的排列。洗牌算法有以下几种.

Fisher and Yates' original method

该算法最初是1938年由Ronald A. Fisher和Frank Yates在《Statistical tables for biological, agricultural and medical research》一书中描述,算法生成1-N个数的随机排列的过程如下:

The basic method given for generating a random permutation of the numbers 1–N goes as follows:

  1. Write down the numbers from 1 to N.
  2. Pick a random number k between one and the number of unstruck numbers remaining (inclusive).
  3. Counting from the low end, strike out the kth number not yet struck out, and write it down elsewhere.
  4. Repeat from step 2 until all the numbers have been struck out.(出局)
  5. The sequence of numbers written down in step 3 is now a random permutation of the original numbers.
  1. 原始数组中有数字1到N
  2. 设原始数组未被标记的数字个数为k,生成一个1到k之间的随机数
  3. 取出刚才生成的随机数,将其标记并插入到新的排列数组尾端。
  4. 重复过程2直到原始数组中没有未被标记的数字
  5. 过程3中生成的新数组就是一个对原始数组的随机排列

证明随机性: 一个元素m被放入第i个位置的概率P = 前i-1个位置选择元素时没有选中m的概率 * 第i个位置选中m的概率,即

Modern version of the Fisher–Yates shuffle

改进版的Fisher–Yates shuffle算法是1964年Richard Durstenfeld在 Communications of the ACM volume 7, issue 7中首次提出,相比于原始Fisher-Yates shuffle最大的改进是不需要在步骤3中重复的数未被标记的数字,该算法不再将标记过的数字移动到一个新数组的尾端,而是将随机数选出的数字与排在最后位置的未标记数字进行交换

To shuffle an array a of n elements (indices 0..n-1):
  for i from n − 1 downto 1 do
       j ← random integer with 0 ≤ j ≤ i
       exchange a[j] and a[i]

c++实现:

void fisher_yates_shuffleArray(int a[],int len)
{
    int i=len,j;
    srand(time(NULL));
    if(i==0) return;

    while(--i)
    {
        j=rand()%(i+1);
        swap(a[i],a[j]);
    }
}

python:

from random import random

def FisherYatesShuffle(items):
    for i in reversed(range(1, len(items))):
        j = int(random() * (i+1))
        items[i], items[j] = items[j], items[i]

if __name__ == '__main__':
    srclist = [n for n in range(10)]
    FisherYatesShuffle(srclist)
    print srclist

  random.random() 0 <= n < 1.0

Inside-Out Algorithm

        Knuth-Durstenfeld Shuffle 是一个内部打乱的算法,算法完成后原始数据被直接打乱,尽管这个方法可以节省空间,但在有些应用中可能需要保留原始数据,所以需要另外开辟一个数组来存储生成的新序列。

        Inside-Out Algorithm 算法的基本思思是从前向后扫描数据,把位置i的数据随机插入到前i个(包括第i个)位置中(假设为k),这个操作是在新数组中进行,然后把原始数据中位置k的数字替换新数组位置i的数字。其实效果相当于新数组中位置k和位置i的数字进行交互

To initialize an array a of n elements to a randomly shuffled copy of source, both 0-based:
  a[0] ← source[0]
  for i from 1 to n − 1 do
      j ← random integer with 0 ≤ j ≤ i
      if j ≠ i
          a[i] ← a[j]
      a[j] ← source[i]

c++实现:

int* insideOutShuffle(int a[],int n)
{
    srand(time(NULL));

    int *result=new int[n];
    result[0]=a[0];
    for(int i=1;i<n;i++)
    {
        int j=rand()%(i+1);//[0,i];
        if(j!=i)
            result[i]=result[j];
        result[j]=a[i];

    }
    return result;
}

算法的时间复杂度为O(n),空间复杂度为O(n)。

这个算法的一个优点就是可以处理n未知的数组,伪代码如下

To initialize an empty array a to a randomly shuffled copy of source whose length is not known:
  while source.moreDataAvailable
      j ← random integer with 0 ≤ j ≤ a.length
      if j = a.length
          a.append(source.next)
      else
          a.append(a[j])
          a[j] ← source.next

一个洗牌程序;

本程序是一个高效的洗牌、发牌程序。函数fillDeck按每门花色从“A”到“K”的顺序初始化数组Card。数组Card传递给函数shuffle,函数shuffle用for循环结构循环发放52张(数组下标0~51),程序为每一张牌选择一个0-51之间的随机数,然后把数组中当前的Card结构与随机选出的Card结构对应交换。

本程序重点训练结构、数组及函数的参数传递。

红桃:A、2、3、4、5、6、7、8、9、10、J、Q、K。

方片:A、2、3、4、5、6、7、8、9、10、J、Q、K。

黑桃:A、2、3、4、5、6、7、8、9、10、J、Q、K。

梅花:A、2、3、4、5、6、7、8、9、10、J、Q、K。

#include<stdio.h>

#include<stdlib.h>

#include<time.h>

struct card

 {

   char *face;

   char *suit;

 };

 typedef struct card Card;   /*  定义结构别名  */

 void fillDeck(Card *,char *[],char *[]); /* 注意参数的类型 */

 void shuffle(Card *);

 void deal(Card *);

 main()

 {

Card deck[52];  /* 结构数组,共有52个成员 */

/*下面这两个数组用于存放四种花色各13张,共计52张牌。*/

char *face[]={"Ace","Deuce","Three","Four","Five","Six","Seven",

"Eight","Nine","Ten","Jeck","Queen","King"};  /* A-K 13张牌 */

char *suit[]={"Hearts","Diamonds","Clubs","Spades"};/*红桃、方片、梅花、黑桃*/

    srand(time(NULL));  /* 随机数发生器,不用理解 */

    fillDeck(deck,face,suit);

    shuffle(deck);

    deal(deck);

    return 0;

 }

 

/*  按顺序发放52张牌 */  

void fillDeck(Card *wDeck,char *wFace[],char *wSuit[])

   {

     int i;

     for(i=0;i<52;i++)

     {

wDeck[i].face=wFace[i%13];  /*A-K计13张,注意%和/的用法*/  

wDeck[i].suit=wSuit[i/13];/*0-12红桃,13-25方片,27-39黑桃,40-52梅花*/

     }

   }

 

   void shuffle(Card *wDeck)

   {

     int i,j;

     Card temp;

     for(i=0;i<52;i++)

     {

       j=rand()%52;   /*在52中随机选一个数*/

       temp=wDeck[i];  /*将当前牌的信息存放临时变量*/

       wDeck[i]=wDeck[j];/*将随机选出牌的信息存放当前的结构*/

       wDeck[j]=temp;  /*将临时变量中保存的信息存放随机选出的结构中*/

     }

   }

 

    void deal(Card *wDeck)

    {

      int i;

      for(i=0;i<52;i++)

      {

       printf("%5s of %-8s%c",wDeck[i].face,wDeck[i].suit,(i+1)%2?’	’:’
’);

       }

    }
View Code

参考:http://en.wikipedia.org/wiki/Fisher-Yates_shuffle

http://amyangfei.me/2012/11/29/shuffle-algorithm/

更多:http://coolshell.cn/articles/8593.html 如何测试洗牌程序


原文地址:https://www.cnblogs.com/youxin/p/3348626.html