【问题标题】:Data structure with efficient manipulation and retrieval by both key and index通过键和索引有效操作和检索的数据结构
【发布时间】:2011-12-05 11:24:06
【问题描述】:

我正在寻找具有例如功能的数据结构。 .NET 中的OrderedDictionary,即维护元素顺序的关联集合(即,将 keyvalue 关联的集合)(就像普通的 @ 987654322@ 确实如此)。

它必须通过索引和键快速查找。它还应该有一个快速的“追加”操作(在末尾插入一个新项目),以及快速删除具有任何索引的项目(基于索引或键)。

如果我没记错的话,.NET 中的OrderedDictionary 同时使用哈希表和数组来存储其项目。因此,根据键检索索引(反之亦然)是 O(n),当然从数组中间删除一个项目是 O(n) 开始,加上从键中添加的索引查找(如果按键删除)。

我的问题是是否存在满足我条件的更有效的数据结构,或者这确实是我最好的选择?

【问题讨论】:

    标签: .net data-structures dictionary lookup-tables


    【解决方案1】:

    我认为您可以使用两棵红黑树来做到这一点:一个键查找树,用于存储由比较函数排序的键,以及一个索引查找树,键以任意顺序排列,如列表中。每个索引查找节点必须有一个“大小”字段——如果每个节点中都包含一个“大小”字段,红黑树可以按索引进行查找。例如,参见 C5 Generic Collection Library 中的 RedBlackTreeSet 实现。

    键查找树中的每个条目都需要一个指向索引查找树中相应条目的指针。除了左节点和右节点指针,索引查找树还需要一个父指针字段来允许自下而上的导航以及自上而下的导航。

    每个键总共需要六个指针:两个节点中通常的左右指针,加上从键查找节点到索引查找节点的指针,加上每个节点中的父指针索引查找节点。您还需要在每个节点中都有一个指针来指向存储的值。

    操作:

    追加 - 追加操作会将键插入到两棵树中 - 一次在键查找树中,在由比较函数确定的位置,然后再次在索引查找树的最右边位置。插入红黑树是对数时间操作。

    按键查找 - 这是在键查找树上完成的,使用比较功能找到正确的位置 - O(log(n))

    按索引查找 - 这可以在索引查找字段上完成,如上所述 - O(log(n))

    从键中获取索引 - 首先在键查找树中查找键 O(log(n))。按照指向索引查找树的指针。跟随父指针直到根节点,(平衡树的 O(log(n)))。使用向上的“大小”字段来确定键的索引。 - O(log(n)) 总体。

    按索引删除 - 在索引查找树中查找项目。从索引查找树中删除。在键查找树中查找找到的键。从键查找树中删除。所有操作都是 O(log(n)) ,所以删除是 O(log(n)) 整体。

    按键删除 - 使用“从键获取索引”来获取键的索引。从索引查找树中按索引删除。从键查找树中按键删除。 O(log(n)) 总体。

    此结构还支持在任意位置插入 O(log(n)),而不仅仅是在末尾。

    存储开销显然是相当大的,但仍然是 O(n)。时间复杂度满足所有要求。

    很遗憾,我不知道这种结构的任何实现。

    更新:在我看来,您可以将树与哈希表结合起来进行 O(1) 按键查找。正如我上面建议的那样,不要使用两棵树,而是使用哈希表进行按键查找,使用平衡的顺序统计树进行位置查找,如上所述,但哈希表的槽包含指向的指针用于执行 get-list-position-by-key 查找的平衡树的节点。按键查找现在是 O(1),其他所有内容平均保持 O(ln(n))。当然,你现在偶尔会得到 O(n) 的重新哈希惩罚,就像任何哈希表一样。

    【讨论】:

    • 几年前我实际上做了一个 RB-tree 的实现,它在 O(log n) 时间内在 C++ 中按索引查找,但忘记了那个。当然,唯一的缺点是我们仍然需要两棵树,而不仅仅是一棵。但是你的建议确实符合我给出的要求。想知道是否还有更好的方法... ;)
    • 您可以使用散列和树来按键获得 O(1) 查找,而不是两棵树。其他一切都保持 O(ln(n))。我刚刚添加了一个更新。对于支持快速插入的有序结构,理论上是否可能比 O(ln(n)) 查找做得更好?
    【解决方案2】:

    OrderedDictionary 实际上满足您的要求。

    您对 OrderedDictionary 的分析不正确。根据this,基于键的查找实际上是 O(1),而索引是 O(1)。

    即使是简单的分析也可以通过键或索引为您提供 O(1) 查找。数组提供 O(1) 访问,而哈希表提供有效的 O(1) 访问。

    插入/删除稍微复杂一些,但考虑到摊销分析仍然是 O(1)

    文章声称插入和删除是 O(n)。这至少不适用于插入,因为摊销分析允许您简单地将插入给定元素的“成本”从 1 提高到 2。当插入需要调整数组大小的元素时,成本的后半部分用于支付复制的成本。最终插入将花费更长的时间,但它仍然是 O(1) 摊销的,并且只有在您导致不太可能调整数组大小时才会出现差异。

    【讨论】:

    • 是的,插入是摊销的 O(1),但最坏的情况是 O(n)。但是按键删除呢?
    • 实际上如果这个解决方案是基于数组的,最好的情况只会在插入到列表末尾时发生。当任意插入或删除时,当将元素移出(插入时)或移入(删除时)时,您将在列表的其余部分进行线性扫描。
    【解决方案3】:

    也许您会在The C5 Generic Collection Library for C#(从第 233 页开始)中找到一些有趣的东西

    【讨论】:

      【解决方案4】:

      你可以像链接一样使用Balanced binary search tree,只是为了在TreeNode的定义中你应该添加你的键,但问题是找到元素不是O(1),它是O(log(n)) index(其实 index 不是 TreeNode 的一部分,相对可以找到)但是所有的操作都是 O(log(n)) 并且是基于比较方法的最快已知方式。

      【讨论】:

      • 如何在 BST 中按索引获取项目?另外,BST 不是按键排序的,这意味着它不保留项目索引吗?
      • @DeCaf,你可以在一个 bst 中按 key 而不是 value 排序,在另一个 bst 中按 value 排序,所以当 key 调用时,你会在 O(log(n))当您想通过索引(值基数)找到它时,您可以在 O(log(n)) 中进行操作,两者的删除时间也相同。
      • 对于索引,当你说第 i 个元素时,表示第 i 个最大的元素,可以在 bst 的 O(log(n)) 中完成。
      • 也许我的表述有点含糊。对于索引,我实际上是指插入顺序,即与普通List 的工作方式相同。意思是附加 3、1、2,应该给出 3 索引 0、1 索引 1 和 2 索引 2。但出于好奇,您如何找到 BST 中的第 i:th 个最大元素?
      • @DeCaf,你可以在这里看到 O(log n) 摊销算法:stackoverflow.com/questions/2329171/…
      猜你喜欢
      • 2013-05-01
      • 1970-01-01
      • 2012-06-22
      • 2014-03-08
      • 2018-03-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-22
      相关资源
      最近更新 更多