【问题标题】:Weighted Random Number generator with updates带更新的加权随机数生成器
【发布时间】:2020-03-14 20:56:53
【问题描述】:

我的问题是这个问题的延伸:Weighted random numbers

我正在尝试实现加权随机数。我目前只是 把我的头撞在墙上,无法弄清楚。

在我的项目中(Hold'em 手牌范围,主观全押赢率 分析),我正在使用Boost的随机函数。所以,假设我想要 选择 1 到 3 之间的随机数(因此选择 1、2 或 3)。升压的 mersenne twister 发电机就像一个魅力。但是,我 希望选择权重,例如:

1 (weight: 90) 2 (weight: 56) 3 (weight:  4)

Boost 是否为此提供了某种功能?

扩展:允许用户动态改变给定键的权重。

如何最佳地解决问题?

天真的解决方案可能是扫描所有元素,根据新的权重调整所有元素的权重……但更新的时间为 O(n)。这是非常低效的。我们如何做得更好?

我希望update(key, w)get() 优于或等于O(logn)

【问题讨论】:

  • 为什么是python标签?
  • 您注意到 Boost.Random 文档中的discrete_distribution了吗?
  • @Shawn,现在在std
  • @GiovanniCerretani 是的,是的。但是,OP 指出他使用的是 Boost 库,而不是标准库。
  • 然而,discrete_distribution 没有改变概率的接口。唯一的方法是创建一个新的discrete_distribution

标签: c++ algorithm random


【解决方案1】:

一种可能的解决方案来自算术编码和Fenwick trees

如果你有一个非负数列表,[a_0, ... a_n] 类型为T,Fenwick 树数据结构允许你在O(log n) 时间内实现以下两个函数:

  1. Index upper_bound(T p):对于给定值p,计算最小索引i,使得前缀和a_0 + ... + a_i > p
  2. set(Index i, T p):更新a_i <- p

生成随机i的算法很简单:生成一个随机数k均匀分布在[0, sum a_i)范围内,然后使用i = upper_bound(k)找到i

简单示例:

i            0 1 2 3 4 5 6 7
a_i          0 1 0 0 3 4 0 2
prefix_sum   0 1 1 1 4 8 8 10

k                   0 1 2 3 4 5 6 7 8 9
i = upper_bound(k)  1 4 4 4 5 5 5 5 7 7

P.Fenwick. A New Data Structure for Cumulative Frequency Tables(PDF,1994 年)

My C++ implementation of a Fenwick tree(未彻底测试)

【讨论】:

  • 不错!这是满足时间复杂度标准的唯一解决方案。我想我必须开始阅读论文才能通过面试:(
  • @nz_21,这个解决方案很快,但我不确定它是不是最好的。如果您找到替代的快速解决方案,请告诉我。
  • Evg 足够快,足以满足面试官的限制 :)
  • 请注意,如果所有权重都大于 0,lower_bound 效果最好。
  • 值得注意的是,如果第一个项目的重量为零,lower_bound 可能会导致错误的结果。
【解决方案2】:

您同时标记了 PythonC++,我不确定 Python,但在 C++ 中,这实际上是 STL 的一部分。看看piecewise_constant_distribution

【讨论】:

  • 你如何“动态改变给定键的权重”?
  • RNG 将权重分布作为参数,所以在下次调用时更改它?如何更改取决于 OP。
  • 即重新创建分发对象。但我想这不是 OP 想要的。
  • 好吧,您实际上不需要重新创建它,您可以将它作为字段存储在某个地方,并在需要时进行更改。无论哪种方式,RNG 引擎仍然是相同的,所以不确定为什么这对 OP 不起作用。
  • 每次你想改变i-th的概率,你必须创建一个新的分布。这是一个O(n) 操作,但 OP 想要比这更快的东西。
【解决方案3】:

pythonnumpy 有一个函数numpy.random.choice,允许您设置概率(总和为 1)。所以你可以用你的重量做:

weights = [90, 56, 4]
np.random.choice([1, 2, 3], p=[w / sum(weights) for w in weights])

我不知道复杂性,但是numpy 是一个非常高效的库,所以你可以挖掘它的文档和实现。

【讨论】:

    【解决方案4】:

    如果您使用accepted answer 中的算法,您只需在更改单个权重时更新sum_of_weight

    sum_of_weight -= choice_weight[index];
    sum_of_weight += new_weight;
    choice_weight[index] = new_weight;
    

    【讨论】:

    • 你需要重建整个累积分布来利用二分搜索。重建部分是 O(n)
    • @nz_21 如果您希望得到的答案涉及 (stackoverflow.com/a/1761646/4253931) 中的建议优化,您应该在问题中提及。
    【解决方案5】:

    为什么不使用加权列表(生成器)中的简单 random.choice。让我知道它是否有效:

    import random
    generator  = [1] * 90 + [2] * 56 + [3] * 4 #1 (weight: 90) 2 (weight: 56) 3 (weight:  4)
    random.choice(generator)
    

    【讨论】:

      【解决方案6】:
      int main()
      {
          std::mt19937::result_type seed = std::random_device()();
          auto engine = std::mt19937(seed);
      
          auto initial_weights = { 90.0, 56.0, 4.0 };
          auto distribution = std::discrete_distribution<>(initial_weights);
      
          // Use the original distribution
          for (auto i = 0; i != 20; ++i)
          {
              std::cout << distribution(engine) << std::endl;
          }
      
          std::cout << std::endl;
      
          // Modify the distribution temporary when generating random numbers
          for (auto i = 0; i != 20; ++i)
          {
              auto param = std::discrete_distribution<>::param_type{ 90.0 - 4.5 * i, 56.0, 4.0 + 5.0 * i };
              std::cout << distribution(engine, param) << std::endl;
          }
      
          std::cout << std::endl;
      
          // Make a permanent change to the distribution
          auto param = std::discrete_distribution<>::param_type{ 30.0, 56.0, 40.0 };
          distribution.param(param);
      
          // Use the modified distribution
          for (auto i = 0; i != 20; ++i)
          {
              std::cout << distribution(engine) << std::endl;
          }
      
          std::cout << std::endl;
      
          return 0;
      }
      

      【讨论】:

        猜你喜欢
        • 2013-11-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-01
        • 2010-12-04
        • 2012-09-13
        • 2014-02-05
        • 2022-07-06
        相关资源
        最近更新 更多