Естественное слияние отсортировать связанный список

Некоторое время я искал реализацию естественной сортировки слиянием (связанных списков), но безуспешно.

Сортировать связанный список слиянием

Здесь у нас есть и рекурсивная, и итеративная реализация, но я не знаю, как превратить это в естественную сортировку слиянием.

Как проверить выполнение прогонов, чтобы получить сложность O (n) в лучшем случае? Это не обязательно должен быть C / C ++, может быть любой язык или даже псевдокод.

Спасибо.


person Miko    schedule 21.04.2012    source источник


Ответы (4)


В Википедии есть реализация псевдокода:

 # Original data is on the input tape; the other tapes are blank
 function mergesort(input_tape, output_tape, scratch_tape_C, scratch_tape_D)
     while any records remain on the input_tape
         while any records remain on the input_tape
             merge( input_tape, output_tape, scratch_tape_C)
             merge( input_tape, output_tape, scratch_tape_D)
         while any records remain on C or D
             merge( scratch_tape_C, scratch_tape_D, output_tape)
             merge( scratch_tape_C, scratch_tape_D, input_tape)

 # take the next sorted chunk from the input tapes, and merge into the single given output_tape.
 # tapes are scanned linearly.
 # tape[next] gives the record currently under the read head of that tape.
 # tape[current] gives the record previously under the read head of that tape.
 # (Generally both tape[current] and tape[previous] are buffered in RAM ...)
 function merge(left[], right[], output_tape[])
     do
        if left[current] ≤ right[current]
            append left[current] to output_tape
            read next record from left tape
        else
            append right[current] to output_tape
            read next record from right tape
    while left[current] < left[next] and right[current] < right[next]
    if left[current] < left[next]
        append current_left_record to output_tape
    if right[current] < right[next]
        append current_right_record to output_tape
    return
person ulmangt    schedule 21.04.2012
comment
Спасибо, это не из Википедии. Кажется, что это может работать для связанных списков, но этот дизайн требует передачи по ссылке или двойных указателей, которые недоступны в моем целевом языке. Все еще пытаюсь обойти это. - person Miko; 22.04.2012
comment
Нет, эту логику нельзя применить к связанным спискам. - person Miko; 22.04.2012
comment
Псевдокод либо неполный, я неверно его истолковал, либо просто неправильно. Не могу заставить это работать на бумаге / теории. - person Miko; 22.04.2012

Это моя попытка на F #. Реализация обычной сортировки слиянием для справки:

// Sorts a list containing elements of type T.  Takes a comparison
// function comp that takes two elements of type T and returns -1
// if the first element is less than the second, 0 if they are equal,
// and 1 if the first element is greater than the second.
let rec sort comp = function 
| []  -> []  // An empty list is sorted
| [x] -> [x] // A single element list is sorted
| xs  ->
    // Split the list in half, sort both halves,
    // and merge the sorted halves.
    let half = (List.length xs) / 2
    let left, right = split half xs
    merge comp (sort comp left) (sort comp right)

Теперь попытка натуральной версии. В лучшем случае это будет O (n), но в лучшем случае - когда список ввода находится в обратном порядке.

let rec sort' comp ls =

    // Define a helper function.  Streaks are stored in an accumulator.
    let rec helper accu = function
    | [] -> accu 
    | x::xs -> 
        match accu with
        // If we are not in a streak, start a new one
        | [] -> helper [x] xs 

        // If we are in a streak, check if x continues
        // the streak.
        | y::ys -> 
            if comp y x > 0 

            // x continues the streak so we add it to accu
            then helper (x::y::ys) xs

            // The streak is over. Merge the streak with the rest
            // of the list, which is sorted by calling our helper function on it.
            else merge comp accu (helper [x] xs)

    helper [] ls

Вторая попытка. Это также будет O (n) в лучшем случае, тогда как в лучшем случае сейчас, когда список ввода уже отсортирован. Я отказался от функции сравнения. Отсортированный список будет построен в обратном порядке, поэтому вам нужно будет перевернуть его в конце.

let rec sort'' comp ls =
    // Flip the comparison function
    let comp' = fun x y -> -1 * (comp x y)
    let rec helper accu = function
    | [] -> accu
    | x::xs -> 
        match accu with
        | [] -> helper [x] xs
        | y::ys -> 
            if comp' y x > 0 
            then helper (x::y::ys) xs
            else merge comp' accu (helper [x] xs)

    // The list is in reverse sorted order so reverse it.
    List.rev (helper [] ls)
person mushroom    schedule 07.12.2012

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

[Код на Java]

// Merge sort the linked list.
// From min to max.
// Time complexity = O(nlgn).
public static Node mergeSortLLFromMinToMax (Node head) {
    if (head == null || head.next == null) return head; // No need to sort.
    // Get the mid point of this linked list.
    Node prevSlower = head;
    Node slower = head;
    Node faster = head;
    while (faster != null && faster.next != null) {
        prevSlower = slower;
        slower = slower.next;
        faster = faster.next.next;
    }
    // Cut of the main linked list.
    prevSlower.next = null;

    // Do recursion.
    Node left = mergeSortLLFromMinToMax (head);
    Node right = mergeSortLLFromMinToMax (slower);

    // Merge the left and right part from min to max.
    Node currHead = new Node ();
    Node tempCurrHead = currHead;
    while (left != null && right != null) {
        if (left.data <= right.data) {
            // Add the elem of the left part into main linked list.
            tempCurrHead.next = left;
            left = left.next;
        } else {
            // Add the elem of the right part into main linked list.
            tempCurrHead.next = right;
            right = right.next;
        }
        tempCurrHead = tempCurrHead.next;
    }
    if (left != null) {
        // Add the remaining part of left part into main linked list.
        tempCurrHead.next = left;
        left = left.next;
        tempCurrHead = tempCurrHead.next;
    } else if (right != null) {
        // Add the remaining part of right part into main linked list.
        tempCurrHead.next = right;
        right = right.next;
        tempCurrHead = tempCurrHead.next;
    }

    return currHead.next;
}
person zproject89    schedule 20.04.2014

Моя очень сырая реализация алгоритма с использованием C #

public static class LinkedListSort
{
    public static DataStructures.Linear.LinkedListNode<T> Sort<T>(DataStructures.Linear.LinkedListNode<T> firstNode) where T : IComparable<T>
    {
        if (firstNode == null)
            throw new ArgumentNullException();

        if (firstNode.Next == null)
            return firstNode;

        var head = firstNode;
        var leftNode = head;
        int iterNum = 0;

        while (leftNode != null)
        {
            //Let's start again from the begining
            leftNode = head;
            iterNum = 0;
            DataStructures.Linear.LinkedListNode<T> tailNode = null;

            while (leftNode != null)
            {
                //Let's get the left sublist

                //Let's find the node which devides sublist into two ordered sublists
                var sentinelNode = GetSentinelNode(leftNode);
                var rightNode = sentinelNode.Next;

                //If the right node is null it means that we don't have two sublist and the left sublist is ordered already
                //so we just add the rest sublist to the tail
                if (rightNode == null)
                {
                    if (tailNode == null)
                        break;
                    tailNode.Next = leftNode;
                    break;
                }

                sentinelNode.Next = null;

                //Let's find the node where the right sublist ends
                sentinelNode = GetSentinelNode(rightNode);
                var restNode = sentinelNode.Next;
                sentinelNode.Next = null;

                DataStructures.Linear.LinkedListNode<T> newTailNode = null;

                //Merging of two ordered sublists   
                var mergedList = Merge(leftNode, rightNode, ref newTailNode);
                //If we're at the beginning of the list the head of the merged sublist becomes the head of the list
                if (iterNum == 0)                   
                    head = mergedList;                  
                else //add the                  
                    tailNode.Next = mergedList;                     

                tailNode = newTailNode;
                leftNode = restNode;
                iterNum++;
            }
            if (iterNum == 0)
                break;
        }
        return head;
    }

    /// <summary>
    /// Merges two ordered sublists   
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="aNode">Left part of sublist</param>
    /// <param name="bNode">Right part of sublist</param>
    /// <param name="tailNode">Tail node of the merged list</param>
    /// <returns>The result of merging</returns>
    private static DataStructures.Linear.LinkedListNode<T> Merge<T>(DataStructures.Linear.LinkedListNode<T> leftNode,                                                                       
                                                                    DataStructures.Linear.LinkedListNode<T> rightNode,
                                                                    ref DataStructures.Linear.LinkedListNode<T> tailNode) where T : IComparable<T>
    {
        var dummyHead = new DataStructures.Linear.LinkedListNode<T>();
        var curNode = dummyHead;

        while (leftNode != null || rightNode != null)
        {
            if (rightNode == null)
            {
                curNode.Next = leftNode;
                leftNode = leftNode.Next;
            }
            else if (leftNode == null)
            {
                curNode.Next = rightNode;
                rightNode = rightNode.Next;
            }
            else if (leftNode.Value.CompareTo(rightNode.Value) <= 0)
            {
                curNode.Next = leftNode;
                leftNode = leftNode.Next;
            }
            else
            {
                curNode.Next = rightNode;
                rightNode = rightNode.Next;
            }
            curNode = curNode.Next;
        }
        tailNode = curNode;
        return dummyHead.Next;
    }

    /// <summary>
    /// Returns the sentinel node
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="firstNode"></param>
    /// <returns></returns>
    private static DataStructures.Linear.LinkedListNode<T> GetSentinelNode<T>(DataStructures.Linear.LinkedListNode<T> firstNode) where T : IComparable<T>
    {
        var curNode = firstNode;

        while (curNode != null && curNode.Next != null && curNode.Value.CompareTo(curNode.Next.Value) <= 0)             
            curNode = curNode.Next;

        return curNode;
    }
}
person Grigory Bushuev    schedule 07.01.2015
comment
Я не вижу, где это идентифицирует прогоны во входных данных, как того требует natural часть вопроса и время выполнения O (n) в лучшем случае. Можете просветить меня? - person Mark Ransom; 07.01.2015
comment
@MarkRansom Естественная сортировка слиянием предполагает, что мы используем естественные отсортированные последовательности. В своей реализации я использую этот случай. Наилучший случай O (n), который у нас есть, когда все элементы входных данных упорядочены. Моя отредактированная реализация также использует этот случай. - person Grigory Bushuev; 08.01.2015