问题描述:
输入两个正整数m和n(m<=n),输出一个有m个随机数组成的随机列表,这些随机数的范围时0~n-1,并且每一个整数只能出现一次。
方法一:O(n)
整体来说,从n个整数中随机选择m个不重复整数,选择每一个整数的概率都是m/n,因此在进行选择时,需要保证每个整数被选择的概率是m/n就能满足问题要求。
实现代码如下:
if(rand()%remaining < select )能够保证选择每个元素的概率时m/n。
例如,从5个元素中选择2个元素,
(1)在选择第一个元素时:需满足rand()%5 < 2,这时候选择元素的概率时2/5。
(2)在选择第二个元素时:需满足rand()%4<1,这时候选择该元素的概率也是2/5 = 2/5 * 1/4 + 3/5 * 2/4 。
6voidrand_select(intn,intm)//m<=n
8intselect=m;
10inti=0;
12for(i=0;i<n;i++)
14if(rand()%remaining<select)//保证每个元素被选中的概率时m/n
16printf("%d",i);
18}
20}
22}
方法二:0(mlogm)
直接选择m个范围在0~n-1的随机数,然后插入到某种数据结构中,比如二分查找数等,可以实现logm的效率。这里使用STL 中的set。
代码如下:
27voidrand_select(intn,intm)
29inti=0;
31srand(time(NULL));
33{
35}
37while(it!=mset.end())
39cout<<*it<<"";
41}
43}
但是这个算法存在的缺陷是:当m和m很大时,可能会产生许多相同的随机值,在进行mset插入时出现浪费,一种防止这种消耗的做法见《编程珠玑》问题12.9答案。
方法三:
这个方法是通过用随机的方法弄乱一个数组来实现,然后排序数组并输出前m个。弄乱数组a[0~n-1]的方法是(伪码):
for(int i = 0;i<n;i++)
swap(a[i],rand(i,n-1))。
在这里,由于我们只需要去m个随机数,故可以只弄乱数组的前m个元素,然后排序数组的前m个元素并输出,实现代码如下:
52voidrand_select(intn,intm)
54int*a=newint[n];
56for(i=0;i<n;i++)
58for(i=0;i<m;i++)
60intj=rand()%(n-i)+i;
62}
64for(i=0;i<m;i++)
66cout<<endl;