【问题标题】:Efficently simulate rolling weighted dice (or traversing a weighted graph), with frequent updates有效地模拟滚动加权骰子(或遍历加权图),并频繁更新
【发布时间】:2012-01-06 18:23:02
【问题描述】:

我有一个加权的有向图,它包含大约 20,000 个节点。

  1. 给定图中的一个节点,我以与相对权重相关的概率随机选择一个相邻节点。
  2. 每次选择后,我都会收到有关选择是好是坏的反馈,并更新网络。例如,在一个错误的选择之后,我会降低指向所选节点的所有边的权重。

我学习了yesterday alias method 用于模拟滚动加权骰子,这与选择一个相同(每个节点是一个加权骰子,边对应于其他节点)。一卷是高效的,但更新权重不是;别名方法可能不合适,因为我将更新比我滚动的更多的骰子!

我应该使用哪种数据结构,允许频繁更新,以及哪种算法最适合做出选择?


一些想法/笔记:

  • 我可以通过记录每次权重调整来减少更新,然后仅在必要时实际更新节点/模具(即直接在滚动之前)。但我仍然会为 每个 卷预先计算一次别名数据。
  • 相反,我可以简单地按原样存储图形(这样更新成本很低)并放弃别名方法。我会在每次滚动之前即时计算相对权重(二进制搜索在这里有效)。
  • 动态计算相对权重的另一个好处是我可以将每个节点的“全局权重”分解出来,以进一步减少更新。然后,一个错误的选择将导致只有 2 次更新:传入边权重和节点的全局权重。
  • 添加:也许有一些介于两者之间的东西:一种在数据结构中保持局部相对权重的方法(例如树或别名方法),然后在每次滚动期间将它们与“全局权重”动态合并。

事实是,在实践中,我不需要经常做出选择(每分钟不超过一次),所以我不需要 最有效的解决方案。但这是一个有趣的项目,我有兴趣找到理论上的最佳解决方案。

【问题讨论】:

  • 您对权重进行了哪些更新?您是否要重新计算所有权重、仅一个权重、一半权重等?
  • @templatetypedef "例如,在一个错误的选择之后,我减少了指向所选节点的所有边的权重。"此外,当使用别名方法(或使用预先计算的二分搜索概率)时,更改一个传出权重意味着重新计算所有别名信息(或概率)。

标签: c algorithm data-structures graph-theory


【解决方案1】:

我认为您可以使用 log(k) 复杂度来做到这一点,其中 k 是骰子中的面数。

对于一个特定的节点,让 p1, p2, ..., pk 是相对概率。令 p1+p2,...,+pk = p。

用这些相对概率构造一个树结构作为叶子。每个非叶节点的父节点是其子节点的相对概率之和。要“掷骰子”,请在 0 和 p 之间绘制一个随机值,然后沿着它穿过树。当你想更新骰子面的相对概率时,只需改变相应的叶子节点值并通过树向上传播即可。

通过这种方式,您可以选择一个具有一个随机数的随机值,其中需要 log(k) 个步骤来找到与该随机数对应的叶子,并且当您更新一个叶子时,需要 log(k) 时间来更新树.

这是一个非常简单的解决方案描述,如果您需要完整的描述,请告诉我。我确信它有效,但不确定这是否足以满足您的需求。

总结一下,这个算法需要: 1. 0到p之间只有一个随机数 2.“掷骰子”(即找到下一个节点)的 O(log(k)) 复杂度,其中 k 是骰子中的面数 3. O(log(k)) 来“更新给定节点的骰子”。如果原始节点有 m 条边,则复杂度为 O(log(k1))+O(log(k2))...O((km)) 其中 k1, k2, ... km 是相邻节点的连通性节点。

====Tree Example====

如果骰子有 4 个面且相对概率分别为 1:50、2:80、3:20、4:70,则按如下方式构建树:

          220
       /       \
    130         90
   /   \      /    \
 50    80    20    70
  |    |     |      |
  1    2     3      4

生成一个0到220之间的随机数v。如果是v=100:走左边路线(因为10080)更新v = v-80 = 20. 由于我们在叶子声明 o/p 即 2

如果 v=210: left and v=v-130=80, left v=v-70=10, return leaf=4

如果 4 变为 60,则将 70 变为 60,将 90 变为 80,将 220 变为 210。

==== Lazy update variation ====

每当权重发生变化时,不要立即更新树。相反,只需将其标记为“脏权重”,等到您需要从该特定节点进行预测。

当您需要从特定节点进行预测并且如果某些权重不正确时,要么 a.仅使用脏节点或 b 更新树。更新整个树。如果脏权重的个数为t,总权重的个数为k,如果t*log(k)

【讨论】:

  • 这模拟了 O(log n) 时间内的掷骰子,在最坏的情况下需要 O(n log n) 进行更新,这意味着在最坏的情况下它比别名方法慢。
  • @ElKamina 是的,我跟着你。如果我构建和维护树,那么每卷可能会有数千次更新(因为我想更新一个节点的“全局”权重,它被合并到每棵树中)。在每次滚动之前从权重构建树可能会更好,如果是这样,那么全局权重可以存储在中心位置并且每次滚动仅更新一次,这是另一个好处。也许有一个中间地带,我在树中维护单个骰子权重,然后在每次滚动之前将其与第二个“全局”权重树有效地合并。想法?
  • @templatetypedef 如果我错了,请纠正我,别名方法是 O(n) 进行更新。因此,相比之下,我的方法在掷骰子(O(log(n)与 O(1))时效果更差,但更新效率非常高(O(log(n))与 O(n))
  • @ElKamina- 我担心的是我没有看到这个更新时间是 O(log n),因为如果你要存储累积概率,那么你必须更新所有搜索树中的值也高于被更改的概率,导致 O(n log n) 与一个朴素的实现一起工作,而 O(n) 与一个优化的实现一起工作。这并不比别名方法让我印象深刻。我错过了什么吗?
  • @templatetypedef 请参阅解决方案底部的示例,了解如何构建和更新树。
猜你喜欢
  • 1970-01-01
  • 2014-03-16
  • 1970-01-01
  • 2017-08-28
  • 1970-01-01
  • 1970-01-01
  • 2019-03-05
  • 2020-01-03
  • 2022-01-23
相关资源
最近更新 更多