如何评估一组可达到的随机排列的“质量”

计算科学 随机数生成
2021-12-01 21:32:58

要随机排列列表,必须选择其中之一n!可能性,这很快超出了传统 PRNG 的能力。即使 Mersenne Twister 的列表长度超过 2080 个条目也会失败,并且在实践中可能会失败得更快,因为不能保证以消除重复结果的方式使用它的状态。

因此,在设计算法以将列表从不充分状态中洗牌并将其设计为库函数(而不是特定于应用程序的解决方案)的过程中,通常应该满足(或最大化或最小化)首先,什么妥协可能是最良性的?

这是一般情况。显然,某些应用程序忽略了其他应用程序可能会导致灾难性故障的问题。

换句话说,当你事先知道它一定是不完美的(就像你知道 PRNG 是不完美的一样)时,你如何评估洗牌的质量?

我问的原因,作为我想检测的各种事情的一个例子,是在开发洗牌算法的过程中,我得到了一些明显不好的结果,但不确定将它们形式化的最佳方法是什么就像测试一样。

例如,一个严重有问题的洗牌是:

static uint32_t n;     // number of elements in list to shuffle
static uint32_t m;     // n rounded up to a power-of-two, minus one
static uint32_t state; // set to a random value to randomise order

void shuffle_seed(uint32_t seed) {
  state = s;
}

uint32_t next_index(void) {
  do {
    seed = (state * 1103515245 + 12345) & m;
  } while (state >= n);
  return state;
}

您可以调用此函数n时间并保证和之间的n唯一结果......所以这是一个排列;但是当您将一个种子的结果与另一个种子的结果进行比较时,您会发现它们是从不同点开始的相同序列。此外,每隔一个索引都是奇数,其他索引是偶数。0n

另一种稍微高级的设计给出了部分子字符串匹配,而不是整个匹配。

如果只能选择n要么nk(对于小k) 的不同排列n!,包含相似子字符串的可能是您首先要消除的。我预计还有许多其他我没有想到的糟糕选择。

2个回答

你可以做一些抽查。为所有人测试n!可能性是不切实际的,但是您可以进行一些单元测试,以确保在进行足够多的洗牌后,分布的某些属性是正确的。例如,使数组[1,2,,n]并检查(对于x=2,5,7,12)

  • 是指数的平均值x大约洗牌后n/2?
  • 是指数的方差x在接近其理论时刻后洗牌?
  • 是之间的平均距离xixj关闭理论平均距离(ij在测试集中)
  • xi>xj(洗牌后的指数)将近一半的时间?

你可以继续前进,直到你觉得它至少接近了洗牌的理论统计数据。(这是你问的吗?)

要置换列表,无需枚举 n! 可能性。改组列表就足够了。用于改组的伪代码(来自 wiki 文章)将是

for i from n−1 downto 1 do
    j ← random integer such that 0 ≤ j ≤ i
    exchange a[j] and a[i]

对于 c++11,std::shuffle 和一个好的 RNG 应该可以工作。

这篇 wiki 文章是我的来源。文章在“伪随机生成器”小节中确实注意到了以下内容

当 Fisher-Yates shuffle 与伪随机数生成器或 PRNG 一起使用时,会出现另一个问题:由于此类生成器输出的数字序列完全由其在序列开始时的内部状态决定,因此由这样的随机数生成器驱动的 shuffle生成器不可能产生比生成器具有不同可能状态更多的不同排列。即使可能状态的数量超过排列的数量,从数字序列到排列的映射的不规则性质意味着某些排列将比其他排列更频繁地发生。因此,为了最小化偏差,PRNG 的状态数应该比排列数至少高出几个数量级。

这个问题可以通过使用依赖于处理器噪声的真正 RNG(例如 std::random_device*)来避免。

* 虽然我猜 random_device 也有警告:http ://www.pcg-random.org/posts/cpps-random_device.html