什么是最好的排序算法来排序单链表

时间:2012-06-30 07:58:13

标签: algorithm mergesort space-complexity

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

  

合并排序通常是排序链表的最佳选择:在这种情况下,以这样的方式实现合并排序相对容易,它只需要Θ(1)个额外空间和慢随机访问链表的性能使得其他一些算法(如快速排序)表现不佳,而其他算法(例如heapsort)完全不可能。

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

2 个答案:

答案 0 :(得分:4)

正如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)空间拆分算法,它简单地将列表分成两个大小相等的部分:

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;
}

请注意,half只向前移动了in的一半。在此函数返回时,最初作为in传递的列表将被更改,以便它仅包含第一个ceil(n / 2)项,返回值是包含剩余楼层的列表(n / 2)项目

答案 1 :(得分:1)

这有点让我想起我对Dutch National Flag Problem question的答案。

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

我们对链表的表示:

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

你最终得到了这个合并步骤:

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

pq作为我们要合并的细分的指针。

只需在tail指针后添加节点即可。如果*p <= *q您在尾部添加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)额外空间来移动元素。