【问题标题】:Why is inserting in the middle of a linked list O(1)?为什么在链表中间插入 O(1)?
【发布时间】:2010-10-24 20:21:53
【问题描述】:

根据Wikipedia article on linked lists,在链表中间插入被认为是O(1)。我认为这将是O(n)。您不需要找到可能在列表末尾附近的节点吗?

这个分析是否没有说明节点操作的发现(尽管它是必需的)而只是插入本身?

编辑

与数组相比,链表有几个优点。在列表的特定点插入元素是一个常数时间操作,而在数组中插入可能需要移动一半或更多元素。

以上陈述对我来说有点误导。如果我错了,请纠正我,但我认为结论应该是:

数组:

  • 找到插入/删除点 O(1)
  • 执行插入/删除 O(n)

链接列表:

  • 找到插入/删除点 O(n)
  • 执行插入/删除 O(1)

我认为唯一不必找到位置的情况是,如果你保留某种指向它的指针(在某些情况下,就像头部和尾部一样)。所以我们不能直截了当地说链表在插入/删除选项方面总是优于数组。

【问题讨论】:

    标签: linked-list big-o


    【解决方案1】:

    您是对的,文章将“索引”视为单独的操作。所以插入本身是 O(1),但到达中间节点是 O(n)。

    【讨论】:

    • 在同一位置插入超过 1 个对象时会产生更大的差异...
    • @Anony-Mousse 你能再解释一下吗?即插入多个对象时只需要找到一次插入位置?
    • 现有列表的大小为 O(n),而不是您计划在那里进行的插入次数。
    【解决方案2】:

    插入本身是 O(1)。节点查找是 O(n)。

    【讨论】:

      【解决方案3】:

      不,当您决定要插入时,假定您已经在迭代列表中。

      链接列表上的操作通常以这样一种方式完成,即它们并不真正被视为通用“列表”,而是作为节点的集合——将节点本身视为主循环的迭代器。因此,当您浏览列表时,您会注意到作为业务逻辑的一部分,需要添加一个新节点(或删除一个旧节点)并且您这样做了。您可以在一次迭代中添加 50 个节点,每个节点只需 O(1) 时间即可断开两个相邻节点的链接并插入新节点。

      【讨论】:

        【解决方案4】:

        为了与图表显示的数组进行比较,它是 O(1),因为您不必将所有项目移动到新节点之后。

        所以是的,他们假设您已经拥有指向该节点的指针,或者获取指针是微不足道的。换句话说,问题是这样的:“给定 X 处的节点,在这个节点之后插入的代码是什么?”您可以从插入点开始。

        【讨论】:

          【解决方案5】:

          插入链表不同于遍历链表。您不是在定位项目,而是在重置指针以将项目放在那里。无论是在前端附近还是接近尾端插入都没有关系,插入仍然涉及重新分配指针。当然,这取决于它是如何实现的,但这就是列表的优势——您可以轻松插入。通过索引访问是数组的亮点。然而,对于一个列表,通常需要 O(n) 才能找到第 n 个项目。至少那是我在学校时记得的。

          【讨论】:

            【解决方案6】:

            因为它不涉及任何循环。

            插入是这样的:

            • 插入元素
            • 链接到上一个
            • 链接到下一个
            • 完成

            无论如何,这都是恒定的时间。

            因此,一个接一个地插入 n 个元素是 O(n)。

            【讨论】:

              【解决方案7】:

              一旦你知道你要把它放在哪里,插入是 O(1)。

              【讨论】:

                【解决方案8】:

                这个分析是否没有说明节点操作的发现(尽管它是必需的)而只是插入本身?

                你明白了。在给定点插入假定您已经持有指向要在其后插入的项目的指针:

                InsertItem(item * newItem, item * afterItem)
                

                【讨论】:

                  【解决方案9】:

                  不,它不考虑搜索。但是,如果您已经持有指向列表中间项的指针,则在该点插入是 O(1)。

                  如果一定要搜索,就得加上搜索时间,应该是O(n)。

                  【讨论】:

                    【解决方案10】:

                    这篇文章是关于比较数组和列表的。查找数组和列表的插入位置都是O(N),本文忽略。

                    【讨论】:

                    • 不会找到数组的插入点是 O(1) 吗?由于数组存储在连续的内存中,它所要做的就是添加偏移量。
                    • @vg1890 - 你必须先找到偏移量。
                    【解决方案11】:

                    O(1) 取决于您有一个项目,您将在其中插入新项目。 (之前或之后)。如果你不这样做,那就是 O(n),因为你必须找到那个项目。

                    【讨论】:

                      【解决方案12】:

                      我认为这只是您选择计入 O() 表示法的一个例子。在插入正常操作的情况下进行计数是复制操作。对于数组,在中间插入涉及将位置上方的所有内容复制到内存中。使用链表,这变成了设置两个指针。无论插入什么,您都需要找到位置。

                      【讨论】:

                        【解决方案13】:

                        如果您有在操作后插入的节点的引用对于链表来说是 O(1)。
                        对于数组,它仍然是 O(n),因为您必须移动所有后续节点。

                        【讨论】:

                          【解决方案14】:

                          最常见的情况可能是在列表的开头或结尾插入(而列表的结尾可能很快就可以找到)。

                          相比之下,在数组的开头或结尾插入项目(如果数组位于末尾则需要调整数组大小,如果数组位于开头则需要调整大小并移动所有元素)。

                          【讨论】:

                          • 如果在末尾保留一个空元素的缓冲区,则可以使将项目插入数组末尾的时间为 O(1),尽管有时插入仍然是 O(1)。大多数收藏都是这样做的。也可以通过将索引运算符更改为返回元素编号 (n+x) % len 来使数组开头的惰性项为 O(1),其中 x 是您在开头插入项的次数的名单。双端队列有时是这样实现的(但有时也用双向链表实现。
                          猜你喜欢
                          • 2011-01-10
                          • 2012-12-12
                          • 2018-02-08
                          • 2015-09-28
                          • 1970-01-01
                          • 2021-09-09
                          • 1970-01-01
                          • 1970-01-01
                          相关资源
                          最近更新 更多