试图在每次迭代中产生一个唯一的随机数序列

Trying to produce a unique sequence of random numbers per iteration

本文关键字:一个 唯一 随机数序列 迭代      更新时间:2023-10-16

正如标题所述,每次运行这个小程序时,我都试图创建一个唯一的随机数序列。

然而,有时我得到这样的结果:

102
201
102

的代码
#include <cstdlib>
#include <ctime>
#include <iostream>
using namespace std;
int main() {
        for (int i = 0; i < 3; i++) {
                srand (time(NULL)+i);
                cout << rand() % 3;
                cout << rand() % 3;
                cout << rand() % 3 << 'n' << endl;
        }
}

显然,srand没有我想要的那种神奇的功能。我希望能有个合理的破解方法?

Edit1:澄清一下,这只是一个简单的测试程序,它将在更大的范围内实现。所以我可能会运行1000次,或者更多的rand%50,而不是3次rand%3的迭代。如果我在它的操作中看到了102,我希望我再也不会看到102。

首先,如果您打算使用srand/rand,您希望在每次程序执行开始时为其播种一次(且仅一次):

int main() {
    srand(time(NULL));
    for (int i = 0; i < 3; i++) {
    cout << rand() % 3;
    cout << rand() % 3;
    cout << rand() % 3 << 'n' << endl;
}

其次,time通常只产生一个分辨率为1秒的结果,所以即使有了这个修正,如果您在同一秒内运行该程序两次,您可以期望它在两次运行中产生相同的结果。

第三,无论如何,您并不真的想使用srand/rand<random>中的随机数生成器通常要好得多(而且,也许更重要的是,它们被更好地定义为代表一个更广为人知的数量)。

#include <random>
#include <iostream>
int main() { 
    std::mt19937_64 gen { std::random_device()() };
    std::uniform_int_distribution<int> d(0, 2);
    for (int i = 0; i < 3; i++) {
        for (int j=0; j<3; j++)
            std::cout << d(gen);
        std::cout << "n";
    }
}
然而,根据编辑,这仍然不够。你真正想要的是一个没有重复的随机样本。要做到这一点,你需要做的不仅仅是生成数字。随机生成的数字不仅可以重复,而且如果你生成足够多的数字,不可避免地会重复(但即使不是不可避免的,重复的可能性也会变得相当高)。

只要生成的结果数量相对于可能的结果数量较少,就可以很容易地在生成结果时将结果存储在集合中,并且只将先前不存在于集合中的结果视为实际输出:

#include <random>
#include <iostream>
#include <set>
#include <iomanip>
int main() {
    std::mt19937_64 gen { std::random_device()() };
    std::uniform_int_distribution<int> d(0, 999);
    std::set<int> results;
    for (int i = 0; i < 50;) {
        int result = d(gen);
        if (results.insert(result).second) {
            std::cout << std::setw(5) << result;
            ++i;
            if (i % 10 == 0)
                std::cout << "n";
        }
    }
}

如果结果的数量接近可能结果的数量,这将变得非常低效。例如,假设您生成的数字从1到1000(所以有1000个可能的结果)。考虑一下如果你决定产生1000个结果(即所有可能的结果)会发生什么。在这种情况下,当你产生最后一个结果时,实际上只剩下一种可能性——但不是只产生那一种可能性,而是产生一个又一个随机数,直到你偶然发现剩下的一种可能性。

对于这种情况,有更好的方法来做这项工作。例如,您可以从一个容器开始,其中包含所有可能的数字。要生成输出,需要在该容器中生成一个随机索引。您输出该数字,并从容器中删除该数字,然后重复(但这一次,容器小了1,因此您将随机索引的范围减小了1)。这样,您生成的每个随机数都会产生一个输出。

如果可以通过对数字数组进行洗牌来完成相同的操作。但这有两个缺点。首先,你需要正确地洗牌——费雪-耶茨洗牌法效果很好,但否则很容易产生偏差。其次,除非您确实使用了数组中的所有(或非常接近所有)数字,否则这是低效的。

在极端情况下,考虑需要几个64位数字(例如10个)。在这里,首先用264-1中的数字填充数组。然后进行264-2交换。所以,你做了大约265操作来产生10个数字。在这种极端情况下,问题应该是相当明显的。虽然如果您生成1000个32位的数字就不那么明显了,但您仍然会遇到相同的基本问题,只是程度要小一些。因此,尽管对于一些特定情况,这是一种有效的方法,但其适用性相当有限。

生成一个包含27个小于3的三位数字的数组。洗牌。根据需要迭代经过洗牌的数组,值将是唯一的,直到耗尽所有值。

正如其他人指出的那样,不要一直重新播种您的随机数生成器。此外,rand是一个糟糕的生成器,您应该使用c++标准库中可用的更好的选择之一。

您正在有效地生成一个以3为基数的三位数。使用您选择的RNG生成0范围内的以10为基数的数字。26,换算成以3为底。那就有000…222 .

如果必须避免重复,那么按照pjs的建议对数组进行洗牌。这将导致后面的数字比前面的数字"不那么随机",因为它们取自较小的池。