所谓的洗牌算法,大概意思就是将一组数随机打乱,说到随机,很容易就想到C里面的rand函数,en...说到这个函数,看看该函数如何使用
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
srand(time(NULL));
for(int i=1;i <= 10;++i)
{
printf("%d\r\n",rand());
}
return 0;
}
这里我打印了10个数,可以快速的运行几组程序,其实可以发现每组程序出来的结果会很接近或者相似,为啥呢,可以跟进去看看源码
// Seeds the random number generator with the provided integer.
extern "C" void __cdecl srand(unsigned int const seed)
{
__acrt_getptd()->_rand_state = seed;
}
// Returns a pseudorandom number in the range [0,32767].
extern "C" int __cdecl rand()
{
__acrt_ptd* const ptd = __acrt_getptd();
ptd->_rand_state = ptd->_rand_state * 214013 + 2531011;
return (ptd->_rand_state >> 16) & RAND_MAX;
}
可以看出,其算法之简陋,其代码中的_rand_state也就是种子,所以当srand传人一个固定值的话,每次随机出来的值也会一样。我们使用时间当种子,当快速运行几组程序后,其结果当然也会近似。所以在一些重要的工作场合中,要慎用rand函数随机,毕竟可能可以预测哦。
这里的话扯远了,我们学习就先用这个rand函数吧,那么洗牌中的打乱问题我们可以解决了,下面解决下一个问题,当随机出来一个数后,我们必然要保证这个随机数不能重复出现,毕竟牌值是唯一的。
很容易想到一种方案,那就是遇到重复就重新在随机一个,en,那么当范围值很大的话,可以考虑下可行不?
假设打乱十万个数,这个时候,越到后面,或者说还剩余一两个数的时候,那么太困难了,虽然可能最后可能可以实现,不过时间成本太大,无限猴子定理,哈哈。
所以换种思路,上面的思路根本原因是没有把随机的范围给缩小,如何缩小,直接把筛选出来的数扔出去呗,重新在这个范围内选择,即可以确保其不会重复,也会缩小范围,大概的算法思想是这样,具体看下面分析
假设数组有10个元素,其元素值依次为1 ~ 10,这里的话因为涉及到缩小范围,所以我们需要的是随机下标值,如下:
那么我们随机的范围就是[0,9],假设我们随机出来的是5,那么我们只需将5下标对应的数值6和10进行交换,再将其end指针往前挪一位即可
后面我们的随机范围就变成了[0,8],假设此时随机出来为3,那么变换后结果如下:
后面的步骤就不多说了,重复即可。
参考代码:
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
srand(time(NULL));
int nArr[10] = {0};
int nLen = sizeof(nArr)/sizeof(int);
int nEnd = nLen;
for(int i=0;i < nLen;++i)
{
nArr[i] = i + 1;
}
//洗牌算法核心
while(nEnd)
{
int index = rand() % nEnd; //随机出来[0,nEnd-1]
--nEnd;//缩小范围
if(index != nEnd)
{//交换
nArr[index] ^= nArr[nEnd];
nArr[nEnd] ^= nArr[index];
nArr[index] ^= nArr[nEnd];
}
}
printf("洗牌后结果: \r\n");
for(int i=0;i < nLen;++i)
{
printf("%d ",nArr[i]);
}
printf("\r\n");
return 0;
}
测试结果如下: