【问题标题】:Linked List v.s. Binary Search Tree Insertion Time Complexity链表与二叉搜索树插入时间复杂度
【发布时间】:2021-12-02 09:28:34
【问题描述】:

链表

链表的插入时间复杂度对于实际操作来说是 O(1),但是需要 O(n) 时间才能遍历到正确的位置。大多数在线资源将链表的平均插入时间列为 O(1):

https://stackoverflow.com/a/17410009/10426919
https://www.bigocheatsheet.com/
https://www.geeksforgeeks.org/time-complexities-of-different-data-structures/

英国夏令时

二叉搜索树的插入需要遍历节点,耗时 O(log n)。

问题

Am I mistaken to believe that insertion in a BST also takes O(1) time for the actual operation?

类似于链表的节点,在 BST 中插入节点只会将当前节点的指针指向被插入节点,而被插入节点将指向当前节点的子节点。

If my thinking is correct, why do most online resources list the average insert time for a BST to be O(log n), as opposed to O(1) like for a linked list?

似乎对于链表,实际插入时间被列为插入时间复杂度,但对于BST,遍历时间被列为插入时间复杂度。

【问题讨论】:

  • 如果不保持 BST 平衡,那么找到正确的插入点需要 O(log n),添加节点需要 O(1)。但是如果你确实保持树平衡,找到正确的插入点需要 O(log n),添加节点需要 O(1),重新平衡树可能需要 O(log n)。如果您在插入后不重新平衡,那么最坏情况的搜索时间可能会降低到 O(n)。在这种情况下,BST 与链表完全相同。所以关键是当人们谈论 BST 时,他们通常会假设这棵树是一个平衡 BST。

标签: algorithm data-structures time-complexity complexity-theory space-complexity


【解决方案1】:

二叉搜索树是有序的,它通常是平衡的(以避免O(n)最坏情况的搜索时间),这意味着当你插入一个值时必须进行大量的改组以平衡树。这种重新平衡平均需要 O(log n) 操作,而链接列表只需要在您找到在节点之间插入项目的位置后更新固定数量的指针。

【讨论】:

    【解决方案2】:

    它反映了使用情况。对于您实际向他们请求的操作,它是 O(1) 和 O(log n)。

    使用 BST,您可能会让它自行管理,而无需了解实施细节。也就是说,您将发出tree.insert(value) 之类的命令或tree.contains(value) 之类的查询。这些东西需要 O(log n)。

    使用链表,您更有可能自己管理它,至少是定位。你不会发出像list.insert(value, index) 这样的命令,除非索引非常小或者你不关心性能。您更有可能发出insertAfter(node, newNode)insertBeginning(list, newNode) 之类的命令,它们只需要O(1) 时间。请注意,我从 Wikipedia 的 Linked list operations > Singly linked lists 部分获取了这两个部分,该部分甚至没有 用于在作为索引给出的特定位置插入的操作。因为实际上,您将使用使用链表的算法来管理“位置”(以节点的形式),而管理位置的时间则归于该算法。顺便说一句,也可以为 O(1),例如:

    • 您正在从一个数组构建一个链表。您将通过保留一个引用最后一个节点的变量来做到这一点。要附加下一个值/节点,请将其插入到最后一个节点之后(实际上是 O(1) 操作),然后更新您的变量以引用新的最后一个节点(也是 O(1))。
    • 想象一下,您找不到使用线性扫描但使用哈希映射的位置,直接存储对链表节点的引用。然后查找引用需要 O(1) 并在查找到的节点之后插入也只需要 O(1) 时间。

    如果您向我们展示了其中的一些“大多数在线资源将链表的平均插入时间列为 O(1)”,我们可能会看到它们确实在展示像insertAfterNode 这样的插入操作,而不是insertAtIndex 现在你在问题中包含了一些链接:我对那些关于链接列表的 O(1) 插入的来源的想法: first one 确实指出,只有当您已经拥有“位置迭代器”之类的东西时,它才是 O(1)。 second one 反过来指的是我上面显示的同一个 Wikipedia 部分,即在给定节点之后或列表开头插入。 third one 是我所知道的关于编程的最糟糕的网站,所以我并不惊讶他们只是说 O(1) 而没有任何进一步的信息。

    换一种说法,因为我喜欢现实世界的类比:如果你问我更换汽车发动机中的 X 部件需要多少钱,我可能会说 200 美元,尽管该部件只需要 5 美元。因为我自己不会那样做。我会让机械师来做,我必须为他们的工作付费。但如果你问我更换自行车铃铛要多少钱,我可能会说 5 美元,而铃铛要 5 美元。因为我会自己替换。

    【讨论】:

    • 您为我明确的主要区别是,即使实际上需要 O(n) 时间来遍历链表以插入某个位置,但这通常不是链表没有使用也没有实现。这就是插入被列为 O(1) 的原因,因为正常用法是在链表的当前节点处插入。虽然我列出的链接提到遍历需要 O(n),但他们没有提到这不是链表的通常实现和用法。
    • @Brendan 是的,这一点在那里被忽视了,这就是为什么我认为这是一个好问题。我猜那些对这些东西足够熟悉可以写关于它们的人通常对它们有点甚至不再想知道这些东西了:-)
    【解决方案3】:

    要插入到链表中,你只需要维护链表的结束节点(假设你在最后插入)。

    要插入二叉搜索树 (BST) 并在插入后维护 BST,您无法在 O(1) 中执行此操作 - 因为树可能会重新平衡。这个操作不像插入链表那么简单

    查看一些示例here

    【讨论】:

      【解决方案4】:

      链表的插入时间实际上取决于您插入的位置和链表的类型。

      例如考虑以下情况:

      1. 您正在使用单个链表,并且要在末尾/中间插入,您将需要 O(n) 的运行时间来遍历列表直到末尾节点或中间节点。
      2. 您正在使用双链表(两个指针第一个指针指向头元素,第二个指针指向最后一个元素)并且您要在最后插入,这一次您仍然会有 O(n) 时间复杂度,因为您需要使用第一个或第二个指针遍历到列表的中间。
      3. 您正在使用单链表,并且您将在列表的第一个位置插入,这一次您将有 O(1) 的复杂性,因为您根本不需要遍历任何节点。双链表也是如此,在链表末尾插入位置。

      因此您可以看到在最坏的情况下,链接列表将采用 O(n) 而不是 O(1)。

      现在,在 BST 的情况下,如果您的 BST 平衡且不偏斜,您可以得出 O(log n) 时间。如果你的 TREE 是倾斜的(每个元素都大于前一个元素),这次你需要遍历所有节点来找到插入位置。例如假设你的树是1->2->4->6,你要插入节点9,所以你需要访问所有的节点来找到插入位置。

      1
       \
        2
         \
          4
           \
            6 (last position after which new node going to insert)
             \
              9 (new insertion position for the new node)
      

      因此,您可以看到您需要访问所有节点才能找到合适的位置,如果您有 n 个节点,则运行时间复杂度为 O(n+1) => O(n)。

      但是如果你的 BST 是平衡的并且没有偏差,那么情况就会发生巨大的变化,因为你的每一步都可以消除不符合条件的节点。

      PS:我的意思是not comes under the condition,你可以把它当作家庭作业!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多