web-dev-qa-db-fra.com

Tri par fusion non récursif

Quelqu'un peut-il expliquer en anglais comment fonctionne le tri par fusion non récursif?

Merci

24
DarthVader

Passez en boucle dans les éléments et faites en sorte que tous les groupes adjacents de deux soient triés en les échangeant si nécessaire.

Maintenant, traiter avec des groupes de deux groupes (deux quelconques, des groupes probablement les plus adjacents, mais vous pouvez utiliser les premier et dernier groupes), fusionnez-les en un groupe sélectionnant l'élément de la plus basse valeur de chaque groupe jusqu'à ce que les 4 groupe de 4. Maintenant, vous n'avez rien d'autre que des groupes de 4 plus un reste possible. En utilisant une boucle autour de la logique précédente, faites tout à nouveau, sauf que cette fois, travaillez par groupes de 4. Cette boucle s'exécute jusqu'à ce qu'il n'y ait qu'un seul groupe.

14
DigitalRoss

Le tri par fusion non récursif fonctionne en considérant les tailles de fenêtre de 1,2,4,8,16..2 ^ n sur le tableau en entrée. Pour chaque fenêtre ("k" dans le code ci-dessous), toutes les paires de fenêtres adjacentes sont fusionnées dans un espace temporaire, puis replacées dans le tableau.

Voici mon unique fonction, le type de fusion non-récursif basé sur C . Les entrées et sorties sont en 'a'. Stockage temporaire dans 'b'. Un jour, j'aimerais avoir une version en place:

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

En passant, il est également très facile de prouver que c’est O (n log n). La boucle externe sur la taille de la fenêtre augmente avec une puissance de deux, donc k a des itérations log n. Bien qu'il y ait beaucoup de fenêtres couvertes par la boucle interne, ensemble, toutes les fenêtres pour un k donné couvrent exactement le tableau d'entrée, donc la boucle interne est O (n). Combinaison de boucles interne et externe: O (n) * O (log n) = O (n log n).

18
Rama Hoetzlein

Citant de Algorithmist :

Le tri par fusion ascendante est un variante non récursive de la fusion sort, dans lequel le tableau est trié par une séquence de passes. Au cours de chaque passer, le tableau est divisé en blocs de taille m. (Initialement, m = 1) . Tous les deux blocs adjacents sont fusionnés (comme dans le tri de fusion normal), et le le prochain passage est fait avec un deux fois plus grand valeur de m.

7
bobbymcr

La raison principale pour laquelle vous souhaitez utiliser un MergeSort non récursif est d'éviter le débordement de pile de la récursivité. Par exemple, j'essaie de trier 100 millions d'enregistrements, chacun d'environ 1 Ko (100 gigaoctets), par ordre alphanumérique. Un tri d'ordre (N ^ 2) nécessiterait 10 ^ 16 opérations, c'est-à-dire qu'il faudrait des décennies pour fonctionner même à 0,1 microseconde par opération de comparaison. Une commande (N log (N)) Le tri par fusion nécessite moins de 10 ^ 10 opérations ou moins d’une heure pour être exécuté à la même vitesse opérationnelle. Toutefois, dans la version récursive de MergeSort, le tri d'éléments 100 millions entraîne 50 millions d'appels récursifs à MergeSort (). Avec une récurrence de quelques centaines d’octets par pile, la pile de la récursion déborde alors que le processus s’intègre facilement dans la mémoire de tas. Faire le tri par fusion en utilisant de la mémoire allouée dynamiquement sur le tas - J'utilise le code fourni par Rama Hoetzlein ci-dessus, mais j'utilise de la mémoire allouée dynamiquement sur le tas au lieu d'utiliser la pile - Je peux trier mes 100 millions d'enregistrements avec le sorte de fusion non-récursive et je ne déborde pas la pile. Une conversation appropriée pour le site "Stack Overflow"!

PS: Merci pour le code, Rama Hoetzlein.

PPS: 100 gigaoctets sur le tas? !! C'est un tas virtuel sur un cluster Hadoop, et le MergeSort sera implémenté en parallèle sur plusieurs machines partageant la charge ...

4
K. Ricci

Les deux types de fusion récursif et non récursif ont la même complexité temporelle que O (nlog (n)). Cela est dû au fait que les deux approches utilisent la pile d'une manière ou d'une autre.

En approche non récursive l'utilisateur/programmeur définit et utilise la pile

En approche récursive, la pile est utilisée en interne par le système pour stocker l'adresse de retour de la fonction, appelée récursivement

4
Vivek Dani

Je suis nouveau ici . J'ai modifié la solution de Rama Hoetzlein (merci pour les idées). Mon tri par fusion n'utilise pas la dernière boucle de recopie. De plus, il tombe sur le tri par insertion. Je l'ai référencé sur mon ordinateur portable et c'est le plus rapide. Encore mieux que la version récursive. En passant, il est en Java et trie d’ordre décroissant à croissant. Et bien sûr, c'est itératif. Il peut être multithread. Le code est devenu complexe. Donc, si quelqu'un est intéressé, jetez un coup d'oeil.

Code: 

    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;
1
ytoamn

Juste au cas où quelqu'un se cacherait encore dans ce fil ... J'ai adapté l'algorithme de tri par fusion non récursif de Rama Hoetzlein ci-dessus pour trier les listes à double liaison. Ce nouveau tri est en place, stable et évite des coûts de division du code de la liste coûteux dans d'autres implémentations de tri par fusion de listes liées.

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

Édité 2017-10-27: Correction d'un bug affectant les listes impaires

0
Angus Johnson