您的位置:首页 > 编程语言

取样问题——《编程珠玑》读书笔记

2012-05-15 11:10 369 查看
问题描述:
给定一个足够大的随机选择函数rand(), 对于[0, n)范围内的数,随机选取m个(m<n),且取出的m个数有序排列(增序或减序)。

问题解答:
1、 一开始拿到问题的时候,我的想法就是从rand()%n中取出m个不同的数。这其中需要解决的问题一个就是如何去重,另一个是排序问题。
作者的算法参考自《The Art of Computer Programming. Volume2: Seminumerical Algorithms》,考虑方向完全不一样。
该算法依次考虑整数0,1,2……n-1,并通过一个适当的随机测试对每个整数进行选择。通过按序访问整数,可以保证输出结果的有序性。
为了理解选择的标准,我们考虑下m=2, n=5的情况。那么我们需要对每个整数的选取概率定为2/5,并且要保证一定能选出2个数出来。
首先我们会考虑整数0,选择0的概率为2/5,可以通过下面的语句来实现:
if (rand() % 5) < 2
不幸的是,我们不能用同样的概率来选择整数1:这样做的话我们从5个整数中选出的整数可能是两个也可能不是两个。因此决策有一些不同:在已经选择0的情况下以1/4的概率选择1, 而在没有0的情况下一2/4的概率选择1(我们来看下选择1的概率是多少:2/5 * 1/4 + 3/5 * 2/4 = 2/5,使我们需要的概率)。一般说来,如果要从r个剩余的整数中选出s个,我们以概率s/r选择下一个数。
下面是完整的C代码,可参考

#include <iostream>
#include <stdlib.h>
using namespace std;

int genknuth(int m, int n)
{
for (int i = 0; i < n; i++)
{
if ((rand() % (n - i)) < m)
{
cout << i << "\t";
m--;
}
}
cout << endl;
return 0;
}

int main()
{
genknuth(10, 101);
return 0;
}


2、顺带附上刚开始的想法,时间复杂度为O(mlogm):

int gensets(int m, int n)
{
set<int> s;
while (s.size() < m)
{
s.insert(rand() % n);
}
set<int>::iterator i;
for (i = s.begin(); i != s.end(); i++)
{
cout << *i << "\t";
}
cout << endl;
return 0;
}


3.

生成随机数另一种方法是把包含整数0~n-1的数组顺序打乱,然后把m个元素排序输出。Ashley Shepherd和Alex Woronow发现,在这个问题中我们只需要打乱数组的前m个元素,时间复杂度为O(n)

int intcmp(const void* it1, const void* it2)
{
return *(int*)it1 - *(int*)it2;
}
int randrange(int a, int b)
{
return (rand() % (b -a)) + a;
}
int genshuf(int m, int n)
{
int i, j;
int* x = new int
;
for (i = 0; i < n; i++)
{
x[i] = i;
}
int t;
for (i = 0; i < m; i++)
{
j = randrange(i, n - 1);
t = x[i];
x[i] = x[j];
x[j] = t;
}

qsort(x, m, sizeof(int), intcmp);
for (i = 0; i < m; i++)
{
cout << x[i] << "\t";
}
cout << endl;
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: