洗牌算法汇总
2013-10-01 17:01 youxin 阅读(1268) 评论(0) 编辑 收藏 举报编程珠玑提到将一个数组随机重新洗牌,目的即打乱原有的排列。洗牌算法有以下几种.
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:
- Write down the numbers from 1 to N.
- Pick a random number k between one and the number of unstruck numbers remaining (inclusive).
- Counting from the low end, strike out the kth number not yet struck out, and write it down elsewhere.
- Repeat from step 2 until all the numbers have been struck out.(出局)
- The sequence of numbers written down in step 3 is now a random permutation of the original numbers.
- 原始数组中有数字1到N
- 设原始数组未被标记的数字个数为k,生成一个1到k之间的随机数
- 取出刚才生成的随机数,将其标记并插入到新的排列数组尾端。
- 重复过程2直到原始数组中没有未被标记的数字
- 过程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?’\t’:’\n’); } }
参考:http://en.wikipedia.org/wiki/Fisher-Yates_shuffle
http://amyangfei.me/2012/11/29/shuffle-algorithm/
更多:http://coolshell.cn/articles/8593.html 如何测试洗牌程序