洗牌算法
给定一个n个数的序列,设计一个算法将其随机打乱,保证每个数出现在任意一个位置的概率相同(也就是说在n!个的排列中,每一个排列出现的概率相同)。
朴素的做法:
假设输入为数组num[length]。
随机选一个数,放到num[0]中,再随机选数,如果该数已经选过,重新选,直到该数未选过时放入num[1]中,以此类推,直到所有的数都选出来,很明显,这种选法一共有n!中可能,每种可能出现的概率都相同。
但是该做法效率不高,因为选过的数再选将耗费大量时间。
改进的洗牌算法:
基于以上算法的缺陷,我们做出改进:选过的数将不再考虑。比如num[0…k]为已选的数,那么我们的随机只在k+1到length-1间进行。
void MySwap(int &a, int &b)
{
int tmp = x;
x = y;
y = tmp;
}
void Shuffle(int num[], int length)
{
if (NULL == num || length <= 0) return;
for (int index = length-1; index >= 1; --index)
{
MySwap(num[index], num[rand()%(index+1)]);
}
}
解释一下:
index为本次选的牌的存放位置,rand()%(index+1)产生0到index之间的随机数,而已选的数的下标为index+1到length-1,所以可以保证已选的数不会再重复选到。
一些小细节:
习惯使用–index而不是index–的原因是:index–需要一个临时变量来保存自减前的index值,而–index,直接先自减,之后返回自身,因此效率更高一些。(当然,实际上编译器可能会做一些优化,使得两者的区别不大,这仅仅是一个良好的编程习惯)。
使用逆序:rand()%(index+1)比较方便地产生0到index之间的随机数,如果是正序,则需要写成:
for (int index = 0; index < length; ++index)
{
MySwap(num[index], num[index+rand()%(length-index)]);
}
运算多一些,而且不够简洁。
最后提一个点:以上代码每次运算的结果都会是一样的,如果想要每次都不一样,需要添加种子(使用srand((int)time(0));根据时间来选择种子)。
每天进步一点点,Come on!
(●’?’●)
本人水平有限,如文章内容有错漏之处,敬请各位读者指出,谢谢!
时间: 2024-10-26 03:57:09