C++;random_shuffle()它是如何工作的?

C++;random_shuffle()它是如何工作的?,random,c++-cli,shuffle,srand,Random,C++ Cli,Shuffle,Srand,我有一个52张牌的牌组向量,我想洗牌 vector<Card^> cards; 问题是它每次给我的结果都是一样的,所以我使用了srand将其随机化: srand(unsigned(time(NULL))); random_shuffle(cards.begin(),cards.end()); 这仍然不是真正随机的。当我开始发牌时,和上次一样。例如:“1.交易:A,6,3,2,K;2.交易:Q,8,4,J,2”,当我重新启动程序时,我得到了完全相同的交易顺序 然后我使用了sran

我有一个52张牌的牌组向量,我想洗牌

vector<Card^> cards;
问题是它每次给我的结果都是一样的,所以我使用了
srand
将其随机化:

srand(unsigned(time(NULL))); 
random_shuffle(cards.begin(),cards.end());
这仍然不是真正随机的。当我开始发牌时,和上次一样。例如:“1.交易:A,6,3,2,K;2.交易:Q,8,4,J,2”,当我重新启动程序时,我得到了完全相同的交易顺序

然后我使用了
srand()
random\u shuffle
及其第三个参数:

 int myrandom (int i) {
    return std::rand()%i; 
 }
 srand(unsigned(time(NULL))); 
 random_shuffle(cards.begin(),cards.end(), myrandom);

现在它工作了,在重新运行时总是给我不同的结果,但我不知道为什么它是这样工作的。这些函数是如何工作的,我在这里做了什么?

< P>这个答案需要一些调查,看看VC++中C++标准库头,看看C++标准本身。我知道该标准是怎么说的,但我对VC++(包括C++CLI)的实现很好奇

首先,标准对std::random\u shuffle有何规定。我们可以找到。特别是它说:

对给定范围内的元素重新排序[第一,最后一个],使这些元素的每个可能排列具有相同的出现概率

1) 随机数生成器是实现定义的,但函数std::rand常用的

粗体部分是关键。标准规定RNG可以是特定于实现的(因此不同编译器的结果会有所不同)。标准建议经常使用
std::rand
。但这不是一个要求。因此,如果一个实现不使用
std::rand
,那么它很可能不会使用
std::srand
作为起始种子。一个有趣的脚注是,
std::random\u shuffle
函数从C++14开始就被弃用了。然而,
std::shuffle
仍然存在。我的猜测是,由于
std::shuffle
要求您提供一个函数对象,因此在生成随机数时,您明确定义了所需的行为,这是较旧的
std::random\u shuffle
的优势

我取了VS2013,查看C++标准库头,发现<代码>:

优点Mersenne捻线器的常用版本MT19937产生32位整数序列,具有以下理想特性:

它有一个非常长的周期2^19937− 1.虽然在随机数生成器中,长周期并不能保证质量,但短周期(如许多旧软件包中常见的2^32)可能会有问题

它是k分布到32位精度,每1≤ K≤ 623(见下文定义)

它通过了许多统计随机性测试,包括顽固测试

幸运的是,C++ 11标准库(我认为应该在VS2010和以后的C++/CLI上工作)包含一个MelShan-Twitter函数对象,它可以与 STD::Sffffle < /Cord>一起使用。请参见此更详细的说明。前面提供的C++标准库引用实际上包含了这样的代码:

std::random_device rd;
std::mt19937 g(rd());

std::shuffle(v.begin(), v.end(), g);
需要注意的是,
std::random_device
会产生不确定(不可重复)的无符号整数。如果我们想在Mersenne Twister(
std::mt19937
)PRNG中植入,我们需要不确定的数据。这在概念上类似于在
rand
中植入
srand(time(NULL))
(后者不是一个很好的随机性来源)

这看起来很好,但在处理卡片洗牌时有一个缺点。Windows平台上的无符号整数是4字节(32位),可以存储2^32个值。这意味着只有4294967296个可能的起点(种子),因此洗牌的方法只有那么多。问题是有52个!(52阶乘)洗牌标准52张牌组的方法。这恰好是80658175170943878571606368685403766975289505440883277824000000000000种方法,远远大于我们通过设置32位种子可以获得的唯一方法的数量

谢天谢地,Mersenne Twister可以接受0到2^19937-1之间的种子。52!是一个很大的数字,但所有组合都可以用226位(或~29字节)的种子表示。标准库允许
std::mt19937
接受最多2^19937-1(~624字节的数据)的种子但由于我们只需要226位,下面的代码将允许我们创建29字节的非确定性数据,用作
std::mt19937
的合适种子:

// rd is an array to hold 29 bytes of seed data which covers the 226 bits we need */
std::array<unsigned char, 29> seed_data; 
std::random_device rd;
std::generate_n(seed_data.data(), seed_data.size(), std::ref(rd));
std::seed_seq seq(std::begin(seed_data), std::end(seed_data));

// Set the seed  for Mersenne *using the 29 byte sequence*
std::mt19937 g(seq);  
在Windows VC++/CLI上,您将收到一条警告,您希望使用上面的代码抑制该警告。因此,在文件顶部(在其他包含之前),您可以添加以下内容:

#define _SCL_SECURE_NO_WARNINGS 1

阅读
gen
参数上的内容。它显示了编译器对“未指定源”的想法当你运行程序时,你只看到了相同的运行问题吗?即,如果你在运行之间等待5秒,你得到不同的输出吗?那就是C++ + CLI,而不是C++。fSfM。当然,SRAND的问题是它只需要一个无符号int,所以你很可能是有限的。大约2 ^ 32个可能的种子值。我说大概是因为一个种子的1是特殊的(许多实现也使得种子有0个特殊)。。但是使用32位int的种子组合要少得多。我不推荐直接使用srand/32位种子进行洗牌。您需要226位种子才能覆盖所有组合。我认为使用数组来容纳29个随机字节不会有什么区别。std::mt19937是一个32位数字的生成器,因此洗牌的输入将再次是li斜接到32位数字。没有?@LyK
std::shuffle(cards.begin(),cards.end(), g);
#define _SCL_SECURE_NO_WARNINGS 1