Chinaunix首页 | 论坛 | 博客
  • 博客访问: 244748
  • 博文数量: 62
  • 博客积分: 973
  • 博客等级: 准尉
  • 技术积分: 530
  • 用 户 组: 普通用户
  • 注册时间: 2011-11-16 23:25
文章分类

全部博文(62)

文章存档

2013年(1)

2012年(14)

2011年(47)

分类: C/C++

2011-12-25 15:05:56

问题描述:

现要求产生 0~n-1 范围内的 个随机整数的有序列表,且不允许重复,m <= n

 

    转自://blog.csdn.net/huagong_adu/article/details/5572637

考虑到 的值可能很大,而通常 C/C++ 提供的随机数产生器所能返回的随机数在 [0,RAND_MAX],其中,RAND_MAX 为 0x7FFF。也就是说只有 15 位的随机性。因此,我们需要要有自己的随机数产生器,以便能够返回更多位数的随机数,通常为 30 位。下面的函数可以满足我们的要求:

int bigrand()

{

return (RAND_MAX * rand() + rand());

}

注意,这里也可以采用将第一个 rand() 的返回值左移 15 位,然后加上第二个 rand() 的返回值的方法来提速。

 

解法一、

 

下面,我们就可以用这个随机数产生器来解决上面的问题了。看到这里,你是不是想到了什么解决方法呢。下面的方法来自算法大师 Donald Knuth

void genknuth(int m, int n)

{

      if(0 == m) return;

      srand((unsigned int)time(NULL));

      for(int i = 0; i < n; i++)

      {

         if((rand() % (n - i)) < m)

         {

             printf("%4d\n", i);

             m--;

         }

      }

}

其中的 srand 语句是我为了在每次运行时能够得到不同的随机数序列而加的。

不知道这个方法有没有出乎你的意料,算上 for 循环才 句话。不过,就是这 句话,已经很完美地解决了上面提出的问题,有序、互异、个数。

这个方法通过顺序扫描 0~n-1 范围内的每个数保证产生的随机数有序和互异。

那产生 个数怎么保证呢?又没有类似循环的语句来控制产生的随机数的个数。

要记住,程序语句只是我们实现某种功能的一种表现形式,这也就是说,我们要实现某种功能,可以由很多种表现形式。下面就来分析一下这个方法是如何保证能够产生 个随机数的。

想必大家都已经看到产生的随机数都是在 if 语句中输出的,那也就是说 if 语句控制了产生的随机数的个数。因此,要说明能够产生 个随机数,只要说明 if 语句能够执行且仅能执行 次。

首先,在 for 循环没有推出的情况下,if 语句最多能够执行 次,因为 if 语句的每次执行都会使 的值减 1,当 m=0 时,if 语句就不会再执行了。因为 (rand() % (n - i)) 是一个非负数。

其次,我们分析 if 语句最少能够执行多少次。如果 if 语句开始一直得不到执行,那么 (n-i) 便会一直减小,直到与 相等。这时 (rand() % (n - i)) 一定小于 (n-i),也就是 m,从而使得 if 语句得到一次执行。而此时 for 循环已经执行了 (n-m+1) 次,离推出循环只剩下 (m-1) 次,要使得 if 语句能够执行 次,那么接下来的 (m-1) 次循环 if 语句必须都得能够执行。事实是什么样的呢?第一次 if 语句开始执行时,(n-i)=m,执行完时,(n-i) 和 都减少 1,从而仍然有 (n-i)=m 成立。这个过程一直持续到 m=0。刚好是 m-1 次。

我的理解:因为rand()%value的值是(0, value-1),把这个运算看做select [0, value) 。

一般n>>m,所以n-i=value一开始也比m大,那么在(0, value)中通过rand运算找出一个小于m的数的机会很多,因为每次都可以从大数一直找到0。如果rand运算没有找到小于m的数(毕竟是random select),那么由于 i 的增大,value将一直减小到m,这时继续select[0, value) 就相当于select[0, m),随便怎么rand()%结果都是小于m的,而且接下来的每次也都是小于m的。

因此,我们可以相信 if 语句确实能够执行且只能执行 个。

Donald Knuth 实在是太厉害了!

顺便说一句,据说在大学期间,有关程序设计之类的比赛,只要有他参加,第一名非他莫属。

 

解法二

 

就是在一个初始为空的集合中插入随机整数,直到足够的整数。

伪代码如下所示:

initialize set S to empty

size = 0

while size < m do

 t = bigrand()%n

 if t is not in S

 insert t into S

 size++

print the elements of S in sorted order

该算法在选择元素时能够保证所有的元素都具有相同的选中概率,它的输出是随机的

void genknuth(int m, int n)

{

 set S;

 while( S.size() < m)

 S.insert(bigrand()%n);

 set::iterator i;

 for(i = S.begin(); i != S.end(); ++i)

 cout<<*i<<"/n";

}

C++标准模板库规范能够保证在Olog m)时间内完成每个插入操作,集合内部迭代需要O(m),所以整个程序需要的时间为O(m log(m)(n相比,m较小时)。然而,该数据结构的空间消耗较大。

 

解法三、

 

for i = [0,n)

 swap(i, randint(i,n-1))

这个是弄乱数据x[0...n-1]。在这个问题中,只需要搅乱数组前面m个元素

int bigrand()

{

 return RAND_MAX*rand() + rand();

}

int randint(int l, int u)

{

 return l + bigrand() % (u-l+1);

}

void genknuth(int m, int n)

{

 int i,j;

 int *x = new int[n];

 for(i = 0; i < n; i++)

 x[i] = i;

 for(i = 0; i < m; i++)

 {

 j = randint(i,n-1);

 swap(x[i],x[j]);

 }

 sort(x, x+m);

 for(i=0; i

 cout<

}

int main()

{

 srand((unsigned)time(NULL));

 int m,n;

 while(1)

 {

 cout<<"Please enter m and n:";

 cin>>m>>n;

 genknuth(m,n);

 }

 return 0;

 }

该算法使用了n个字的内存并需要O(n+mlogm)时间

 

解法四

 

递归

void randselect(int m,int n)

{

 assert(m>=0 && m <= n);

 if(m>0)

 if((bigrand()%n) < m)

 {

 randselect(m-1,n-1);

 cout<

 }

 else

 randselect(m, n-1);

}

原文链接:

http://blog.csdn.net/ztj111/archive/2007/10/23/1840190.aspx

        http://hi.baidu.com/xwf_like/blog/item/ff2a9b98293022006e068c2d.html 

阅读(2234) | 评论(0) | 转发(0) |
0

上一篇:两个栈实现一个队列

下一篇:位图表示法

给主人留下些什么吧!~~