【问题标题】:An efficient data structure for access by ID and finding the weighted random item一种高效的数据结构,用于通过 ID 访问并找到加权随机项
【发布时间】:2020-10-06 14:11:40
【问题描述】:

能否请您帮助我了解允许O(logN)(或至少O(sqrtN))对以下内容进行操作的数据结构:

  1. 插入具有ID (int64_t) 和health (double) 的项目
  2. 删除ID 的项目
  3. 找到一个由health随机加权的项目

首选语言是 C++ 或 C。加权随机是指:

考虑totalHealth=Sum(health[0], health[1], ..., health[N-1])。我需要一个快速(如上所述)的操作,相当于:

  1. 计算const double atHealth = rand_uint64_t()*totalHealth/numeric_limits<uint64_t>::max();
  2. 遍历i=0 to N-1 以找到第一个i 使得Sum(health[0], health[1], ..., health[i]) >= atHealth

约束:health[i] > 0rand_uint64_t() 返回一个在0numeric_limits<uint64_t>::max() 之间均匀分布的整数值。

到目前为止,我尝试的是 C++ unordered_map,它允许 ID 快速插入 (Θ(1)) 并通过 ID 删除,但操作 #3 在 N 中仍然是线性的,如中所述我上面的伪代码。

非常感谢您的帮助!

【问题讨论】:

  • 如果我对您的理解正确,您想要一个快速**分位数函数**(en.wikipedia.org/wiki/Quantile_function) 的数据结构?唯一的区别是你的分布是非标准化的。
  • 如果您正在搜索,您将需要一个有序的结构,您是否尝试过map?如果你不喜欢stl容器,还有abseil.io/docs/cpp/guides/container这些。
  • @SebastianHoffmann,我也不确定(对概率论不是很熟悉),但 health 的值并没有绝对归一化:它们总和不等于 1,我认为它们的标准化将是一个额外的操作,也很繁重。

标签: c++ algorithm performance data-structures


【解决方案1】:

我想不出使用现有 STL 容器的方法,但如果您愿意编写自己的二叉树,我可以想到一种方法。诀窍是每个节点都保持其左侧所有节点的总体运行状况(它不需要担心其右侧的节点,如下所示)。然后,如果您按 ID 顺序遍历树,您还可以在 log(n) 时间计算“累积健康”,同样按 ID 顺序。因此,树按 ID 和累积健康排序,您可以在 log(n) 时间按 ID 或“累积健康”进行查找。例如,考虑一个非常简单的树,如下所示:

         ID: 8
         h: 10
         chl: 15
   +-------|--------+
   |                |
   ID: 4          ID: 10
   h: 15          h: 7
   chl: 0         chl: 0

在上面的h 是节点的健康状况,chl 是它剩下的所有节点的累积健康状况。因此,上述所有节点的总运行状况为 15 + 10 + 7 = 32(我假设您单独维护该计数,尽管您也可以正确跟踪节点的累积运行状况,而您不需要)。我们来看3个案例:

  1. 您计算一个atHealth < 15。然后在第一个节点上,您可以看到您的值小于 chl,因此您知道您需要向左走,最终到达正确的叶子节点。
  2. 您计算了一个atHealth >= 15 < 25,因此您知道它 > 15,因此您不会在根节点左转,您所在的节点的运行状况为 10 和 10 + 15 意味着该节点的累积运行状况介于 15 和25 所以你很好。
  3. 您计算一个atHealth >= 25。每次您访问一个节点并向右走时,您必须添加您所在的节点的 chlh,以便在您遍历树时继续计算累积运行状况,这样您就知道您从 10 + 25 = 25 开始走对,然后您将其添加到之后遇到的任何节点的hchl。因此,您可以快速找到右边的节点是正确的。

当您插入一个新节点时,您会在遍历树时增加每个父节点的总健康度,而当您移除一个节点时,您会从总健康度中减去返回树的路径。因此,插入和删除仍然是O(log(n)),并且通过ID 查找也是log(n),无论是通过ID 还是通过atHealth

如果你想保持一棵平衡的树,事情显然会变得更加复杂,但它仍然是可行的。

【讨论】:

  • 感谢您的回答。因此,不是“每个节点都维护其左侧所有节点的总体健康”,而是您的意思是该节点维护其左子树的总体健康?因为在您的 3 个节点示例中,它看起来像这样,而在稍后的插入操作描述中,您似乎只更新了受影响的子树。
  • 然后在插入和删除时,您不会增加/减少每个父节点的总运行状况,而只是您从左子树获得的父节点。
  • 是的,每个节点都维护其左子树的总体健康状况。是的,您只需要在插入或删除时更新相关节点。例如,对于在每个节点处插入,如果您向左移动,您首先将节点的总运行状况与要插入的节点的运行状况相加,但如果您向右移动,则无需执行任何操作。
  • 感谢您的好主意!我已经实现了类似的东西:只是为了更容易地重新平衡 AVL 树,我在每个节点中维护总 subtreeHealth,而不仅仅是左子树。它没有太大变化,只是子树的subtreeHealth 存储在哪里:在它的父节点或祖父节点中。所以你的想法已经在生产中了:)
猜你喜欢
  • 2012-11-05
  • 2013-02-22
  • 2010-10-27
  • 1970-01-01
  • 2013-04-06
  • 2015-08-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-28
相关资源
最近更新 更多