【问题标题】:Linked List insertion running time confusion链表插入运行时间混乱
【发布时间】:2010-12-28 07:59:01
【问题描述】:

我已尝试确认链接列表插入的运行时间,似乎有两个不同的答案。

对于在链接列表的末尾插入一个元素,我认为它需要 O(n),因为它必须遍历到列表的末尾才能访问尾部。但是我看到的一些答案说 O(1)?他们是否假设所有链表都实现了指向尾部的指针?如果是这样,这是一个可以接受的假设吗?

其次,有些地方还建议在链表中间插入一个元素是 O(1),由于与遍历到列表中间以插入它的相同原因,我对此感到困惑。

有人可以澄清一下吗?谢谢。

【问题讨论】:

  • 如果在列表中间插入 O(1),我们就不再需要数组了 :)。
  • Li0liQ:链表的优点之一是可以在中间固定时间插入项目,而在数组中则需要移动以下所有项目。
  • @Li0liQ:常数时间索引呢?

标签: algorithm language-agnostic big-o


【解决方案1】:

如果您有一个指向要插入项目的节点的指针,则插入链表是 O(1)。 找到这个节点可能是 O(n) 取决于你想要做什么。

如果你保留一个指向列表尾部的指针,那么你不需要寻找它,然后插入是 O(1)。

不,不是所有的链表实现都有指向链表末尾的指针。

示例

假设您有一个空列表,您可以在其中添加一个节点x。然后将n 节点添加到x 之前和之后的列表中。您仍然可以在x 之后插入单个节点,只需更新其next 指针(和新节点的),无论列表中有多少节点。

【讨论】:

    【解决方案2】:

    链表的修改涉及两个操作:

    1. 定位要追加新节点的节点
    2. 实际上是通过改变节点指针来追加节点

    在链表中,第二个操作是O(1) 操作,所以这是第一个操作的成本问题。

    当附加到最后一个节点时,链表的幼稚实现将导致O(n) 迭代时间。但是,好的链表库会考虑最常见的用途,以及访问最后一个节点的特殊情况。这种优化将导致O(1) 检索最后一个元素,从而导致整个O(1) 插入时间结束。

    至于中间,你的分析是正确的,定位节点也需要O(n)。但是,一些库公开了一个方法,该方法将指针指向应该插入新节点的位置而不是索引(例如C++list)。这消除了线性成本,导致所有O(1)

    虽然插入到中间通常被认为是O(n) 操作,但在某些情况下可以优化为O(1)。这与数组列表相反,其中插入操作本身(第二个操作)是O(n),因为更高位置的所有元素都需要重新定位。此操作无法优化。

    用于插入 一个简单的链表实现会导致O(n) 插入时间。但是,优秀的链表库编写者会针对常见情况进行优化,因此他们会保留对最后一个元素的引用(或具有循环链表实现),从而导致 O(1) 插入时间。

    至于插入到中间。像C++ 这样的一些库有一个建议的插入位置。他们将获取一个指向列表节点的指针,新节点将附加到该节点。这样的插入将花费O(1)。我认为您无法通过索引号实现O(1)

    这与数组列表相对,其中插入到中间会强制重新排序所有高于它的元素,因此它必须是 O(n) 操作。

    【讨论】:

      【解决方案3】:

      如果你不改变你的(简单)链表的节点,你需要 O(n) 时间来插入链表中的任意位置(因为你需要从链表的开头复制所有节点到新元素的位置。如果您已经有一个指向要插入项目的节点的指针,则对于可变列表是 O(1),如果必须搜索它,则为 O(n)。 在这两种情况下,您只需要 O(1) 时间即可在列表的开头插入一个元素。如果你经常需要在列表中间插入一个元素(O(n) 的情况),你应该使用另一种数据结构。

      【讨论】:

        【解决方案4】:

        Per the Java LinkedList source code,Java 通过header 条目通过header.previous 提供指向尾部元素的链接来实现LinkedList 尾部操作的O(1)。所以如果你想要最后一个元素,类总是可以返回header.previous,允许恒定的时间。

        我假设许多其他语言使用相同的基本策略。

        【讨论】:

          【解决方案5】:

          显然,您可能看过维基百科条目http://en.wikipedia.org/wiki/Linked_list。我看到他们指定从列表末尾和中间插入/删除都具有 O(1) 性能但未能详细说明他们如何确定这一点的表格。

          Why is inserting in the middle of a linked list O(1)? 的 stackoverflow 上有类似问题的一些有趣答案。该问题的原始发布者编辑了他的帖子,并指出他相信当据说插入/删除是 O(1) 时,他们谈论的是实际的插入操作,而不是寻找插入位置。这是有道理的,但我目前还没有在我找到的任何文章中看到正式声明。

          【讨论】:

            【解决方案6】:

            我认为您感到困惑的一个原因是您认为好像存在一个理想/规范的链表,该链表具有或不具有某些头/尾指针。现实情况是,任何通过前一个元素的指针访问元素的线性(即无分支)数据结构基本上都是一个链表。是否保留指向第一个、最后一个、第 k 个等元素的指针完全取决于您。因此,如果您需要一个经常需要在第 10 个位置插入/删除元素的列表,您可以简单地实现一个具有指向第 9 个元素的额外指针的列表,并在 O(1) 时间内完成。

            另一件事是,当迭代链表的元素时,您可以在 O(1) 中的当前元素之后(如果是双链表,则在它之前)插入一个新元素,因为您已经有一个指向那个的指针。

            【讨论】:

              【解决方案7】:

              正如@Kaleb Brasee 指出的那样,在Java 中插入尾部是O(1),因为Java 使用双向链表作为其LinkedList 实现。我认为这是很多 SDK 实现的一个相当普遍的选择。例如,STL list 实现是双向链接的 (source)。

              【讨论】:

                猜你喜欢
                • 2010-12-28
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2019-08-05
                • 1970-01-01
                相关资源
                最近更新 更多