【问题标题】:Verify Knuth shuffle algorithm is as unbiased as possible验证 Knuth shuffle 算法是否尽可能无偏见
【发布时间】:2009-11-06 04:07:38
【问题描述】:

我正在为我正在处理的 C++ 项目实现Knuth shuffle。我试图从我的洗牌中获得最公正的结果(而且我不是(伪)随机数生成方面的专家)。我只是想确保这是最公正的 shuffle 实现。

draw_t 是字节类型(typedef'd to unsigned char)。 items 是列表中的项目数。我在下面包含了random::get( draw_t max ) 的代码。

for( draw_t pull_index = (items - 1); pull_index > 1; pull_index-- )
{
    draw_t push_index = random::get( pull_index );

    draw_t push_item = this->_list[push_index];
    draw_t pull_item = this->_list[pull_index];

    this->_list[push_index] = pull_item;
    this->_list[pull_index] = push_item;
}

我使用的随机函数已被修改以消除modulo biasRAND_MAX 分配给 random::_internal_max

draw_t random::get( draw_t max )
{
    if( random::_is_seeded == false )
    {
        random::seed( );
    }

    int rand_value = random::_internal_max;
    int max_rand_value = random::_internal_max - ( max - ( random::_internal_max % max ) );

    do
    {
        rand_value = ::rand( );
    } while( rand_value >= max_rand_value );

    return static_cast< draw_t >( rand_value % max );
}

【问题讨论】:

  • 旁注:c++ STL 包含算法random_shuffle。以防万一你不知道。
  • 我不知道 STL 的实现。我还没有花时间学习 STL(我只是一个 Win32/.NET 人),我希望这个项目有一个最小的学习曲线(时间限制),但我会把它带入考虑。
  • 你的随机数生成器可以有多少种不同的状态?如果小于52!那么就会有永远无法产生的洗牌(注意 2^32 甚至 2^64 比 52 小得多!)。您需要使用状态数多于 52 的 RNG!如果您想获得公正的结果:也许可以试试 Mersenne Twister。
  • C++ STL 也(现在)包含了非常棒的 RNG,用于此目的

标签: c++ algorithm shuffle random knuth


【解决方案1】:

好吧,作为黑盒测试,您可以做的一件事是采用一些相对较小的数组大小,对其执行大量随机播放,计算您观察每个排列的次数,然后执行 Pearson's Chi-square 测试以判断结果是否在置换空间上均匀分布。

另一方面,只要索引来自的随机数生成器是无偏的,Knuth shuffle(又名 Fisher-Yates shuffle)就被证明是无偏的。

【讨论】:

  • 我会研究排列计数; Pearson 的卡方检验(据我阅读)在数学方面看起来有点超出我的水平,但我会继续阅读它。
  • 你真的不需要像卡方那样去测试洗牌是否有偏差。像标准偏差这样简单的东西将适用于相同类型的测试(随着洗牌次数的增加,您应该会看到标准偏差接近零)。
  • @Adam:我只是认为使用卡方会很容易,因为我写了一个库函数来处理 Pearson 的卡方。我已经忘记了它是如何工作的细节,但我仍然记得如何使用它。唯一的问题是它是用 D 语言编写的,而不是 C++。应该有可以做到这一点的 C++ 库,但如果您还没有一个方便可用的库(安装等),那么它可能是矫枉过正。
  • @dsimcha:你愿意分享你的卡方 D 库吗?我确信我可以毫不费力地将它移植到 C++(当然,仅供个人使用)。 (您可以通过 gmail 的 firstname dot lastname 与我联系。)
  • 但它实际上是一个完整的统计库。实际的卡方函数非常短,但您必须移植或找到一大堆较低级别功能的 C++ 等效项,例如不完整的 gamma 函数。这可能有点矫枉过正,但我​​想我会与你分享,以防万一。或者,您可以让您的 C++ 代码打印出值并将它们粘贴到 R 之类的统计数据包中,或者如果此测试不必完全自动化,甚至只是观察它们而不是进行正式测试。
【解决方案2】:

如果我没看错,您的random::get (max) 不包括max

这一行:

draw_t push_index = random::get( pull_index );

然后会产生一个“经典”错误,因为您的 pull_indexpush_index 错误地永远不会相同。这会产生一种微妙的偏差,即您永远无法拥有洗牌前的物品。在一个极端的例子中,这种“洗牌”下的两项列表总是会颠倒过来。

【讨论】:

    【解决方案3】:

    看看 Jeff Atwood 的这篇文章:

    洗牌
    http://www.codinghorror.com/blog/archives/001008.html

    另见:

    天真的危险
    http://www.codinghorror.com/blog/archives/001015.html

    【讨论】:

    • 我知道伪随机数生成器的一般限制;你认为我应该考虑转向 Eric Lippert 的算法(为每个索引分配一个随机浮点值并排序)还是加强我的随机数生成器,也许通过利用密码学 API 或实现更好的 PRNG,如 Mersenne Twister 或 Blum Blum嘘?
    • 其实我错过了最好的帖子。请参阅:天真的危险 -- codinghorror.com/blog/archives/001015.html
    【解决方案4】:

    Knuth shuffle 本身可证明是无偏的:恰好存在一系列操作来产生每个可能的 shuffle。然而,你的 PRNG 不太可能有足够的状态位来表达每一个可能的洗牌,所以真正的问题是你的 PRNG 对于它实际产生的洗牌集是否“足够随机”,以及你的播种策略是否足够安全.

    只有您可以决定这一点,因为这取决于不够随机的洗牌的后果。例如,如果您处理的是真钱,我建议您改用加密安全的 PRNG 并改进您的播种策略。尽管大多数内置 PRNG 会产生良好的随机性,但它们也很容易进行逆向工程,并且不带参数调用 seed() 可能会根据当前时间进行播种,这很容易预测。

    【讨论】:

      【解决方案5】:
      #include <cstdlib> // srand() && rand()
      
      /** Shufle the first 'dim' values in array 'V[]'.
          - Implements the Fisher–Yates_shuffle.
          - Uses the standard function 'rand()' for randomness.
          - Initialices the random sequence using 'seed'.
          - Uses 'dim' swaps.
          \see http://stackoverflow.com/questions/1685339/
          \see http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_modern_algorithm
      */
      template <class T>
      void Fisher_Yates_shuffle( T* V, unsigned dim , unsigned seed ) {
          srand(seed);
          T temp;
          unsigned i,iPP;
      
          i   = dim-1;
          iPP = dim;
          while ( i>0 ) {
              unsigned j = rand() % iPP;
              if ( i!=j ) { // swap
                  temp = V[i]; V[i] = V[j]; V[j] = temp;
              }
              iPP = i;
              --i;
          }
      /*
          This implementation depends on the randomness of the random number
          generator used ['rand()' in this case].
      */
      }
      

      【讨论】:

        猜你喜欢
        • 2015-06-03
        • 2012-03-11
        • 1970-01-01
        • 2014-02-21
        • 1970-01-01
        • 2020-06-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多