问题:给定一个有序序列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 对于数据流中的的第n(n>1000)个关键字,则这个关键字被随机选中的概率为 1000/n。故以 1000/n 的概率用这个关键字去替换数组中的一个。这样就可以保证所有关键字都以 1000/n的概率被选中。对于后面的关键字都进行这样的处理,这样就可以保证数组中总是保存着1000个随机关键字。
注:以1000/n的概率选择一个数替换,可通过rand() % n实现,则这个数被替换到前1000个位置中的概率为1000/n。
转自:http://blog.chinaunix.net/uid-20196318-id-216658.html