随机洗牌算法

问题:给定一个有序序列1~n,要你将其完全打乱,要求每个元素在任何一个位置出现的概率均为1/n

解决方案:依次遍历数组,对第n个元素,以1/n的概率与前n个元素中的某个元素互换位置,最后生成的序列即满足要求,1/n的概率可通过rand() % n实现。见如下程序:

void swap(int* p, int* q)

{

    int tmp = *p;

    *p = *q;

    *q = tmp;

}

 

void shuffle(int *arr, int n)

{

    int i;

    for(i = 0; i < n; i++) {

        int idx = rand() % (i + 1); //选取互换的位置

        swap(&arr[idx], &arr[i]);

    }

}

 

 使用数学归纳法证明:

l  n=1时,idx必为0,所以元素arr[0]在任何一个位置的概率为1/1,命题成立。

l  假设当n=k时,命题成立,即n=k时,原数组中任何一个元素在任何一个位置的概率为1/k

 则当n=k+1时,当算法执行完k次时,前k个元素在前k个位置的概率均为1/k

 当执行最后一步时,前k个元素中任何一个元素被替换到第k+1位置的概率为:(1-1/(k+1)) * 1/k = 1/(k+1); 在前面k个位置任何一个位置的概率为(1-1/(k+1)) * 1/k = 1/(k+1);

故前k个元素在任意位置的的概率都为1/(k+1)

 所以,对于前k个元素,它们在k+1的位置上概率为1/(k+1)

 对于第k+1个元素,其在原位置的概率为1/k+1,在前k个位置任何一个位置的概率为:(1-k/k+1) * (1/k)=1/k+1),所以对于第k+1个元素,其在整个数组前k+1个位置上的概率也均为1/k+1

 综上所述,对于任意n,只要按照方案中的方法,即可满足每个元素在任何一个位置出现的概率均为1/n

 

扩展:一道google面试题

给定一个未知长度的整数流(数目大于1000),如何从中随机选取1000个随机数。

 

解决方法:

l  定义长度为1000的数组,对于数据流中的前1000个关键字,显然都要放到数组中。

l  对于数据流中的的第nn>1000)个关键字,则这个关键字被随机选中的概率为 1000/n。故以 1000/n 的概率用这个关键字去替换数组中的一个。这样就可以保证所有关键字都以 1000/n的概率被选中。对于后面的关键字都进行这样的处理,这样就可以保证数组中总是保存着1000个随机关键字。

 

注:以1000/n的概率选择一个数替换,可通过rand() % n实现,则这个数被替换到前1000个位置中的概率为1000/n

posted @ 2013-04-19 14:10  ydzhang  阅读(219)  评论(0编辑  收藏  举报