所以,这是我的小问题。
假设我有一个桶子列表a 0 ... a n ,它们分别包含L< = c 0 .. .c n < H项目。我可以决定L和H的限制。我甚至可以动态更新它们,但我认为它不会有多大帮助。
桶的顺序很重要。我不能去交换它们。
现在,我想将这些存储桶编入索引,以便:
好像很容易吗?看到这些标准,我立即想到了Fenwick树。这就是他们真正想要的。
但是,当您考虑用例时,其他一些用例会出现:
我还没有想出如何有效地编辑Fenwick树:删除/添加节点而不重建整个树...
当然我们可以设置L = 0,因此删除会变得不必要,但是无法真正避免添加项目。
所以这就是问题:
您知道这个索引的更好结构还是如何更新Fenwick树?
主要关注的是效率,因为我计划实施缓存/内存考虑值得担心。
背景:
我试图提出一个类似于B-Trees和Ranked Skip Lists但具有本地化索引的结构。这两种结构的问题在于索引是沿着数据保存的,这在缓存方面是低效的(即你需要从内存中获取多个页面)。数据库实现表明,保持索引与实际数据隔离对缓存更友好,因此更有效。
答案 0 :(得分:3)
我已将您的问题理解为:
每个桶都有一个内部订单,桶本身有一个订单,所以所有元素都有一些排序,你需要在那个订单中使用第i个元素。
要解决这个问题:
您可以做的是维护一个“累积值”树,其中叶节点(x1,x2,...,xn)是桶大小。节点的值是其直接子节点的值的总和。保持2的幂将使它变得简单(你总是可以用零大小的桶来填充它)并且树将是一个完整的树。
对应每个存储桶,您将维护指向相应叶节点的指针。
例如,说桶大小是2,1,4,8。
树看起来像
15
/ \
3 12
/ \ / \
2 1 4 8
如果您想要总计数,请阅读根节点的值。
如果你想修改一些xk(即改变相应的桶大小),你可以按照父指针向上走树,更新值。
例如,如果您向第二个存储桶添加4个项目(标有*的节点是更改的节点)
19*
/ \
7* 12
/ \ / \
2 5* 4 8
如果要查找第i个元素,可以沿着上面的树行走,有效地进行二分查找。您已经有一个左孩子和右孩子。如果我>左子节点当前节点的值,您减去左子节点值并在右侧树中递归。如果i< =左子节点值,则向左移动并再次递归。
假设你想在上面的树中找到第9个元素:
由于root的左子女是7 < 9。 你从9减去7(得到2)然后向右走。
从2&lt; 4(左边的12个孩子),你往左走。
您位于与第三个存储桶对应的叶节点。您现在需要选择该存储桶中的第二个元素。
如果你必须添加一个新的存储桶,你可以通过添加一个新根来增加树的大小(如果需要),使现有树成为左子节点,并添加一个除了添加的存储树之外的所有零存储桶的新树(我们是新树的最左边的叶子)。这将在O(1)时间内分摊,以便为树添加新值。警告你最后只能添加一个桶,而不是中间的任何地方。
获得总数是O(1)。 更新单个存储桶/项目查找是O(logn)。
添加新桶分摊O(1)。
空间使用是O(n)。
您可以使用B树进行相同的操作,而不是二叉树。
答案 1 :(得分:0)
我仍然希望得到答案,但是在@Moron
建议之后,这是我到目前为止所能提出的。
显然,我的小 Fenwick Tree 想法无法轻易改编。在fenwick树的末端添加新桶很容易,但在中间没有它,所以它是一种失败的原因。
我们留下了2个数据结构:二进制索引树(具有讽刺意味的是Fenwick用来描述他的结构的名称)和排名跳过列表。
通常,这不会将数据与索引分开,但我们可以通过以下方式获得此行为:
我倾向于选择Skip Lists到Binary Trees,因为它们是自组织的,所以我不遗余力地不断地重新平衡我的树。
这些结构允许到O(log N)
中的第i个元素,我不知道是否有可能获得更快的渐近性能。
另一个有趣的实现细节是我有一个指向这个元素的指针,但其他人可能已被插入/删除,我现在如何知道我的元素的等级?
如果存储桶指向拥有它的节点,则可能。但这意味着要么节点不应该移动,要么在移动时应该更新存储桶的指针。