【问题标题】:Is there a O(nlog(n)) algorithm for inverting a simply linked list?是否有用于反转简单链表的 O(nlog(n)) 算法?
【发布时间】:2009-07-21 09:28:23
【问题描述】:

this answer 的 cmets 中提出了一个想法,即反转简单链表只能在 O(nlog(n)) 时间内完成,而不是 O(n) 时间。

这绝对是错误的——O(n) 反转不是问题——只需遍历列表并随时更改指针。需要三个临时指针 - 这是恒定的额外内存。

我完全理解 O(nlog(n)) 比 O(n) 更差(慢)。

但出于好奇 - 用于反转简单链表的 O(nlog(n)) 算法可能是什么?具有恒定额外内存的算法更可取。

【问题讨论】:

  • 按 O(n) 执行,但在 ith 节点上执行 log(i) nops。
  • 作为一个快速迂腐的吹毛求疵:您的 O(n) 算法 一种 O(n logN) 算法,因此您的问题的一个答案是未修改的 O(n) 算法。 O(n log n) 是一组算法,其增长速度不超过 n log n,包括 O(n) 甚至 O(1)。要限制在两个方向上相同的渐近边界,您需要说ϴ(n)。 (实际上,大多数人说O(n)时的意思是ϴ(n)

标签: algorithm language-agnostic list big-o


【解决方案1】:

我认为你很困惑。你说的是 O(n log(n)),实际上比 O(n) 更糟糕。你的意思是 O(log n) 吗?如果是这样,答案是否定的。您不能在 O(log n) 中反转链表。 O(n) 是微不足道的(也是显而易见的解决方案)。 O(n log(n)) 没有多大意义。

编辑:好的,你的意思是 O(n log(n))。那么答案是肯定的。如何?简单的。您对列表进行排序:

  1. 计算列表的长度。成本:O(n);
  2. 创建一个相同大小的数组;
  3. 将链表的元素以随机顺序复制到数组中,将原始顺序作为元素的一部分。例如:[A,B,C] -> [(B,2),(C,3),(A,1)]。成本:O(n);
  4. 使用高效排序(例如快速排序)以相反的原始顺序对数组进行排序,例如 [(C,3),(B,2),(A,1)]。成本:O(n log(n));
  5. 从反向数组创建一个链表。成本:O(n)。

总成本:O(n log(n))

尽管有所有中间步骤,但排序是最昂贵的操作。 O(n) 其他步骤是恒定的(意味着步骤数不是 n 的因素)所以总成本是 O(n log(n))。

编辑 2: 我最初并没有将列表项按随机顺序排列,但我意识到您可能会争辩说,对已排序列表的有效排序小于 O(n log(n) ) 即使您正在反转它。现在我并不完全相信情况确实如此,但上述修订消除了潜在的批评。

是的,这是一个病态的问题(和答案)。当然你可以在 O(n) 内完成。

【讨论】:

  • 不,我要求的是更糟糕的算法。
  • 他知道情况更糟,这就是问题的全部意义所在。他只是想知道是否有一种算法可以进行 O(n log n) 但乍一看并不愚蠢。
【解决方案2】:

每个O(n)算法也是O(n log n),所以答案是肯定的。

【讨论】:

  • 我想我们都认为 OP 的意思是“O(n log n) 但不是 O(n)”。无论如何,既然这是一个病态问题,而且你是对的,+1。
  • @balpha: 病态的问题吸引了病态的挑剔者:)
  • +1。这是问题的正确答案,但我相信 OP 意味着 Big-Theta(n log n) 而不是 Big-Oh。
  • 我相信 OP 实际上是指 Ω(nlgn) 而不是 Θ(nlgn)... ;-)
【解决方案3】:

愚蠢的想法,但是 O(n log n) 而不是 O(n)

  • 为列表的每个项目分配一个唯一的 ID。每个后继者的id要大于item的id(O(n))
  • 使用任何基于比较的排序算法,使用 id 作为键,按升序对项目进行排序 (O(n log n))
  • 使用排序项目给出的顺序构建一个新列表(O(n))

【讨论】:

    【解决方案4】:

    嗯……

    您可以使用接受链表并将其反转的递归,通过使用链表的两半调用自身,如果输入只是两个节点,它将反转它们。

    这是非常低效的,但我相信它是 O(nlog(n))...

    在伪代码中类似于以下内容(假设您有一个 len 函数返回 O(n) 中的列表长度和一个 sub_list(list, start_id, end_id) 函数返回从 start_id 开始到 end_id 结束的列表子集在 O(N)) 中:

    function invert(list)
    
        if len(list) == 1 return list
    
        if len(list) == 2:
            new_list = list.next
            new_list.next = list
            return new_list
    
        middle_of_list = len(list) / 2  <-- integer division
    
        first_half = invert ( sub_list(list, 1, middle_of_list) )
        second_half = invert ( sub_list(list, middle_of_list+2, len(list) )
    
        first_half.next = second_half
    
        return first_half
    

    【讨论】:

      【解决方案5】:

      如果你是迂腐的,那么这个算法是 O(n log n),因为指针的大小至少为 log n,并且必须被分配 n 次。

      但实际上机器的字长是固定的,因此通常不会考虑到这一点。

      【讨论】:

        【解决方案6】:

        如果问题实际上是针对 Ω(nlgn) 算法,也许这种过于复杂的算法可以解决问题?

        • 从链表构建平衡树结构
        • 每个叶子都包含来自链表的原始值和链表中值的索引号;使用索引作为树键
        • 遍历树,将所有叶子逆序上报给原始索引
        • 根据匹配值创建一个新的链表

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-06-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-11-19
          • 1970-01-01
          相关资源
          最近更新 更多