编程珠玑第12章的一个问题:
程序的输入包含两个整数m和n(m<n).输出0~n-1范围内m个随机整数的有序列表,且不允许重复。从概率角度,得到的是没有重复的有序选择,其中每个选择出现的概率相等。
书中陈述了3种方法:
1 Knuth的方法,按概率输出整数,算法考虑从0,1,2,...n-1的数。并通过一个适当的随机测试对每个整数进行选择。通过有序访问整数,输出结果是有序的。
假设5个当中选择2个,
选择0的概率是2/5,语句就是if (rand()%5 < 2),
而选择1的概率则分两种情况,已经选择0,则会以1/4的概率选择1;没有选择0,则以2/4的概率选择1;总结就是从剩下的4个元素中选择剩余应该选择的个数。
总结,其实就是从r个剩余的整数中选择s个,则以s/r的概率选择下一个数。
代码:
//解法1:依次考虑0,1,...,n-1的整数,通过随机测试队每个整数进行选择,
//选择的标准是从n个整数中选择m个整数,则每个被选中的数字的概率是m/n
//从r个剩余的整数中选择s个整数,概率是r/s
void getknuth(int m, int n)
{
int i;
srand(time(NULL));
for (i = 0; i < n; i++)
{
//从n-i个元素中选择m元素的概率
if ((rand()%(n-i)) < m)
{
cout << i << " ";
m--;
}
}
cout << endl;
}
以上算法复杂度是O(n)
2 借助C++的set集合,set保证元素是有序,且是不重复的,所以重复插入小于n的任意一个数,直到集合的大小是m就可以了。如下:
//解法2:用set实现
void gensets(int m, int n)
{
set<int> s;
while (s.size() <= m)
s.insert(rand()%n);
set<int>::iterator it;
for (it = s.begin(); it != s.end(); it++)
cout << *it << " ";
cout << endl;
}
时间复杂度,set的插入复杂度是O(logm),至少插入m次,所以插入部分时间O(mlogm),遍历O(m),总共O(mlogm)
3 将包含整数0~n-1的数组按顺序打乱,然后把前m个元素排序输出。因为这个选择方法,每次都是从未选择的元素中选择元素,所以显示保证了元素的不重复。
代码:
//解法3:把包含0到n-1的有序数组顺序打乱,然后将前m个元素排序输出
void genshuf(int m, int n)
{
int i, j;
int *arr = new int[n];
for (i = 0; i < n; i++)
arr[i] = i;
for (i = 0; i < m; i++)
{
//从n-i个元素中随机选择一个元素, 每次都是从未选择的元素中选择元素
//所以保证了元素的不重复性
j = rand() % (n-i);
swap(arr[i], arr[j]);
}
sort(arr, arr+m);
for (i = 0; i < m; i++)
cout << arr[i] << " ";
cout << endl;
delete [] arr;
}
时间复杂度O(n)加上排序的时间,O(mlogm),总共O(n+mlogm),有一个空间复杂度O(n)