转:在0~N(不包括N)范围内随机生成一个长度为M(M <= N)且内容不重复的数组
2015-03-11 17:07
351 查看
1. 最朴素暴力的做法.
2. 在方法1的基础上我们可以进行优化. 每轮遍历n太耗时, 那么优化成logn如何, 于是采用一下set作为辅助, 为了利用它logn的时间复杂度. 代价是多了一个M大小的set的空间.
3. 如果你被方法1和2无穷的重复比较弄烦了, 可能想到, 每次新产生的随机数都要和已有的数去进行比较是否已存在, 越往后这种方法的效率越低, 不停在做无用功. 那么反其道而行如何? 几斤几两咱们都亮在牌面上, OK, 把所有数先都列出来, 从里面往外筛选, 那么每次选出来的, 肯定是不重复的. 只需要选M次就可以了.
4. 方法3是思路比较理想化和直接, 实际操作中, 你会发现比方法1都要慢很多很多, 原因就出在容器的erase函数, 内部实现的本质是内存片的拷贝, 这个操作相当相当的耗时.这个和语言无关, 换成java等其它语言, 类似的这种函数都是同样的原理. 其实思考到方法3, 真理已经呼之欲出了. 我们沿着这个思路继续优化算法. 从中剔除元素的想法是好的, 但是方法不佳. 其实我们需要的本来就是基本的数组就可以了, 速度还快, 用deque, vector这些容器无非是为了使用他们的erase函数把某个数剔除出去不参与下次的随机过程. 随着一个个数被选出, 容器的大小也在不停变小, 其实使用数组, 利用下标的偏移, 我们直接就可以做到了! 和用数组实现一个队列或者栈不是一样的吗, 无非就是数组下标的移动! 于是沿着方法3的思想, 我们每次随机出来一个下标index(0 <= index < size(size初值为N)), 每次把arr[index]这个位置的元素甩到数组最后面就可以了, 就相当于剔除操作了!
性能测试:
/article/2448507.html
直接输出:
如何用随机数生成0到n之间的m个不重复的数
1、最直接的方法就是先随机生成一个0到n之间的数,判断这个数是否已被选上,如果以前没选过,则选上,如果以前已选,则丢弃
[cpp] view plaincopy
void common(int n,int m)
{
int * randnum=(int *)malloc(n*sizeof(int));
memset(randnum,0,n*sizeof(int)); //把n个位置全部置0
srand(time(NULL));
while(m)
{
int cur=rand()%n;
if (randnum[cur]==0) //进行判断,如果当前数没有选择过,则选择并输出
{
cout<<cur<<endl;
randnum[cur]=1;
m--;
}
}
free(randnum);
}
这种方法简单易懂,但是需要额外的空间来确保取出的数不重复,那么我们有没有更为简单的方法呢,答案是肯定的
2、先上代码,后做解释
[cpp] view plaincopy
void mRand(int n ,int m)
{
srand(time(NULL));
for (int i=0;i<n;i++)
{
if(rand()%(n-i)<m)
{
cout<<i<<endl;
m--;
}
}
}
上边的代码虽然简洁,但是不易懂,我们接下来说明一下
首先是一个循环,这个循环确保了输出的数是不重复的,因为每次的i都不一样
其次是m个数,在每次循环中都会用rand()%(n-i)<m来判断这个数是否小于m,如果符合条件则m减1,直到为0,说明已经取到m个数了
再次是如何保证这m个数是等概率取到的
在第一次循环中i=0, n-i=n, 则随机数生成的是0-n-1之间的随机数,那么此刻0被取到的概率为 m/n-1
在第二次循环中i=1,n-i=n-1,则随机数生成的是0-n-2之间的随机数,这时1被取到的概率就和上一次循环中0有没有取到有关系了。假设在上一次循环中,没有取,则这次取到的1的概率为 m/n-2;假设上一次循环中,已经取到了,那么这次取到1的概率为m-1/n-2,所以总体上这次被取到的概率为 (1-m/n-1)*(m/n-2)+(m/n-1)*(m-1/n-2),最后通分合并之后的结果为m/n-1和第一次的概率一样的
同理,在第i次循环中,i被取上的概率也为m/n-1
所以这m个数是等概率取到的
参考:http://blog.csdn.net/dlengong/article/details/7932579
void cal1() { int i = 0, j = 0, num = 0; int result[M]; result[0] = rand() % N; //第一个肯定不重复, 直接加进去 for (i = 1; i < M; i++) //获得剩下的(M-1)个随机数 { num = rand() % N; //生成0 ~ N之间的随机数字 for (j = 0; j < i; j++) { if (num == result[j]) //如果和result数组中某个元素重复了 { i--; //重新开始此次循环 break; } } if (j == i) // 说明新产生的数和数组里原有的元素都不同, 则add进去 { result[i] = num; } } }
2. 在方法1的基础上我们可以进行优化. 每轮遍历n太耗时, 那么优化成logn如何, 于是采用一下set作为辅助, 为了利用它logn的时间复杂度. 代价是多了一个M大小的set的空间.
void cal2() { set<int> s; int num = 0, index = 0; int result[M]; while (index < M) { num = rand() % N; if (s.find(num) == s.end()) //如果没找到 { s.insert(num); result[index++] = num; } } }
3. 如果你被方法1和2无穷的重复比较弄烦了, 可能想到, 每次新产生的随机数都要和已有的数去进行比较是否已存在, 越往后这种方法的效率越低, 不停在做无用功. 那么反其道而行如何? 几斤几两咱们都亮在牌面上, OK, 把所有数先都列出来, 从里面往外筛选, 那么每次选出来的, 肯定是不重复的. 只需要选M次就可以了.
void cal3() { int result[M] = {0}; deque<int> deq;<span style="white-space:pre"> </span>//队列 int i = 0, index; <span style="white-space:pre"> </span>for (i = 0; i < N; i++) //初始化, 把所有N个数都放到容器里, 从这里面往外挑, 每次必不重复 { deq.push_back(i); } for (i = 0; i < M; i++) //挑选出M个数 { index = rand() % deq.size(); //注意deq.size()是不断变小的, 但是每次都符合随机特性 result[i] = deq.at(index); //把deq数组index位置的元素赋给result[i] deq.erase(deq.begin() + index); //从deq队列中把该元素删除 } }
4. 方法3是思路比较理想化和直接, 实际操作中, 你会发现比方法1都要慢很多很多, 原因就出在容器的erase函数, 内部实现的本质是内存片的拷贝, 这个操作相当相当的耗时.这个和语言无关, 换成java等其它语言, 类似的这种函数都是同样的原理. 其实思考到方法3, 真理已经呼之欲出了. 我们沿着这个思路继续优化算法. 从中剔除元素的想法是好的, 但是方法不佳. 其实我们需要的本来就是基本的数组就可以了, 速度还快, 用deque, vector这些容器无非是为了使用他们的erase函数把某个数剔除出去不参与下次的随机过程. 随着一个个数被选出, 容器的大小也在不停变小, 其实使用数组, 利用下标的偏移, 我们直接就可以做到了! 和用数组实现一个队列或者栈不是一样的吗, 无非就是数组下标的移动! 于是沿着方法3的思想, 我们每次随机出来一个下标index(0 <= index < size(size初值为N)), 每次把arr[index]这个位置的元素甩到数组最后面就可以了, 就相当于剔除操作了!
void cal4() { int result[M] = {0}; int data = {0}; int i = 0, index = 0; for (i = 0; i < N; i++) //初始化 { data[i] = i; } for (i = 0; i < M; i++) { index = rand() % (N - i); result[i] = data[index]; //把data数组index位置的元素赋给result[i] data[index] = data[N - i - 1]; //从data数组末尾(这个位置在不停前移)拿一个数替换到该位置, 相当于这个元素被剔除了 } }
性能测试:
/article/2448507.html
直接输出:
如何用随机数生成0到n之间的m个不重复的数
1、最直接的方法就是先随机生成一个0到n之间的数,判断这个数是否已被选上,如果以前没选过,则选上,如果以前已选,则丢弃
[cpp] view plaincopy
void common(int n,int m)
{
int * randnum=(int *)malloc(n*sizeof(int));
memset(randnum,0,n*sizeof(int)); //把n个位置全部置0
srand(time(NULL));
while(m)
{
int cur=rand()%n;
if (randnum[cur]==0) //进行判断,如果当前数没有选择过,则选择并输出
{
cout<<cur<<endl;
randnum[cur]=1;
m--;
}
}
free(randnum);
}
这种方法简单易懂,但是需要额外的空间来确保取出的数不重复,那么我们有没有更为简单的方法呢,答案是肯定的
2、先上代码,后做解释
[cpp] view plaincopy
void mRand(int n ,int m)
{
srand(time(NULL));
for (int i=0;i<n;i++)
{
if(rand()%(n-i)<m)
{
cout<<i<<endl;
m--;
}
}
}
上边的代码虽然简洁,但是不易懂,我们接下来说明一下
首先是一个循环,这个循环确保了输出的数是不重复的,因为每次的i都不一样
其次是m个数,在每次循环中都会用rand()%(n-i)<m来判断这个数是否小于m,如果符合条件则m减1,直到为0,说明已经取到m个数了
再次是如何保证这m个数是等概率取到的
在第一次循环中i=0, n-i=n, 则随机数生成的是0-n-1之间的随机数,那么此刻0被取到的概率为 m/n-1
在第二次循环中i=1,n-i=n-1,则随机数生成的是0-n-2之间的随机数,这时1被取到的概率就和上一次循环中0有没有取到有关系了。假设在上一次循环中,没有取,则这次取到的1的概率为 m/n-2;假设上一次循环中,已经取到了,那么这次取到1的概率为m-1/n-2,所以总体上这次被取到的概率为 (1-m/n-1)*(m/n-2)+(m/n-1)*(m-1/n-2),最后通分合并之后的结果为m/n-1和第一次的概率一样的
同理,在第i次循环中,i被取上的概率也为m/n-1
所以这m个数是等概率取到的
参考:http://blog.csdn.net/dlengong/article/details/7932579
相关文章推荐
- 在0~N(不包括N)范围内随机生成一个长度为M(M <= N)且内容不重复的数组
- randnumber_mn.c 生成一个数组,包括k个不重复的整数,并且要求这些整数范围为[m,n),生成的结果中不能包含inum中的数字,size表示inum的长度
- js生成指定范围内指定长度随机不重复的字符串数组
- java 一个int数组 长度为100 随机生成100个数 即1-100 将其插入进数组 插入的数字不能重复
- 生成一个长度为100的数组,为数组中的每一项随机填充1-100之间的数并且保证不重复
- 生成一个数组,包括k个不重复的整数,并且要求这些整数范围为[0,n)
- java 一个int数组 长度为100 随机生成100个数 即1-100 将其插入进数组 插入的数字不能重复
- js实现生成一个指定长度为n且随机不重复的数组
- 随机生成30个数字(范围0-30)存到一个数组中,将数组中重复的数字去除,动态创建数组保存剩下的数字
- 关于一道 产生一个int数组,长度为100,并向其中随机插入1-100,并且不能重复
- 【转】产生一个int数组,长度为100,并向其中随机插入1-100,并且不能重复
- 温故知新--数组(产生一个int数组,长度为100,并向其中随机插入1-100,并且不能重复。)
- 产生一个int数组,长度为100,并向其中随机插入1-100,并且不能重复
- 产生一个int数组,长度为100,并向其中随机插入1-100,并且不能重复。
- 给定一个长度为N的数组,其中每个元素的取值范围都是1到N。判断数组中是否有重复的数字。(原数组不必保留)
- a 产生一个int数组,长度为100,并向其中随机插入1-100,并且不能重复。
- 面试题:C#声明一个100大小的数组 随机生成1-100之间不重复的数
- 产生一个int数组,长度为10,并向其中随机插入1-10,并且不能重复
- 产生一个int数组,长度为100,并向其中随机插入1-100,并且不能重复。
- 编程产生一个int数组,长度为30,并向其中随机插入1-30,并且不能重复输出数组。实现一个冒泡排序算法对其进行排序,输出排序结果