Нерекурсивная Сортировка Слиянием

может кто-нибудь объяснить на английском языке, как работает Нерекурсивная сортировка слияния ?

спасибо

7 ответов


цикл через элементы и сделать каждую смежную группу из двух отсортированных путем замены двух, когда это необходимо.

теперь, имея дело с группами из двух групп (любые две, скорее всего, смежные группы, но вы можете использовать первую и последнюю группы) объединить их в одну группу, выбирая наименьшее значение элемента из каждой группы неоднократно, пока все 4 элемента не будут объединены в группу из 4. Теперь у вас нет ничего, кроме групп по 4 плюс возможный остаток. Использование цикла вокруг предыдущего логика, сделайте все это снова, кроме этого времени работы в группах по 4. Этот цикл выполняется до тех пор, пока не останется только одна группа.


нерекурсивная сортировка слиянием работает с учетом размеров окон 1,2,4,8,16..2^n над входным массивом. Для каждого окна ("k" в коде ниже) все соседние пары окон объединяются во временное пространство, а затем помещаются обратно в массив.

вот моя единственная функция, основанная на C, нерекурсивная сортировка слияния. Вход и выход в "а". Временное хранение в "б". Однажды я хотел бы иметь версию, которая была на месте:

float a[50000000],b[50000000];
void mergesort (long num)
{
    int rght, wid, rend;
    int i,j,m,t;

    for (int k=1; k < num; k *= 2 ) {       
        for (int left=0; left+k < num; left += k*2 ) {
            rght = left + k;        
            rend = rght + k;
            if (rend > num) rend = num; 
            m = left; i = left; j = rght; 
            while (i < rght && j < rend) { 
                if (a[i] <= a[j]) {         
                    b[m] = a[i]; i++;
                } else {
                    b[m] = a[j]; j++;
                }
                m++;
            }
            while (i < rght) { 
                b[m]=a[i]; 
                i++; m++;
            }
            while (j < rend) { 
                b[m]=a[j]; 
                j++; m++;
            }
            for (m=left; m < rend; m++) { 
                a[m] = b[m]; 
            }
        }
    }
}

кстати, это также очень легко докажите, что это O (n log n). Внешний цикл по размеру окна растет как мощность двух, поэтому k имеет n итераций журнала. Хотя есть много окон, покрытых внутренним циклом, все окна для данного k точно покрывают входной массив, поэтому внутренний цикл-O(n). Объединение внутренней и внешней петель: O(n)*O(log n) = O (N log n).


цитирую архив:

сортировка слияния снизу вверх нерекурсивный вариант слияния сортировка, в которой массив сортируется по последовательность проходов. Во время каждого проходим, массив делится на блоки в размере m. (Первоначально m = 1). Каждые два соседних блока объединяются (как и в обычной сортировке слияния), и следующий проход сделан с вдвое большим значение m.


и рекурсивная, и нерекурсивная сортировка слияния имеют одинаковую временную сложность O(nlog(n)). Это связано с тем, что оба подхода используют стек тем или иным способом.

В нерекурсивном подходе пользователь/программист определяет и использует стек

В рекурсивном подходе стек используется внутренне системой для хранения обратного адреса функции, которая называется рекурсивно


основная причина, по которой вы хотите использовать нерекурсивный MergeSort, - это избежать переполнения стека рекурсии. Я, например, пытаюсь отсортировать 100 миллионов записей, каждая запись длиной около 1 кбайт (= 100 гигабайт), в алфавитно-цифровом порядке. Сортировка Порядка (N^2) заняла бы 10^16 операций, т. е. потребовались бы десятилетия, чтобы выполнить даже 0,1 микросекунды за операцию сравнения. Порядок(N log (N)) сортировка слиянием займет менее 10^10 операций или менее часа для выполнения одной и той же операции скорость. Однако в рекурсивной версии MergeSort сортировка 100 миллионов элементов приводит к 50 миллионам рекурсивных вызовов MergeSort (). При рекурсии в несколько сотен байтов на стек это переполняет стек рекурсии, хотя процесс легко помещается в память кучи. Выполнение сортировки слиянием с использованием динамически выделенной памяти в куче - я использую код, предоставленный рамой Хетцлайном выше, но я использую динамически выделенную память в куче вместо использования стека - я могу сортировки 100 миллионов записей с нерекурсивный алгоритм сортировки слиянием, а не переполнение стека. Соответствующий разговор для сайта "Stack Overflow"!

PS: Спасибо за код, рама Хетцлайн.

PPS: 100 гигабайт в куче?!! Ну, это виртуальная куча в кластере Hadoop, и MergeSort будет реализован параллельно на нескольких машинах, разделяющих нагрузку...


Я здесь новенький. Я изменил решение рамы Хетцлейна (спасибо за идеи ). Моя сортировка слиянием не использует последний цикл копирования. Кроме того, он возвращается к сортировке вставки. Я сравнил его на своем ноутбуке, и это самый быстрый. Даже лучше, чем рекурсивная версия. Кстати это в Java и от убывания к возрастанию. И, конечно, это итеративно. Его можно сделать многопоточным. Код стал сложным. Так что если кому интересно, есть смотреть.

код :

    int num = input_array.length;
    int left = 0;
    int right;
    int temp;
    int LIMIT = 16;
    if (num <= LIMIT)
    {
        // Single Insertion Sort
        right = 1;
        while(right < num)
        {
            temp = input_array[right];
            while(( left > (-1) ) && ( input_array[left] > temp ))
            {
                input_array[left+1] = input_array[left--];
            }
            input_array[left+1] = temp;
            left = right;
            right++;
        }
    }
    else
    {
        int i;
        int j;
        //Fragmented Insertion Sort
        right = LIMIT;
        while (right <= num)
        {
            i = left + 1;
            j = left;
            while (i < right)
            {
                temp = input_array[i];
                while(( j >= left ) && ( input_array[j] > temp ))
                {
                    input_array[j+1] = input_array[j--];
                }
                input_array[j+1] = temp;
                j = i;
                i++;
            }
            left = right;
            right = right + LIMIT;
        }
        // Remainder Insertion Sort
        i = left + 1;
        j = left;
        while(i < num)
        {
            temp = input_array[i];
            while(( j >= left ) && ( input_array[j] > temp ))
            {
                input_array[j+1] = input_array[j--];
            }
            input_array[j+1] = temp;
            j = i;
            i++;
        }
        // Rama Hoetzlein method
        int[] temp_array = new int[num];
        int[] swap;
        int k = LIMIT;
        while (k < num)
        {
            left = 0;
            i = k;// The mid point
            right = k << 1;
            while (i < num)
            {
                if (right > num)
                {
                    right = num;
                }
                temp = left;
                j = i;
                while ((left < i) && (j < right))
                {
                    if (input_array[left] <= input_array[j])
                    {
                        temp_array[temp++] = input_array[left++];
                    }
                    else
                    {
                        temp_array[temp++] = input_array[j++];
                    }
                }
                while (left < i)
                {
                    temp_array[temp++] = input_array[left++];
                }
                while (j < right)
                {
                    temp_array[temp++] = input_array[j++];
                }
                // Do not copy back the elements to input_array
                left = right;
                i = left + k;
                right = i + k;
            }
            // Instead of copying back in previous loop, copy remaining elements to temp_array, then swap the array pointers
            while (left < num)
            {
                temp_array[left] = input_array[left++];
            }
            swap = input_array;
            input_array = temp_array;
            temp_array = swap;
            k <<= 1;
        }
    }

    return input_array;

на всякий случай, если кто-то все еще скрывается в этой теме ... Я адаптировал нерекурсивный алгоритм сортировки слияния рамы Хетцлейна выше для сортировки двойных связанных списков. Эта Новая Сортировка на месте, стабильна и позволяет избежать дорогостоящего по времени разделения кода, который находится в других реализациях сортировки слиянием связанных списков.

// MergeSort.cpp
// Angus Johnson 2017
// License: Public Domain

#include "io.h"
#include "time.h"
#include "stdlib.h"

struct Node {
    int data;
    Node *next;
    Node *prev;
    Node *jump;
};

inline void Move2Before1(Node *n1, Node *n2)
{
    Node *prev, *next;
    //extricate n2 from linked-list ...
    prev = n2->prev;
    next = n2->next;
    prev->next = next; //nb: prev is always assigned
    if (next) next->prev = prev;
    //insert n2 back into list ...  
    prev = n1->prev;
    if (prev) prev->next = n2;
    n1->prev = n2;
    n2->prev = prev;
    n2->next = n1;
}

void MergeSort(Node *&nodes)
{
    Node *first, *second, *base, *tmp, *prev_base;

    if (!nodes || !nodes->next) return;
    int mul = 1;
    for (;;) {
        first = nodes;
        prev_base = NULL;
        //sort each successive mul group of nodes ...
        while (first) {
            if (mul == 1) {
                second = first->next;
                if (!second) { 
                  first->jump = NULL;
                  break;
                }
                first->jump = second->next;
            }
            else
            {
                second = first->jump;
                if (!second) break;
                first->jump = second->jump;
            }
            base = first;
            int cnt1 = mul, cnt2 = mul;
            //the following 'if' condition marginally improves performance 
            //in an unsorted list but very significantly improves
            //performance when the list is mostly sorted ...
            if (second->data < second->prev->data) 
                while (cnt1 && cnt2) {
                    if (second->data < first->data) {
                        if (first == base) {
                            if (prev_base) prev_base->jump = second;
                            base = second;
                            base->jump = first->jump;
                            if (first == nodes) nodes = second;
                        }
                        tmp = second->next;
                        Move2Before1(first, second);
                        second = tmp;
                        if (!second) { first = NULL; break; }
                        --cnt2;
                    }
                    else
                    {
                        first = first->next;
                        --cnt1;
                    }
                } //while (cnt1 && cnt2)
            first = base->jump;
            prev_base = base;
        } //while (first)
        if (!nodes->jump) break;
        else mul <<= 1;
    } //for (;;)
}

void InsertNewNode(Node *&head, int data)
{
    Node *tmp = new Node;
    tmp->data = data;
    tmp->next = NULL;
    tmp->prev = NULL;
    tmp->jump = NULL;
    if (head) {
        tmp->next = head;
        head->prev = tmp;
        head = tmp;
    }
    else head = tmp;
}

void ClearNodes(Node *head)
{
    if (!head) return;
    while (head) {
        Node *tmp = head;
        head = head->next;
        delete tmp;
    }
}

int main()
{  
    srand(time(NULL));
    Node *nodes = NULL, *n;
    const int len = 1000000; //1 million nodes 
    for (int i = 0; i < len; i++)
        InsertNewNode(nodes, rand() >> 4);

    clock_t t = clock();
    MergeSort(nodes);    //~1/2 sec for 1 mill. nodes on Pentium i7. 
    t = clock() - t;
    printf("Sort time: %d msec\n\n", t * 1000 / CLOCKS_PER_SEC);

    n = nodes;
    while (n)
    {
        if (n->prev && n->data < n->prev->data) { 
            printf("oops! sorting's broken\n"); 
            break;
        }
        n = n->next;
    }
    ClearNodes(nodes);
    printf("All done!\n\n");
    getchar();
    return 0;
}

отредактировано 2017-10-27: Исправлена ошибка, влияющая на нечетные нумерованные списки