【问题标题】:Is there a known implementation of an indexed linked list?是否有已知的索引链表实现?
【发布时间】:2010-12-15 08:07:43
【问题描述】:

我的直觉告诉我没有什么好的方法可以实现这一点,但是,与 Stephen Colbert 先生不同,我更愿意相信开发人员社区而不是我的直觉......

有没有一种已知的方法可以有效地实现“两全其美”的列表,一种通过索引提供随机访问和 O(1) 插入/删除(如链表)?

我预见到两种可能的结果:“不,这是不可能的,原因如下……”或“嗯,是的,这已经完成;请看这里、这里和这里。”

【问题讨论】:

    标签: language-agnostic list indexing linked-list


    【解决方案1】:

    我不相信插入和查找都可以获得O(1)。添加数组(甚至是花哨的可拆分向量)的那一刻,插入变为O(n)。

    根据列表的预期行为,有多种方法可以减轻损害。如果查找比插入/删除更多,最好只使用向量(可变大小的数组) - 这些是相当有效的,不太像数组,但比遍历列表更好(因为这些往往是列表数组,它在技术上仍然是遍历一个列表,但列表中的每个元素通常都有自己的大小,这使得它更有效)。

    如果插入和删除更频繁,您可以使索引构建一个惰性索引,以便仅在需要时完成。例如,插入和删除只会改变链表部分(并将索引标记为脏)——只有当有人尝试使用索引时,它才会被重建并标记为干净。

    您甚至可以通过记录第一个脏条目来优化重建。这意味着如果您只在列表的后半部分插入或删除,当有人要使用它时,您不需要重建整个索引。

    我曾经实施的一个解决方案是 2D 列表。我的意思是:

            +-----------+    +-----------+    +-----------+
    List -> | count = 7 | -> | Count = 4 | -> | Count = 6 | -> null
            +-----------+    +-----------+    +-----------+
                  |                |                |
                  V                V                V
            +-----------+    +-----------+    +-----------+
            |    [0]    |    |    [7]    |    |   [11]    |
            +-----------+    +-----------+    +-----------+
                  |                |                |
                  V                V                V
            +-----------+    +-----------+    +-----------+
            |    [1]    |    |    [8]    |    |   [12]    |
            +-----------+    +-----------+    +-----------+
                  |                |                |
                  :                :                :
                  :                :                :
                  |                |                |
                  V                V                V
            +-----------+    +-----------+    +-----------+
            |    [6]    |    |   [10]    |    |   [16]    |
            +-----------+    +-----------+    +-----------+
                  |                |                |
                  V                V                V
                 null             null             null
    

    虽然这使得插入和查找都为 O(n),但平衡是正确的。在纯数组解决方案中,查找为O(1),插入为O(n)。对于纯链表,插入是O(1)(一旦你找到了插入点,当然,操作本身就是O(n)),查找是O(n)。

    两者的二维列表都是O(n),但系数较低。如果您要插入,只需检查每列的第一行即可找到正确的列。然后你遍历列本身寻找正确的行。然后插入该项目并增加该列的计数。删除也是类似的,但在这种情况下,计数会减少,当计数为零时,整个列都会被删除。

    对于索引查找,您遍历列以找到正确的列,然后遍历列中的项以获取正确的项。

    而且,它甚至可以通过尝试保持最大高度和宽度大致相同来自动调整。

    【讨论】:

    • 一个包含很多好主意的综合答案:谢谢,我真的很感激。
    • 有已知的受限列表实现(例如,使用这三个允许的操作:追加、按 id 删除、从 id 获取索引),前提是预先知道追加的调用次数。这些数据结构保证了其所有操作的恒定或对数复杂性,但它们仅对一小部分问题有用。
    • 如果 2D 列表是正方形的(2 维的大小是相等的),那不是 O(n^1/2) 用于查找和插入吗?您循环遍历第一维中的最大 n^1/2 元素,然后再循环第二维中的最大 n^1/2。如果列表不是方形的,那么最坏的情况只是一个很长的列表,这肯定是 O(n),这是对的。还有一些简单的技巧可以保持平衡。例如。每次列表与顶部列表相比具有一定长度时,将其拆分。并且在删除时,您可以轻松地将两个相邻的列表在它们变得太短时合并。
    • @sanderd17:是的,如果你保持平衡,复杂性会随着你的陈述而改变。
    【解决方案2】:

    如果你认为O(log N) == O(1),
    签出:

    【讨论】:

    • @paxdiablo,我按答案编辑。插入/删除时为 O(logn)。
    【解决方案3】:

    当我在课堂上实现链表时,我考虑过通过存储 3 个附加字段来优化访问时间:链表中间的节点、最近访问的节点的索引和最近访问的节点本身。

    要按索引检索节点,我将首先查看到达给定索引处节点的所有可用路径,然后选择最便宜的方式来完成它。方法很简单:

    1. 从开始到所需的索引
    2. 从最近访问的节点转到所需的索引(转发)
    3. 从最近访问的节点转到所需的索引(向后)
    4. 从中间节点到所需索引(转发)
    5. 从中间节点到所需索引(向后)
    6. 从节点末尾到所需索引

    所需索引和起始索引之间差异最小的路径将是最便宜的选择。如果还没有访问过节点,则可以将最近访问过的节点设置为中间节点。当然,偶数个元素没有实际的中间,所以我只选择 n/2 的底数。

    无论如何,我从来没有真正实现过这种优化,甚至真正分析过它,但我希望我能提供帮助。

    【讨论】:

    • 这样做的问题是每次插入新节点都得更新中点,不遍历列表就无法计算两个节点的相对偏移量。
    • 关于中间节点的说法是对的,但是我们可以计算相对偏移量,因为我们实际上已经拥有了我们需要的所有索引。我们有我们想要访问的节点的索引(传递给函数),我们有所有的起始索引。 0 表示链表的开始,n-1 表示链表的结束,n/2 表示中间,最近访问的节点的索引存储在链表中。相对偏移应该是索引的差异。至少我是这么认为的,但我当然可能是错的。
    • @Marc - 对。不思考。这仍然比 O(1) 索引要多,但会更好。尽管我们总是必须为每次插入和删除更新索引(和中点)(如果我们删除中点/最近索引的节点怎么办?我们必须找到另一个节点来替换它),这可能会变得昂贵。
    • 可以在插入或删除时更新中间节点,方法是从当前中间节点向后或向前移动,直到我们再次达到 n/2,最多为 1 步,因为大小插入或删除时列表的值大约为 -1 或 +1。如果我们删除最近索引的节点,则可以将“新”最近索引的节点设置为中间节点。找到中间节点应该总是很容易。但是,通过所有这些额外的步骤,这可能成为对具有大量插入的列表进行不必要的优化,但它可以改善经常访问的列表。
    • 我认为您所描述的是迈向跳过列表的第一步。
    【解决方案4】:

    你的直觉是正确的。

    链接列表是 O(1) 的插入/删除,因为您执行插入或删除某些内容的操作只是切换几个指针(一个或两个在您插入的对象上,一个或两个在一个或两个其他对象)。显然,这不会因列表的大小而改变。

    跳过列表将为您提供 O(logn) 查找,但由于您要维护索引,它也意味着 O(logn) 插入/删除,因为该索引需要保持最新。

    您用于查找的任何并行数据结构都需要维护,因此您的复杂性将随该索引的复杂性而扩展。

    您有什么特别想解决的问题吗?

    例如,如果你能保证一个完美的散列,你可以获得 O(n) 的插入、删除和查找。但您需要提前了解有关您的数据的一些信息才能使其发挥作用。

    【讨论】:

      【解决方案5】:

      哈希表怎么样?您通过密钥和 O(1) 插入/删除获得 O(1) 随机访问。问题是条目是无序的。

      如需有效实现有序序列,请查看finger trees。它们使您可以 O(1) 访问 head 和 last 以及 O(log n) 随机访问内部节点。在 O(1) 的任一端插入或删除。值得注意的是,手指树的反转需要恒定的时间。

      【讨论】:

        【解决方案6】:

        我不知道插入时的确切 BigO(因为这会根据样本大小和增长而有所不同),但我会立即想到 Java 的 java.util.LinkedList。

        http://java.sun.com/j2se/1.5.0/docs/api/java/util/LinkedList.html

        编辑:是的,显然它下面仍然是一个真正的链表,因此索引获取可能是 O(n/2),这当然是正式的 O(n)。

        您总是会浪费一大堆空间并实现一个 List 实现,该实现保留一个并行链表和数组,并延迟插入/删除。

        【讨论】:

        • 爆炸!看起来该类仅通过迭代提供索引,因此随机访问将比插入/删除慢:“索引到列表中的操作将从开头或结尾遍历列表,以更接近指定索引的为准”(来自@ 987654322@).
        • 从该页面:“索引到列表中的操作将遍历列表”。我收集到 OP 也在寻找 O(1) 索引查找。
        【解决方案7】:

        虽然我认为您无法获得整数索引,但如果您使用“引用”类型,则支持哈希表可能会起作用。

        【讨论】:

          【解决方案8】:

          Java 的 LinkedList 对索引获取具有 O(n) 访问权限。 LinkedList 扩展 AbstractSequentialList 以表明它不提供 O(1) 索引获取。

          我建议看看Apache's TreeList。它提供 O(log n) 次插入/删除和 O(1) 次索引查找。

          【讨论】:

          • 什么是 O(n)“索引”查找。不应该只是 O(log n) 查找吗?
          • 集合通常允许您根据键 get(Object) 和/或索引 get(index) 查找值。当提到索引获取时,我说的是后者,你想要列表中的第 n 个项目。
          • 我的意思是 TreeList 不提供 O(n) 获取。那没有任何意义。
          猜你喜欢
          • 2020-11-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-09-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多