首页 > 代码库 > 转:浅谈洗牌算法(面试题)

转:浅谈洗牌算法(面试题)

       很多人都有耳闻过洗牌算法,时常会在面试中碰到,我们下面来定义一下这个问题。

       所谓洗牌算法,就是给你一个1到n的序列,让你随机打乱,保证每个数出现在任意一个位置的概率相同,也就是说在n!个的排列中,每一个排列出现的概率相同。

 

最朴素的做法

       对于这个问题我们从最朴素的解法谈起。每次随机选出一个没有被选过的数放到一个队列中,如果随机出来的数已经被选过,那么继续随机直到遇到一个没有被选过的数放入到队列中;重复这样子操作直到所有的数都被选择出来。

       我们看看这样子作为什么是对的。首先选第一个数的时候有n个数可选,第2个数的时候有(n-1)个数可选,-----这样子看来我们的排列有n*(n-1)*(n-1)*-----*1种,也就是我们最后选出来的排列是n!的排列中的任意一个,这样子显然是符合随机洗牌的要求的。

       但是我们反观该方法的时间复杂度,由于我们每次随机选出一个数都有可能是之前选过的数,需要进行再次随机,因此对选出一个数的随机平均情况下需要随机O(n)次,因此该方法的时间复杂度是O(n^2)的。而且另外还要记一个队列,甚至需要记一个数是否被选出来过,因此该最朴素的做法的时间和空间复杂度都不是最好的,我们需要考虑更好的办法。

 

经典的洗牌算法

       很多面试官需要你会的洗牌算法当然是经典的洗牌算法,也就是我们考虑如何对上面的洗牌算法进行优化。

       我们考虑,当一个数被选之后,我们是没有必要在下一次随机的时候再考虑它的,因此,我们每次只从可选的数的集合中进行随机,也就不用考虑是否会碰到已经选过的数了,这样子直接将算法的复杂度降了一个数量级。

void MySwap(int &x, int &y){    int temp = x;    x = y;    y = temp;}void Shuffle(int n){    for(int i=n-1; i>=1; i--)    {        MySwap(num[i], num[rand()%(i+1)]);    }}

参照上面的代码,我们可以看到,我们每次都是随机的从没有选的数中选择一个,该洗牌算法的时间复杂度为O(n),空间复杂度为O(0)。正确性如上面一样证明。

转:浅谈洗牌算法(面试题)