是否有用于反转简单链表的O(nlog(n))算法?

时间:2009-07-21 09:28:24

标签: algorithm language-agnostic list big-o

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

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

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

但出于好奇 - 可能是一个用于反转简单链接列表的O(nlog(n))算法?具有恒定额外内存的算法是优选的。

6 个答案:

答案 0 :(得分:11)

我觉得你很困惑。你说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)。
  6. 总费用:O(n log(n))

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

    编辑2:我最初没有按随机顺序放置列表项,但意识到你可以说已经排序的列表上的有效排序小于O(n log(n)即使你正在逆转它。现在我并不完全相信这种情况,但上述修订版本消除了这种潜在的批评。

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

答案 1 :(得分:7)

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

答案 2 :(得分:1)

嗯...

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

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

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

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

答案 3 :(得分:1)

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

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

答案 4 :(得分:0)

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

但实际上机器有一个固定的字大小,所以通常不会考虑这一点。

答案 5 :(得分:0)

如果问题实际上是Ω(nlgn)算法,那么这个过于复杂的算法可能会这样做吗?

  • 从链表
  • 构建平衡树结构
  • 每个叶子包含链表中的原始值和链表中值的索引号;使用索引作为树键
  • 遍历树,以与原始索引相反的顺序报告所有叶子
  • 根据匹配值
  • 创建新的链接列表