正如标题所述,我每次运行这个小程序时都试图创建一个独特的随机数序列。
但是,有时我得到的结果如下:
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:为了澄清,这只是一个简单的测试程序,用于更大规模的实施。因此,不是rand%3的3次迭代,而是运行1000或更多rand%50。 如果我在其操作的某个时刻看到102,我想要它,以便我再也看不到102.
答案 0 :(得分:5)
首先,如果您要使用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个结果(即所有可能的结果)会发生什么。在这种情况下,当你生成最后一个结果时,实际上只剩下一种可能性了 - 而不是只产生一种可能性,你会产生一个又一个的随机数,直到你偶然发现剩下的一种可能性。
对于这种情况,有更好的方法来完成这项工作。例如,您可以从容纳所有可能数字的容器开始。要生成输出,请在该容器中生成随机索引。您输出该数字,并从容器中删除该数字,然后重复(但这次,容器是一个较小的容器,因此您将随机索引的范围减少一个)。这样,您生成的每个随机数都会产生一个输出。
可以通过改组数字来实现同样的目的。这有两个缺点。首先,你需要正确地改变它们 - 一个Fischer-Yates shuffle很好地工作,但是否则很容易产生偏见。其次,除非你实际上使用数组中的所有(或非常接近全部)数字,否则这是低效的。
对于极端情况,请考虑使用一些(例如10个)64位数字。在此,首先使用2 64 -1中的数字填充数组。然后你做2 64 -2交换。因此,您只需要执行大约2次 65 操作即可生成10个数字。在这种极端的情况下,问题应该是非常明显的。虽然如果每个产生(比方说)1000个32位的数字并不那么明显,你仍然会有相同的基本问题,只是程度稍低。因此,虽然这是针对少数特定情况执行操作的有效方法,但其适用性相当狭窄。
答案 1 :(得分:0)
生成一个包含27个三位数字的数组,其数字小于3. Shuffle。根据需要迭代洗牌数组,值将是唯一的,直到你用完所有值。
正如其他人所指出的那样,不要继续重新种植随机数生成器。另外,rand
是一个糟糕的生成器,你应该使用C ++标准库中更好的选择之一。
答案 2 :(得分:0)
您实际上正在生成一个三位数的基数3。使用您选择的RNG生成0 ... 26范围内的基数为10的数字,并将其转换为基数3.即可得到000 .. 222。
如果你绝对必须避免重复,那么就像pjs建议的那样洗牌。这将导致后来的数字随机性降低。而不是之前的数字,因为它们来自较小的池。