Arrays 如何有效地生成0到上限N之间的K个非重复整数的列表
问题给出了所有必要的数据:在给定的区间[0,N-1]内生成K个非重复整数序列的有效算法是什么。如果K大且接近于N,则普通算法(生成随机数,在将它们添加到序列之前,查找它们是否已经存在)非常昂贵Arrays 如何有效地生成0到上限N之间的K个非重复整数的列表,arrays,algorithm,random,permutation,Arrays,Algorithm,Random,Permutation,问题给出了所有必要的数据:在给定的区间[0,N-1]内生成K个非重复整数序列的有效算法是什么。如果K大且接近于N,则普通算法(生成随机数,在将它们添加到序列之前,查找它们是否已经存在)非常昂贵 中提供的算法似乎过于复杂,需要一些实现。我刚刚找到了另一种算法,只要你知道所有相关参数,它似乎可以在一次传递中完成这项工作。通过将K个数字存储在散列存储中来加速这个简单的算法。在开始之前知道K可以消除插入哈希映射的所有低效性,并且您仍然可以获得快速查找的好处。以下代码(在C中,未知来源)似乎非常好地解决了
中提供的算法似乎过于复杂,需要一些实现。我刚刚找到了另一种算法,只要你知道所有相关参数,它似乎可以在一次传递中完成这项工作。通过将K个数字存储在散列存储中来加速这个简单的算法。在开始之前知道K可以消除插入哈希映射的所有低效性,并且您仍然可以获得快速查找的好处。以下代码(在C中,未知来源)似乎非常好地解决了这个问题:
/* generate N sorted, non-duplicate integers in [0, max[ */
int *generate(int n, int max) {
int i, m, a;
int *g = (int *)calloc(n, sizeof(int));
if ( ! g) return 0;
m = 0;
for (i=0; i<max; i++) {
a = random_in_between(0, max - i);
if (a < n - m) {
g[m] = i;
m ++;
}
}
return g;
}
/*在[0,max]*/
int*生成(int n,int max){
int i,m,a;
int*g=(int*)calloc(n,sizeof(int));
如果(!g)返回0;
m=0;
对于(i=0;i生成一个数组0…N-1
filleda[i]=i
然后洗牌第一个K
项
洗牌:
- 开始
J=N-1
- 选择一个随机数
0…J
(比如,R
)
- 将
a[R]
与a[J]
- 由于
R
可以等于J
,因此该元素可以与自身交换
- 从
J
中减去1
,然后重复
最后,取K
last元素
这实质上是从列表中选取一个随机元素,将其移出,然后从剩余的列表中选取一个随机元素,依此类推
在O(K)和O(N)时间内工作,需要O(N)存储
洗牌部分称为Knuth的洗牌,在《计算机编程艺术》第二卷中有描述。这是Perl代码。Grep是一个过滤器,我一直没有测试这段代码
@list = grep ($_ % I) == 0, (0..N);
- I=间隔
- N=上限
仅通过模运算符获取与间隔匹配的数字
@list = grep ($_ % 3) == 0, (0..30);
将返回0、3、6、…30
这是伪Perl代码。您可能需要对其进行调整以使其能够编译。来自Python库的代码使其非常简单有效:
from random import sample
print sample(xrange(N), K)
sample
函数返回从给定序列中选择的K个唯一元素的列表。
xrange
是一个“列表仿真器”,即它的行为类似于一个连续数字的列表,而不在内存中创建,这使得它对于像这样的任务非常快速。水库采样版本非常简单:
my $N = 20;
my $k;
my @r;
while(<>) {
if(++$k <= $N) {
push @r, $_;
} elsif(rand(1) <= ($N/$k)) {
$r[rand(@r)] = $_;
}
}
print @r;
my$N=20;
我的$k;
我的@r;
while(){
如果(+++$k,这里有一种方法可以在O(N)中进行,而不需要额外的存储。我很确定这不是一个纯粹的随机分布,但它可能足够接近许多用途
/* generate N sorted, non-duplicate integers in [0, max[ in O(N))*/
int *generate(int n, int max) {
float step,a,v=0;
int i;
int *g = (int *)calloc(n, sizeof(int));
if ( ! g) return 0;
for (i=0; i<n; i++) {
step = (max-v)/(float)(n-i);
v+ = floating_pt_random_in_between(0.0, step*2.0);
if ((int)v == g[i-1]){
v=(int)v+1; //avoid collisions
}
g[i]=v;
}
while (g[i]>max) {
g[i]=max; //fix up overflow
max=g[i--]-1;
}
return g;
}
/*在[0,max[O(N)]中生成N个已排序的非重复整数*/
int*生成(int n,int max){
浮动步长,a,v=0;
int i;
int*g=(int*)calloc(n,sizeof(int));
如果(!g)返回0;
对于(i=0;imax){
g[i]=max;//修复溢出
max=g[i--]-1;
}
返回g;
}
<>代码> < P>我的解决方案是C++面向对象,但我确信它可以被翻译成其他语言,因为它很简单。
- 首先,生成一个包含K个元素的链表,从0到K
- 然后,只要列表不是空的,就生成一个介于0和向量大小之间的随机数
- 将该元素放入另一个向量,并将其从原始列表中删除
此解决方案只涉及两次循环迭代,没有哈希表查找或任何类似的内容。因此在实际代码中:
// Assume K is the highest number in the list
std::vector<int> sorted_list;
std::vector<int> random_list;
for(int i = 0; i < K; ++i) {
sorted_list.push_back(i);
}
// Loop to K - 1 elements, as this will cause problems when trying to erase
// the first element
while(!sorted_list.size() > 1) {
int rand_index = rand() % sorted_list.size();
random_list.push_back(sorted_list.at(rand_index));
sorted_list.erase(sorted_list.begin() + rand_index);
}
// Finally push back the last remaining element to the random list
// The if() statement here is just a sanity check, in case K == 0
if(!sorted_list.empty()) {
random_list.push_back(sorted_list.at(0));
}
//假设K是列表中的最高数字
std::向量排序列表;
std::向量随机列表;
对于(int i=0;i1){
int rand_index=rand()%sorted_list.size();
随机列表。向后推(排序列表。at(随机索引));
排序列表。擦除(排序列表。开始()+随机索引);
}
//最后将剩下的最后一个元素推回到随机列表中
//在K==0的情况下,这里的if()语句只是一个健全性检查
如果(!sorted_list.empty()){
随机列表。向后推(排序列表位于(0));
}
实际上,在与所选元素数量成比例的空间中,而不是与所选集合的大小成比例的空间中,无论所选集合在整个集合中占多大比例,都可以执行此操作。您可以通过生成一个随机排列,然后按如下方式从中选择:
选择一个分组密码,如或XTEA。用于将块大小减小到比所选集合大两倍的最小幂。使用随机种子作为密码的密钥。要在置换中生成元素n,请使用密码加密n。如果输出数字不在您的集合中,请加密该数字。重复此操作,直到该数字位于内部对集合进行加密。平均而言,每个生成的数字必须进行少于两次加密。这还有一个额外的好处,即如果种子是加密安全的,那么整个排列也是安全的
关于这一点,我写得更详细。在中,Knuth描述了以下选择采样算法:
算法S(选择抽样技术)。从一组n中随机选择n个记录,其中0
S1.[初始化]设置t← 0,m← 0。(在此算法中,m表示迄今为止选择的记录数,t表示我们处理的输入记录总数。)
S2.【生成U.】生成一个随机数U,均匀分布在0和1之间
S3.【试验】如果(N——
(defun sample-list (n list &optional (length (length list)) result)
(cond ((= length 0) result)
((< (* length (random 1.0)) n)
(sample-list (1- n) (cdr list) (1- length)
(cons (car list) result)))
(t (sample-list n (cdr list) (1- length) result))))
(defun sample (n sequence)
(let ((length (length sequence))
(result (subseq sequence 0 n)))
(loop
with m = 0
for i from 0 and u = (random 1.0)
do (when (< (* (- length i) u)
(- n m))
(setf (elt result m) (elt sequence i))
(incf m))
until (= m n))
result))
/* Sampling according to [Vitter87].
*
* Bibliography
* [Vitter 87]
* Jeffrey Scott Vitter,
* An Efficient Algorithm for Sequential Random Sampling
* ACM Transactions on MAthematical Software, 13 (1), 58 (1987).
*/
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <string>
#include <iostream>
#include <iomanip>
#include <boost/random/linear_congruential.hpp>
#include <boost/random/variate_generator.hpp>
#include <boost/random/uniform_real.hpp>
using namespace std;
// This is a typedef for a random number generator.
// Try boost::mt19937 or boost::ecuyer1988 instead of boost::minstd_rand
typedef boost::minstd_rand base_generator_type;
// Define a random number generator and initialize it with a reproducible
// seed.
// (The seed is unsigned, otherwise the wrong overload may be selected
// when using mt19937 as the base_generator_type.)
base_generator_type generator(0xBB84u);
//TODO : change the seed above !
// Defines the suitable uniform ditribution.
boost::uniform_real<> uni_dist(0,1);
boost::variate_generator<base_generator_type&, boost::uniform_real<> > uni(generator, uni_dist);
void SequentialSamplesMethodA(int K, int N)
// Outputs K sorted random integers out of 0..N, taken according to
// [Vitter87], method A.
{
int top=N-K, S, curr=0, currsample=-1;
double Nreal=N, quot=1., V;
while (K>=2)
{
V=uni();
S=0;
quot=top/Nreal;
while (quot > V)
{
S++; top--; Nreal--;
quot *= top/Nreal;
}
currsample+=1+S;
cout << curr << " : " << currsample << "\n";
Nreal--; K--;curr++;
}
// special case K=1 to avoid overflow
S=floor(round(Nreal)*uni());
currsample+=1+S;
cout << curr << " : " << currsample << "\n";
}
void SequentialSamplesMethodD(int K, int N)
// Outputs K sorted random integers out of 0..N, taken according to
// [Vitter87], method D.
{
const int negalphainv=-13; //between -20 and -7 according to [Vitter87]
//optimized for an implementation in 1987 !!!
int curr=0, currsample=0;
int threshold=-negalphainv*K;
double Kreal=K, Kinv=1./Kreal, Nreal=N;
double Vprime=exp(log(uni())*Kinv);
int qu1=N+1-K; double qu1real=qu1;
double Kmin1inv, X, U, negSreal, y1, y2, top, bottom;
int S, limit;
while ((K>1)&&(threshold<N))
{
Kmin1inv=1./(Kreal-1.);
while(1)
{//Step D2: generate X and U
while(1)
{
X=Nreal*(1-Vprime);
S=floor(X);
if (S<qu1) {break;}
Vprime=exp(log(uni())*Kinv);
}
U=uni();
negSreal=-S;
//step D3: Accept ?
y1=exp(log(U*Nreal/qu1real)*Kmin1inv);
Vprime=y1*(1. - X/Nreal)*(qu1real/(negSreal+qu1real));
if (Vprime <=1.) {break;} //Accept ! Test [Vitter87](2.8) is true
//step D4 Accept ?
y2=0; top=Nreal-1.;
if (K-1 > S)
{bottom=Nreal-Kreal; limit=N-S;}
else {bottom=Nreal+negSreal-1.; limit=qu1;}
for(int t=N-1;t>=limit;t--)
{y2*=top/bottom;top--; bottom--;}
if (Nreal/(Nreal-X)>=y1*exp(log(y2)*Kmin1inv))
{//Accept !
Vprime=exp(log(uni())*Kmin1inv);
break;
}
Vprime=exp(log(uni())*Kmin1inv);
}
// Step D5: Select the (S+1)th record
currsample+=1+S;
cout << curr << " : " << currsample << "\n";
curr++;
N-=S+1; Nreal+=negSreal-1.;
K-=1; Kreal-=1; Kinv=Kmin1inv;
qu1-=S; qu1real+=negSreal;
threshold+=negalphainv;
}
if (K>1) {SequentialSamplesMethodA(K, N);}
else {
S=floor(N*Vprime);
currsample+=1+S;
cout << curr << " : " << currsample << "\n";
}
}
int main(void)
{
int Ntest=10000000, Ktest=Ntest/100;
SequentialSamplesMethodD(Ktest,Ntest);
return 0;
}
$ time ./sampling|tail
99990 : 9998882
99991 : 9998885
99992 : 9999021
99993 : 9999058
99994 : 9999339
99995 : 9999359
99996 : 9999411
99997 : 9999427
99998 : 9999584
99999 : 9999745
real 0m0.075s
user 0m0.060s
sys 0m0.000s
t=0
m=0
N=10
n=5
s=0
distrib=Array.new(N,0)
for i in 1..500000 do
t=0
m=0
s=0
while m<n do
u=rand()
if (N-t)*u>=n-m then
t=t+1
else
distrib[s]+=1
m=m+1
t=t+1
end #if
s=s+1
end #while
if (i % 100000)==0 then puts i.to_s + ". cycle..." end
end #for
puts "--------------"
puts distrib
100000. cycle...
200000. cycle...
300000. cycle...
400000. cycle...
500000. cycle...
--------------
250272
249924
249628
249894
250193
250202
249647
249606
250600
250034