从特定范围生成确切数量的唯一随机数

时间:2012-08-23 17:40:09

标签: c++ random vector

考虑我给出一个特定范围(0到5,000,000),我应该从这个范围产生2,500,000个唯一随机数。有效的方法是什么?我知道很难得到真正的随机数。

我尝试检查是否存在数字,以便生成新的随机数。但是计算需要几个小时。有没有更好的方法来做到这一点。

背后的原因是,我有一个大小为5,000,000的向量。我想把矢量缩小一半。即从矢量中删除随机50%的元素。

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

    #define NUMBER 2500000
    #define RAND_START 0
    #define RAND_END 5000000

    unsigned int generate_random_number(int min, int max)
    {
        return min + (rand() % (unsigned int)(max - min + 1));
    }

    int main(int argc, char* argv[])
    {
        unsigned int count = 0, random_number;
        vector<unsigned int> rand_vector;
        do 
        {   
            count++;
            random_number = generate_random_number(RAND_START,RAND_END);
// Tried to manually add a different number each time. But still not a considerable improvement in performance. 
            if (std::find(rand_vector.begin(), rand_vector.end(), random_number) != rand_vector.end())
            {
                if(random_number > count)
                    random_number = random_number - count;
                else
                    random_number = random_number + count;          
            }
            rand_vector.push_back(random_number);
            sort(rand_vector.begin(), rand_vector.end());
            rand_vector.erase(unique (rand_vector.begin(), rand_vector.end()), rand_vector.end());
        }while (rand_vector.size() != NUMBER);


        for (unsigned int i =0; i < rand_vector.size(); i++)
        {
            cout<<rand_vector.at(i)<<", ";
        }
        cout<<endl;
        return 0;
    }

我可以采用哪种更好的方法来做到这一点?

4 个答案:

答案 0 :(得分:5)

您似乎被锁定在一个想法上,您必须以某种方式预生成您的随机数。为什么?你说最终的任务是从向量中删除一些随机元素。对于该特定问题,不必预先预先生成所有随机索引。您可以“动态”生成这些索引。

对于这个特定任务(即删除向量中50%的元素),Knuth算法可以很好地工作(参见https://stackoverflow.com/a/1608585/187690)。

只需遍历从0N-1的原始向量的所有元素,并随机决定删除i - 元素的概率为N_to_delete / N_to_iterate,其中N_to_delete是仍然必须删除的元素数,N_to_iterate是向量剩余部分的长度。这种方法一次性完成(如果巧妙地实现),不需要额外的内存,也不需要反复试验。它完全按照您的要求执行:以相同的概率销毁50%的向量元素。

Knuth算法在随机值(M)的数量与范围的长度(N)相比相当大的情况下效果最佳,因为其复杂性与{{1}相关联}。在您的情况下,NM的50%,使用Knuth算法是个不错的主意。

当随机值的数量远小于范围(N)时,Bob Floyd算法(参见上面的链接)更有意义,因为它的复杂性由M << N定义而不是由M定义N。它需要额外的内存(一组),但在生成随机数时仍然不会进行反复试验。

但是,在您的情况下,您尝试从向量中删除元素。向量元素删除由N支配,无论如何都会破坏Bob Floyd算法的好处。

答案 1 :(得分:2)

如果您有唯一的号码,而不是手动检查,您可以使用例如std::unordered_set并继续生成数字,直到集合的大小为您想要的数字数量。

答案 2 :(得分:2)

最简单的编码方式:

std::random_shuffle(vectoshrink.begin(), vectoshrink.end());
vectoshrink.resize(vectoshrink.size() / 2);

如果你想维持vectoshrink中元素的顺序,请使用AndreyT的答案。

如果您确实想要提前选择索引:

std::vector<size_t> vec(vectoshrink.size());
// iota is C++11, but easy to do yourself
std::iota(vec.begin(), vec.end(), size_t(0));
std::random_shuffle(vec.begin(), vec.end());
vec.resize(vec.size() / 2);
// optionally
std::sort(vec.begin(), vec.end());

现在,您可以使用这些索引来缩小原始矢量,方法是将vec中索引处的元素复制到新矢量中,然后将结果与原始矢量交换。

在这两种情况下,random_shuffle所做的不仅仅是严格要求的,因为它会改变整个向量,而实际上我们只需要“洗牌”其中的一半。如果您阅读Fisher-Yates shuffle的工作原理,很容易看出,如果您自己编写代码,那么所需的唯一修改就是执行完全shuffle的步数的一半。但是,C ++没有标准partial_random_shuffle

最后,请注意默认随机源可能不是很好,因此您可能希望使用random_shuffle的三参数版本。对于generate_random_numbermin的某些值,您的max函数存在偏差,因此您可能希望对随机数生成的一般理论进行更多研究。

答案 3 :(得分:0)

生成第一个数字&lt; 5M,第二个数字&lt;(5M-1)等。每次删除元素后,您将减少一个元素,并且您不关心它是否是相同的数字。 ;-)这不会回答您关于唯一数字的问题,而是关于将矢量减半的问题。

您不必生成超出需要的数字。