【问题标题】:Generating exactly certain number of unique random numbers from a specific range从特定范围内准确生成一定数量的唯一随机数
【发布时间】:2012-08-19 07:12:27
【问题描述】:

考虑给我一个特定的范围(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;
    }

有什么更好的方法可以做到这一点?

【问题讨论】:

标签: c++ random vector


【解决方案1】:

您似乎陷入了一种想法,即您必须以某种方式预先生成您的随机数。为什么?你说最终的任务是从向量中删除一些随机元素。对于该特定问题,无需预先生成所有随机索引。您可以简单地“即时”生成这些索引。

对于这个特定的任务(即删除向量中 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) 相比相当大的情况下效果最佳,因为它的复杂性与 N 相关。在您的情况下,MN 的 50%,使用 Knuth 算法是一个不错的主意。

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

但是,在您的情况下,您正试图从 vector 中删除元素。向量元素删除以N为主,这无论如何都打败了Bob Floyd算法的好处。

【讨论】:

  • 但是我会从数组的末尾开始 - 删除时 move 的数据更少 ;)
  • @Michael Krelin - 黑客:好吧,如果你不使用vector::erase,而是聪明地,通过手动将幸存的矢量元素复制到左侧,实现将起作用无论方向如何,都可以有效地(即一次通过)。每个元素只会被复制一次。实际上,std::remove_if 就是这样工作的。
【解决方案2】:

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

【讨论】:

  • 除了需要 250 万个节点来管理号码之外,这并不能避免检查唯一号码;它只是将检查的责任推入unordered_set 对象。当然,unordered_set 会比vector 更有效地执行此操作,但生成 250 万个唯一值仍然需要很多时间。
  • @Pete:我怀疑它有那么糟糕——最坏的情况是只选择了一半的索引,因此需要“重新滚动”,这意味着你只需要做两次与您需要的值的数量一样多的选择。但我没有测试过。
  • @SteveJessop - 不是重复,而是内存分配、搜索和重新平衡。
  • @Pete:好的,但是unordered_set 是一个哈希集,所以它不应该太慢。我想这取决于您所说的“很多时间”是什么意思。很多毫秒,当然:-)
  • @SteveJessop - 你是对的。我在想set。仍然存在内存开销,每个条目一个节点。但是使用unordered_set 搜索和插入要快得多。
【解决方案3】:

最简单的编码方式:

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_number 函数对于 minmax 的某些值有很大的偏差,因此您可能需要对随机数生成的一般理论进行更多研究。

【讨论】:

    【解决方案4】:

    生成第一个数字

    而且您不必生成比您需要的更多的数字。

    【讨论】:

    • 好吧,这些数字不会太随机:如果你看到一个数字 > 5M-1,你可以确定其他数字将是
    • 随机性会小很多吗?
    • 真正的随机数有可能全部等于 5M-1 -- 你的算法永远不会这样。
    • 而且你可以确定你没有更多的元素。你知道,无论如何,第二个数字的范围会更小。
    • 哦,我明白了。但无论如何,在你的情况下,平均值会偏向 0。
    猜你喜欢
    • 2013-01-22
    • 2015-10-28
    • 2016-05-04
    • 2018-12-17
    • 1970-01-01
    • 2011-08-02
    • 2015-04-08
    相关资源
    最近更新 更多