【问题标题】:Indexing count of buckets桶的索引计数
【发布时间】:2011-03-08 09:29:46
【问题描述】:

所以,这是我的小问题。

假设我有一个桶列表 a0 ... an 分别包含 L 0 ... cn

桶的顺序很重要。我不能去交换它们。

现在,我想索引这些存储桶,以便:

  • 我知道项目的总数
  • 我可以查找第 i 个元素
  • 我可以在任何存储桶中添加/删除项目并有效地更新索引

看起来很简单吧?看到这些标准,我立即想到了一棵芬威克树。这才是它们真正的意义所在。

但是,当您考虑用例时,会出现一些其他用例:

  • 如果桶数下降到 L 以下,桶必须消失(暂时不要担心项目)
  • 如果存储桶计数达到 H,则必须创建一个新存储桶,因为该存储桶已满

我还没有弄清楚如何有效地编辑 Fenwick 树:删除/添加一个节点而不重建整个树...

当然我们可以设置 L = 0,这样就不需要移除了,但是添加项目是无法避免的。

那么问题来了:

您是否知道该索引的更好结构或如何更新 Fenwick 树?

主要关注的是效率,因为我确实计划实现它,所以缓存/内存方面的考虑值得担心。

背景

我正在尝试提出一种类似于 B 树和排名跳过列表但具有本地化索引的结构。这两种结构的问题是索引是沿着数据保存的,这在缓存方面效率低下(即您需要从内存中获取多个页面)。数据库实现表明,保持索引与实际数据隔离对缓存更友好,因此更高效。

【问题讨论】:

    标签: algorithm data-structures b-tree


    【解决方案1】:

    我仍然希望得到答案,但是根据@Moron 的建议,到目前为止,我可以提出以下建议。

    显然我的小 Fenwick Tree 想法不容易适应。在 fenwick 树的末端添加新的桶很容易,但在中间却不是,所以这是一种失败的原因。

    我们剩下 2 个数据结构:二叉索引树(讽刺的是 Fenwick 用来描述他的结构的名字)和排名跳过列表。

    通常,这不会将数据与索引分开,但是我们可以通过以下方式获得此行为:

    1. 使用间接:节点持有的元素是指向桶的指针,而不是桶本身
    2. 使用池分配,以便索引元素,即使彼此独立分配,仍然靠近内存,这将有助于缓存

    我更喜欢跳过列表而不是二叉树,因为它们是自组织的,所以我省去了不断重新平衡我的树的麻烦。

    这些结构将允许到达O(log N)中的第i个元素,我不知道是否有可能获得更快的渐近性能。

    另一个有趣的实现细节是我有一个指向这个元素的指针,但可能已经插入/删除了其他元素,我现在怎么知道我的元素的排名?

    如果存储桶指向拥有它的节点,这是可能的。但这意味着要么节点不应该移动,要么它应该在移动时更新存储桶的指针。

    【讨论】:

    • 对不起,帮不上忙。对于可以按位置插入/删除的动态指针数组,您可能可以使用具有顺序统计的平衡树(基本上保持后代的数量)。看看我的回答:stackoverflow.com/questions/3071497/…
    【解决方案2】:

    我已将您的问题理解为:

    每个桶都有一个内部顺序,桶本身也有一个顺序,所以所有元素都有一些顺序,你需要那个顺序中的第 i 个元素。

    解决这个问题:

    您可以做的是维护一个“累积值”树,其中叶节点 (x1, x2, ..., xn) 是存储桶的大小。节点的值是其直接子节点的值之和。将 n 保持为 2 的幂会使其变得简单(最后您始终可以用零大小的桶填充它),这棵树将是一棵完整的树。

    对应每个bucket你会维护一个指向对应叶子节点的指针。

    例如,假设桶大小为 2、1、4、8。

    树看起来像

         15
        /  \
       3    12
      / \  / \
     2  1  4  8
    

    如果要总计数,读取根节点的值。

    如果你想修改一些xk(即改变对应的桶大小),你可以沿着父指针向上走,更新值。

    例如,如果您将 4 个项目添加到第二个存储桶,它将是(标有 * 的节点是更改的节点)

         19*
        /   \
       7*    12
      / \   / \
     2  5*  4  8
    

    如果你想找到第 i 个元素,你沿着上面的树走,有效地进行二分搜索。您已经有一个左孩子和右孩子计数。如果 i > 当前节点的左子节点值,则减去左子节点值并在右树中递归。如果 i

    假设你想在上面的树中找到第 9 个元素:

    因为根的左孩子是 7

    由于 2

    你在第三个bucket对应的叶子节点。您现在需要选择该存储桶中的第二个元素。

    如果您必须添加一个新的桶,您可以通过添加一个新的根,使现有的树成为左子树并添加一个新的树,该树的大小加倍(如果需要),除了您添加的那个之外,其他所有桶(我们是新树最左边的叶子)。这将分摊 O(1) 时间以向树添加新值。需要注意的是,您只能在末尾添加一个存储桶,而不能在中间的任何位置添加。

    获取总数是 O(1)。 更新单个存储桶/查找项目是 O(logn)。

    添加新存储桶的摊销时间为 O(1)。

    空间使用量为 O(n)。

    你可以用 B-Tree 代替二叉树。

    【讨论】:

    • 我很高兴您对这个问题感兴趣 :) 但是,如果我只在末尾或开头添加存储桶,问题会更容易一些。相反,我希望能够在中间插入桶。你的结构在这方面仍然很有趣,它看起来很像标准的 B-Tree。例如,我可以用子树7 (4 3) 替换叶子 4,但它会使树不平衡。你给了我思考的食物:)
    • @Matthieu:这是一个有趣的问题 :-) 那么我理解对了吗?另外,插入是在您修改的存储桶旁边还是可以是任意的?
    • 你没看错。桶的插入实际上来自于在给定位置插入项目。如果你在这个位置填满了桶,而下一个桶没有足够的空间,你需要在它们之间插入一个或几个桶。同样,如果我们可以在桶为空时将其移除,那就太好了。
    • 我已按照您的建议阅读了有关 B-Trees 的信息。它们最初是为存储在磁盘上的内存和结构很少的系统编写的。所以我的问题实际上是一个简单的转置,因为我有一个小缓存和一个慢主内存:) 现在我需要考虑的只是实现以及如何将它们放入缓存行中!谢谢。
    猜你喜欢
    • 2012-01-01
    • 2017-01-26
    • 1970-01-01
    • 2012-06-08
    • 2020-07-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多