洗牌算法
问题1
给定长度为n的数组,随机将数组中的元素打乱。
等价问题:随机生成n个不同的数,数的范围在1-n。
只需要将1-n的数随机打乱即可。
分析:
类似从箱子中不放回取球的问题,可以证明每个球被在第k次取到的概率都是 1n 。
算法:
类比取球时将球分为已取出的和未取出的,将数组分为已打乱的和未打乱的两部分。每次从未打乱的部分随机取出一个,放到已经打乱的部分。
关键代码:
//从后往前倒着写,后面是已经打乱的
for(int i = len;i >= 1;i--){
index = rand() % i; // 从未打乱的部分随机选一个
swap(a[i-1], a[index]); //将其放置在已经打乱的部分
}
问题2
随机从n个数中取出m个
分析:
只需要保证每个数被取到的概率相等即可,仍然类似从箱子中不放回取球的问题。
算法:
类比不放回取球的问题,当取出m个球时结束。
关键代码:
//从前往后写,前面是已经打乱的,len为数组长度
for(int i = 0;i < m;i++){
index = (rand() % (len - i)) + i; // 从未打乱部分随机选一个
swap(a[i], a[index]); //将其放置在已经打乱的部分
}
洗牌算法变式
问题3
(本题来自多益网络笔试题)随机生成5个数,使得数的和为0,数的范围在[-20, 20]。
分析:
- 第一开始想到的是深搜+回溯的做法,及时进行剪枝,但这样不但不能保证算法每次算法时间复杂度相同,并且不能保证随机性。
- 如何保证数组的和为零呢?考虑对每个数取相反数
- 先随机生成n个数,然后对n个数取相反数,这样就有了两个数组,然后将两个数组的元素随机配对相加,这样就能保证数组和为0
- 但是!!!最后的结果真的是随机的吗?
写了个程序测试一下,用Excel做了个图表,当数组长度为10时,得到的分布如下:
很明显不是随机分布,通过比较
P(ai=20)
和
P(ai=0)
可以容易的推算出,
P(ai=20)
的情况要更难以满足一点。
关键代码:
问题4
随机生成n个整数,使得数的和为k,数的范围在[m, n]之间
分析:
待续。