当前位置: 首页 > 知识库问答 >
问题:

对单链表进行排序的最佳就地排序算法是什么

赵同
2023-03-14

我一直在阅读就地排序算法来排序链表。根据维基百科

合并排序通常是对链表进行排序的最佳选择:在这种情况下,实现合并排序相对容易,只需要< code >θ(1)额外的空间,并且链表缓慢的随机访问性能使得其他一些算法(如quicksort)表现不佳,而其他一些算法(如heapsort)则完全不可能。

据我所知,合并排序算法不是一个就地排序算法,并且具有O(n)辅助的最坏情况空间复杂性。现在,考虑到这一点,我无法确定是否存在合适的算法来对具有O(1)辅助空间的单链表进行排序。

共有2个答案

方轩昂
2023-03-14

这在某种程度上让我想起了我对荷兰国旗问题问题的回答。

在考虑了一下这就是我想到的之后,让我们看看这是否有效。我想主要问题是合并排序在O(1)额外空间中的合并步骤。

我们对链表的表示:

[ 1 ] => [ 3 ] => [ 2 ] => [ 4 ]
  ^head                      ^tail

您将完成以下合并步骤:

[ 1 ] => [ 3 ] => [ 2 ] => [ 4 ]
  ^p                ^q       ^tail

作为< code>p和< code>q我们想要合并的段的指针。

只需在<code>尾部</code>指针之后添加节点即可。如果<code>*p

[ 1 ] => [ 3 ] => [ 2 ] => [ 4 ] => [ 1 ]
  ^p       ^pp      ^q/qq    ^tail    ^tt

否则,添加q

[ 1 ] => [ 3 ] => [ 2 ] => [ 4 ] => [ 1 ] => [ 2 ]
  ^p       ^pp      ^q       ^qq/tail          ^tt

(跟踪列表 q 的结尾变得棘手)

现在,如果你移动它们,你会很快失去你在哪里的线索。你可以用一个聪明的方法来移动你的指针或者把长度加入到等式中。我肯定更喜欢后者。方法变成了:

[ 1 ] => [ 3 ] => [ 2 ] => [ 4 ]
  ^p(2)             ^q(2)    ^tail
[ 3 ] => [ 2 ] => [ 4 ] => [ 1 ]
  ^p(1)    ^q(2)             ^tail
[ 3 ] => [ 4 ] => [ 1 ] => [ 2 ]
  ^p(1)    ^q(1)             ^tail
[ 4 ] => [ 1 ] => [ 2 ] => [ 3 ]
  ^p(0)/q(1)                 ^tail
[ 1 ] => [ 2 ] => [ 3 ] => [ 4 ]
  ^q(0)                      ^tail

现在,您可以使用O(1)额外空间来移动元素

亢仰岳
2023-03-14

正如 Fabio A. 在注释中指出的那样,以下合并和拆分实现所暗示的排序算法实际上需要堆栈帧形式的 O(log n) 额外空间来管理递归(或其显式等效项)。O(1)空间算法可以使用完全不同的自下而上方法

这里有一个O(1)空间合并算法,它只需通过将每个列表顶部的较低项移动到新列表的末尾来构建一个新列表:

struct node {
    WHATEVER_TYPE val;
    struct node* next;
};

node* merge(node* a, node* b) {
    node* out;
    node** p = &out;    // Address of the next pointer that needs to be changed

    while (a && b) {
        if (a->val < b->val) {
            *p = a;
            a = a->next;
        } else {
            *p = b;
            b = b->next;
        }

        // Next loop iter should write to final "next" pointer
        p = &(*p)->next;
    }

    // At least one of the input lists has run out.
    if (a) {
        *p = a;
    } else {
        *p = b;        // Works even if b is NULL
    }

    return out;
}

通过对要添加到输出列表的第一项进行特殊的大小写,可以避免指针指向指针p,但我认为我这样做的方式更清晰。

下面是一个 O(1) 空间分割算法,它只是将列表分解为 2 个大小相等的片段:

node* split(node* in) {
    if (!in) return NULL;    // Have to special-case a zero-length list

    node* half = in;         // Invariant: half != NULL
    while (in) {
        in = in->next;
        if (!in) break;
        half = half->next;
        in = in->next;
    }

    node* rest = half->next;
    half->next = NULL;
    return rest;
}

请注意,< code>half向前移动的次数只有< code>in的一半。在此函数返回时,最初作为< code>in传递的列表将被更改,因此它只包含第一个ceil(n/2)项,返回值是包含其余floor(n/2)项的列表。

 类似资料:
  • 这是我的节点类: 我有一个单链表,需要使用插入排序进行排序。我对常规列表的插入排序有很好的理解,但对链表没有。我发现另一个帖子,一个用户说: 让我们考虑一下插入排序是如何工作的:它将列表“拆分”(理论上)为三组:已排序的子集(可能为空)、当前项和未排序的子集(可能为空)。排序当前项之前的所有内容。当前项之后的所有内容都可以排序,也可以不排序。算法检查当前项,并将其与下一项进行比较。请记住,当前项之

  • 哪种排序算法适合对堆栈进行排序以提高空间效率?我需要对堆栈进行“就地”排序。此外,我对“就地”算法的理解是它们不使用任何其他数据结构 - 这是正确的吗? 我知道这与这个问题相似,但我想知道堆栈是否会有所不同?我知道堆栈可以只是一种链接列表,但是你只能访问顶部的事实会改变你的做法吗?

  • 所以我有5个块(假设大小为2000个项目),每个块都是经过排序的数据。是否有一种算法能够利用此属性优化整个10000个项目的排序?

  • 有什么算法让它成为一个链表的并行排序值得吗? 众所周知,合并排序是用于排序链表的最佳算法。 大多数合并排序都是根据数组来解释的,每一半都是递归排序的。这将使并行化变得微不足道:对每一半独立排序,然后合并两半。 但是链表没有“中途”点;链表一直持续到结束: 头→[a]→[b]→[c]→[d]→[e]→[f]→[g]→[h]→[i]→[j]→… 我现在使用的实现遍历列表一次以获取计数,然后递归地拆分计

  • 问题内容: 我需要按字母顺序对链接列表进行排序。我有一个完整的乘客姓名链接列表,需要将乘客姓名按字母顺序排序。一个人怎么做?有人有参考资料或视频吗? 问题答案: 您可以用来按字母顺序对事物进行排序。