随机洗牌算法
2015-07-25 19:43
323 查看
算法一:就是随机一个位置放入队列中的一个书,位置上已经有数则重新随机一个位置,直到队列空
算法(Java):
public HashSet getRamdomNumber(int total,int number){
//在0到total-1中随机生成number个不重复的随机数
HashSet h=new HashSet();
while(h.size()<number){
Random r=new Random();
int n=r.nextInt(total);
//n++;
h.add((Integer)n); //不用考虑重复的情况
}
return h;
}
第二种方法是从网上看到的,思路是:将数组的元素进行随机的交换.这个算法我不太好说,因为它的效果和效率都依赖于交换的次数N.次数越多则乱序的程度越高但是相应的效率也会降低.个人不太推荐这个算法,因为N的次数不好确定.
算法:
void shuffle ( int a[], int n ) //洗牌算法
{
int tmp = 0,p1,p2;
int cnt = rand() % 1023;
while (cnt--) //随机交换两个位置的数,共交换cnt次
{
p1 = rand() % n;
p2 = rand() % n;
tmp = a[p1];
a[p1] = a[p2];
a[p2] = tmp;
}
}
后来在看Core Java 2的时候看到了一个计算彩票的问题,实际上就是洗牌算法.思路是这样的:从数组中随机取出一个放到一个新的集合中,然后重复这个工作.这样最后原数组为空而新的集合则为乱序.这个算法比第一种算法的效率高多了,而且计算次数是固定的.当时就以为找到了最佳算法.后来蒙朋友提醒,对这个算法进行了改进.其实没有必要开辟一个新的集合的,用原来的存储空间就行.具体思路是:从数组的数中随机取出一个和最后一个元素交换,再从前面N-1个数中随机取一个和倒数第二个交换…这样可以达到和前面算法相同的效果而且存储空间也得到了节省,很不错的算法了.
算法:
void shuffle()
{
int *prev=new int[54];
for(int i=0;i<54;i++) //数组的初始化,表示54张牌
{
prev[i]=i;
}
srand(time(0));
int times=53;
while(times!=0)
{
int one=rand()%times;
swap(prev[one],prev[times]); //交换元素
times--;
}
//这个时候prev数组处于乱序状态,用完后回收空间
delete prev;
}
这是我摘自 matrix67大牛的博客的一段总结:
1. for i:=1 to n do swap(a[i], a[random(1,n)]);
2. for i:=1 to n do swap(a[i], a[random(i,n)]);
如果不仔细思考的话,绝大多数人会认为第一个算法才是真正随机的,因为它的操作“更对称”,保证了概率均等。但静下心来仔细思考,你会发现第二种算法才是真正满足随机性的。为了证明这一点,只需要注意到算法的本质是“随机确定a[1]的值,然后递归地对后n-1位进行操作”,用数学归纳法即可轻易说明算法的正确性。而事实上,这段程序一共将会产生n*(n-1)*(n-2)*…*1种等可能的情况,它们正好与1至n的n!种排列一一对应。
有人会问,那第一种算法为什么就错了呢?看它的样子多么对称美观啊……且慢,我还没说第一种算法是错的哦!虽然第一种算法将产生比第二种算法更多的可能性,会导致一些重复的数列,但完全有可能每种数列重复了相同的次数,概率仍然是均等的。事实上,更有可能发生的是,这两种算法都是正确的,不过相比之下呢第一种算法显得更加对称美观一些。为此,我们需要说明,第一种算法产生的所有情况均等地分成了n!个等价的结果。显然,这个算法将会产生n^n种情况,而我们的排列一共有n!个,因此n^n必须能够被n!整除才行(否则就不能均等地分布了)。但是,n!里含有所有不超过n的质数,而n^n里却只有n的那几个质因子。这表明要想n^n能被n!整除,n的质因子中必须含有所有不超过n的质数。这个结论看上去相当荒唐,反例遍地都是,并且直觉上告诉我们对于所有大于2的n这都是不成立的。为了证明这一点,只需要注意到2是质数,并且根据Bertrand-Chebyshev定理,在n/2和n之间一定还有一个质数。这两个质数的乘积已经大于n了。搞了半天,第一种看似对称而美观的算法居然是错的!
算法(Java):
public HashSet getRamdomNumber(int total,int number){
//在0到total-1中随机生成number个不重复的随机数
HashSet h=new HashSet();
while(h.size()<number){
Random r=new Random();
int n=r.nextInt(total);
//n++;
h.add((Integer)n); //不用考虑重复的情况
}
return h;
}
第二种方法是从网上看到的,思路是:将数组的元素进行随机的交换.这个算法我不太好说,因为它的效果和效率都依赖于交换的次数N.次数越多则乱序的程度越高但是相应的效率也会降低.个人不太推荐这个算法,因为N的次数不好确定.
算法:
void shuffle ( int a[], int n ) //洗牌算法
{
int tmp = 0,p1,p2;
int cnt = rand() % 1023;
while (cnt--) //随机交换两个位置的数,共交换cnt次
{
p1 = rand() % n;
p2 = rand() % n;
tmp = a[p1];
a[p1] = a[p2];
a[p2] = tmp;
}
}
后来在看Core Java 2的时候看到了一个计算彩票的问题,实际上就是洗牌算法.思路是这样的:从数组中随机取出一个放到一个新的集合中,然后重复这个工作.这样最后原数组为空而新的集合则为乱序.这个算法比第一种算法的效率高多了,而且计算次数是固定的.当时就以为找到了最佳算法.后来蒙朋友提醒,对这个算法进行了改进.其实没有必要开辟一个新的集合的,用原来的存储空间就行.具体思路是:从数组的数中随机取出一个和最后一个元素交换,再从前面N-1个数中随机取一个和倒数第二个交换…这样可以达到和前面算法相同的效果而且存储空间也得到了节省,很不错的算法了.
算法:
void shuffle()
{
int *prev=new int[54];
for(int i=0;i<54;i++) //数组的初始化,表示54张牌
{
prev[i]=i;
}
srand(time(0));
int times=53;
while(times!=0)
{
int one=rand()%times;
swap(prev[one],prev[times]); //交换元素
times--;
}
//这个时候prev数组处于乱序状态,用完后回收空间
delete prev;
}
这是我摘自 matrix67大牛的博客的一段总结:
1. for i:=1 to n do swap(a[i], a[random(1,n)]);
2. for i:=1 to n do swap(a[i], a[random(i,n)]);
如果不仔细思考的话,绝大多数人会认为第一个算法才是真正随机的,因为它的操作“更对称”,保证了概率均等。但静下心来仔细思考,你会发现第二种算法才是真正满足随机性的。为了证明这一点,只需要注意到算法的本质是“随机确定a[1]的值,然后递归地对后n-1位进行操作”,用数学归纳法即可轻易说明算法的正确性。而事实上,这段程序一共将会产生n*(n-1)*(n-2)*…*1种等可能的情况,它们正好与1至n的n!种排列一一对应。
有人会问,那第一种算法为什么就错了呢?看它的样子多么对称美观啊……且慢,我还没说第一种算法是错的哦!虽然第一种算法将产生比第二种算法更多的可能性,会导致一些重复的数列,但完全有可能每种数列重复了相同的次数,概率仍然是均等的。事实上,更有可能发生的是,这两种算法都是正确的,不过相比之下呢第一种算法显得更加对称美观一些。为此,我们需要说明,第一种算法产生的所有情况均等地分成了n!个等价的结果。显然,这个算法将会产生n^n种情况,而我们的排列一共有n!个,因此n^n必须能够被n!整除才行(否则就不能均等地分布了)。但是,n!里含有所有不超过n的质数,而n^n里却只有n的那几个质因子。这表明要想n^n能被n!整除,n的质因子中必须含有所有不超过n的质数。这个结论看上去相当荒唐,反例遍地都是,并且直觉上告诉我们对于所有大于2的n这都是不成立的。为了证明这一点,只需要注意到2是质数,并且根据Bertrand-Chebyshev定理,在n/2和n之间一定还有一个质数。这两个质数的乘积已经大于n了。搞了半天,第一种看似对称而美观的算法居然是错的!
相关文章推荐
- 动易2006序列号破解算法公布
- Ruby实现的矩阵连乘算法
- C#插入法排序算法实例分析
- 超大数据量存储常用数据库分表分库算法总结
- C#数据结构与算法揭秘二
- C#冒泡法排序算法实例分析
- 算法练习之从String.indexOf的模拟实现开始
- C#算法之关于大牛生小牛的问题
- C#实现的算24点游戏算法实例分析
- c语言实现的带通配符匹配算法
- 浅析STL中的常用算法
- 算法之排列算法与组合算法详解
- C++实现一维向量旋转算法
- Ruby实现的合并排序算法
- C#折半插入排序算法实现方法
- 基于C++实现的各种内部排序算法汇总
- C++线性时间的排序算法分析
- C++实现汉诺塔算法经典实例
- PHP实现克鲁斯卡尔算法实例解析
- C#获取关键字附近文字算法实例