链接列表插入运行时混淆

时间:2009-12-19 14:50:57

标签: algorithm language-agnostic big-o

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

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

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

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

7 个答案:

答案 0 :(得分:14)

如果您有指向要插入项目的节点的指针,则插入到链接列表是O(1)。 查找此节点可能是O(n),具体取决于您要执行的操作。

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

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

示例

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

答案 1 :(得分:3)

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

  1. 找到要将新节点附加到
  2. 的节点
  3. 通过更改节点指针实际附加节点
  4. 在链接列表中,第二个操作是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)操作。

答案 2 :(得分:1)

Per the Java LinkedList source code,Java通过LinkedList条目通过header为尾元素提供链接,从而实现header.previous尾部操作的O(1)。因此,如果您想要最后一个元素,则该类始终可以返回header.previous,从而允许持续时间。

我认为很多其他语言使用相同的基本策略。

答案 3 :(得分:1)

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

答案 4 :(得分:0)

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

Why is inserting in the middle of a linked list O(1)?的stackoverflow上有类似问题的一些有趣答案。这个问题的原始海报编辑了他的帖子,并指出他认为当说插入/删除是O(1)时他们正在讨论实际的插入操作,而不是找到插入的位置。这是有道理的,但我还没有看到我在此时发现的任何文章中正式陈述过。

答案 5 :(得分:0)

我认为你混淆的一个原因是你正在考虑好像有一个理想/规范的链表,它有或没有某些头/尾指针。实际情况是,通过遍历来自先前元素的指针来访问元素的任何线性(即,无分支)数据结构基本上是链表。是否保留指向第一个,最后一个,第k个等元素的指针完全取决于您。因此,如果您需要一个经常需要在第10个位置插入/删除元素的列表,您可以简单地实现一个具有指向第9个元素的额外指针的列表,并在O(1)时间内执行此操作。

另一件事是,当遍历链表的元素时,你可以在O(1)中的当前元素之后(并且就在它之前,如果它是双链表)插入一个新元素,因为你已经指向那个。

答案 6 :(得分:0)

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