【问题标题】:Sort a single linked list对单个链表进行排序
【发布时间】:2010-07-23 06:56:45
【问题描述】:

您将如何对单个链表进行排序。 (这里的问题是单个属性+使用LinkedList进行排序比数组更难) 我想看一个伪代码..

尽量让它在时间和空间上都高效。

谢谢!

【问题讨论】:

  • 家庭作业?如果是,请说明您到目前为止已经走了多远。
  • Sorting a linked list 的可能重复项

标签: algorithm sorting


【解决方案1】:

合并排序只涉及几个简单的步骤:

  1. 检查列表是否为空或只有一个元素。如果是,则原样返回列表。

  2. 将列表分成两半。合并两个部分。

  3. 通过重复从两个列表中取出较小的元素来合并列表。由于零件列表已经排序,因此只需查看两个列表中的第一个元素并选择较小的元素即可。

  4. 返回合并列表。

结果你会得到一个按最小元素排序的链表。

Haskell 中的代码示例:

import Data.List(splitAt)

--Mergesorts a list by smallest element first.
sort :: Ord a => [a] -> [a]
sort = sortBy (<)

--Generic sort that can sort in any order.
sortBy :: (a -> a -> Bool) -> [a] -> [a]
sortBy _ [] = []
sortBy _ [x] = [x]
sortBy f xs = merge f (sortBy f first) (sortBy f second)
    where
        (first,second) = split xs

--Splits a list in half.
split :: [a] -> ([a],[a])
split xs = splitAt (halfLength xs) xs

--Returns a lists length divided by two as a whole number (rounded).
halfLength :: [a] -> Int
halfLength = round . (/2) . fromIntegral . length

--Merges two lists in the provided order.
merge :: (a -> a -> Bool) -> [a] -> [a] -> [a]
merge _ [] [] = []
merge _ [] (y:ys) = y:ys
merge _ (x:xs) [] = x:xs
merge f (x:xs) (y:ys) =
    if f x y
        then x : merge f xs (y:ys)
        else y : merge f (x:xs) ys

【讨论】:

  • 我相信这种合并排序的实现将使用更多的空间。让我们看一个 64 术语列表。如果我将列表拆分为 2 个子列表,我必须记住他们的头部的位置在哪里。首先我分裂一次,并记住第 1 和第 33 个指标。然后我再次拆分前半部分,并且必须记住第 1、第 17 和第 33 个索引。随着术语数量的增加,我需要记住的头节点的数量也会增加。我解释了如何进行就地合并排序here。
【解决方案2】:

我一直在考虑这个问题,我认为您可以通过合并排序实现 O(n log(n)) 时间和 O(1) 空间条件。

让我们看看...
拿清单:
3 -> 2 -> 1 -> 5 -> 6 -> 4

第一关:
为第 1 项、第 2 项和第 3 项设置指针
将第一个和第二个指针之间的较小项设置为指向较大的项。
将 2 个术语中的最后一个设置为指向原始列表的其余部分。
重复直到列表的末尾。
2 -> 3 -> 1 -> 5 -> 4 -> 6

此时,每对术语都是有序的。

第N遍:
设置指向第 (2^(N-1))th 和 (2^(N))+1 项的指针
取值较小的节点并增加其指针。
重复该过程,直到两个列表(长度为 2^N)都用完,每次将较小值的节点附加到前一个较小值的节点。
将指针设置为原始列表的其余部分。
重复直到列表的末尾。

第 0 遍:3 -> 2 -> 1 -> 5 -> 6 -> 4(1 个术语的每个块都是有序的)(微不足道的)
第一遍:2 -> 3 -> 1 -> 5 -> 4 -> 6(每个包含 2 个术语的块都是有序的)
第二遍:1 -> 2 -> 3 -> 5 -> 4 -> 6(每块 4 个词都是有序的)
第三遍:1 -> 2 -> 3 -> 4 -> 5 -> 6(每个 8 个词的块都是有序的)

时间:log(n) 次通过,每次通过 n 次比较。
O(n log(n))

空格:指针和整数变量
O(1)

【讨论】:

    【解决方案3】:

    由于链表只是由其他项目指向的一些项目,您可以构造一个具有 O(n) 时间和 O(n) 空间的指针数组,使用任何优秀的排序算法对它进行排序O(n log n) 时间和 O(1) 空间,然后用 O(n) 时间和 O(1) 空间从头开始重构链表。

    总的来说,这是 O(n log n) 时间和 O(n) 空间。

    【讨论】:

    • 听起来不错,是否可以使用 O(1) 空间和 O(nlogn) 时间对其进行排序?
    • 不,据我所知。您无法访问 O(1) 中的任意列表节点这一事实使得这种情况不太可能发生。事实上,正是 O(n) 空间允许 O(1) 个人访问时间,而这反过来又允许 O(n log n) 总排序时间。
    【解决方案4】:

    我相信您可以使用就地快速排序来做到这一点。
    我错了。这不适用于单链表,因为 in 需要能够在列表中后退。

    所以真正的问题变成了如何进行就地快速排序。

    我们开始...

    1) 获取链表的第一项、第二项和最后一项的指针。
    2) 将第二个指针逐个遍历列表,直到找到大于第一个项的项。
    3) 将第三个指针在列表中向后移动,直到找到一个小于第一个项的项。 此步骤不适用于单链表。
    4) 交换第二个和第三个指针的值。
    5) 重复步骤 2) 到 5),直到第二个和第三个指针相等。
    6) 在第二个指针之后插入第一项

    -此时链表分为:
    [小于 x 的项] x [大于 x 的项]

    7) 对 [terms less than x] 和 [terms greater than x] 重复步骤 2) 到 7),直到 [terms ________ than x] 块的大小为 1。

    空格:每层 3 个指针。
    O(log(n))

    时间:与快速排序相同
    一般 O(n log(n))
    O(n * n) 在最坏的情况下(如果列表已经排序或倒序)


    为格式化和愚蠢而编辑

    【讨论】:

    • 我喜欢阅读这篇文章,并且我认为该算法经过简短的查看后是合理的。我相信它不是 O(1) 空间。我认为这是 O(log n) 空间。请记住,每个递归“堆栈帧”必须有 3 个指针(即使您不使用实际递归)。分而治之,不能一味抛弃旧有的价值。
    • 此处描述:en.wikipedia.org/wiki/In-place_algorithm。 快速排序通常被描述为一种就地算法,但实际上并非如此。大多数实现需要 O(log n) 空间来支持其分而治之的递归。尽管如此 +1 仍然比 O(n) 好。
    • @Mark Peters - 啊,你是对的。我没有考虑堆栈帧上的指针,鉴于它是伪递归的,这显然是需要考虑的。我的错。对于一些可以在 O(n log(n)) 中运行的就地排序算法,我会更加努力。
    • 问题不是规定了单链表吗?而且这个算法不需要双向链表吗?
    • @Jeffrey L Whitledge - 这太尴尬了。我不能完全通过单链表倒退。那是我的愚蠢。
    【解决方案5】:

    插入排序和快速排序都可以在单链表上完成,其效率与在数组上相同。

    【讨论】:

    • 感谢您的快速回答,您能解释一下如何吗?似乎那些使用随机访问属性来实现效率的类型,例如在合并排序中,您将如何访问列表的中间部分?你必须运行 n/2 步
    • 在单链表上进行快速排序的效率与在数组上的效率相同。
    • @incrediman:为什么不呢?从列表中取出第一个作为枢轴,然后遍历其余的,构建两个新列表。对这两个列表进行排序。将它们拼接在一起。链表实际上比数组更容易。 (我承认归并排序是错误的)
    • 不是拼接 O(n) 吗?如果没有,您必须跟踪列表中每个列表的长度,这当然也不会有效。如果您可以用伪代码为单链表编写一个快速排序实现,以演示其效率如何与数组上的快速排序相当,那将会很有用。
    • *您必须跟踪列表中每个列表的尾部
    猜你喜欢
    • 1970-01-01
    • 2022-10-04
    • 1970-01-01
    • 1970-01-01
    • 2010-10-20
    • 2021-04-20
    • 2011-12-26
    相关资源
    最近更新 更多