Каков наилучший алгоритм сортировки на месте для сортировки односвязного списка

Я читал на месте алгоритм сортировки для сортировки связанных списков. Согласно Википедии

Merge sort часто является лучшим выбором для сортировки связанного списка: в этой ситуации относительно легко реализовать сортировку слиянием таким образом, что требуется только Θ(1) дополнительное пространство и медленная производительность случайного доступа связанного списка делают некоторые другие алгоритмы (такие как quicksort) работать плохо, а другие (такие как heapsort) полностью невозможно.

насколько мне известно, алгоритм сортировки слиянием составляет не алгоритм сортировки на месте и имеет наихудшую пространственную сложность O(n) вспомогательный. Теперь, с учетом этого, я не могу решить, существует ли подходящий алгоритм для сортировки одиночного связанного списка с O(1) вспомогательное пространство.

2 ответов


как указал Фабио А. в комментарии, алгоритм сортировки, подразумеваемый следующими реализациями слияния и разделения, фактически требует o(log n) дополнительного пространства в виде кадров стека для управления рекурсией (или их явным эквивалентом). Алгоритм O (1)-пространства возможен с использованием совершенно другого подхода снизу вверх.

вот алгоритм слияния O(1)-space, который просто создает новый список, перемещая нижний элемент из верхней части каждого списка в конец нового списка:

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)-space, который просто разбивает список на 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;
}

обратите внимание, что half перемещается вперед только в два раза меньше, чем in есть. После возвращения этой функции список первоначально передавался как in будет изменен таким образом, что он содержит только первые элементы ceil(n/2), а возвращаемое значение-это список, содержащий оставшиеся элементы пола(n/2).


это как-то напоминает мне о моем ответе на Голландский Национальный флаг проблема вопрос.

подумав, что это то, к чему я пришел, давайте посмотрим, сработает ли это. Я полагаю, что основная проблема-это шаг слияния mergesort в O(1) дополнительное пространство.

наше представление связанного списка:

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

вы в конечном итоге с этим слияние шаг:

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

будучи p и q в указатели для сегментов, которые мы хотим объединить.

просто добавьте свои узлы после 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) дополнительное пространство, чтобы иметь возможность перемещать элементы.