288 votes

Comment trier à la place en utilisant l'algorithme de tri par fusion ?

Je sais que la question n'est pas trop précise.

Tout ce que je veux, c'est que quelqu'un me dise comment convertir un tri par fusion normal en un tri par fusion sur place (ou un tri par fusion avec une surcharge d'espace constante).

Tout ce que je peux trouver (sur le net), ce sont des pages disant "c'est trop complexe" ou "hors de portée de ce texte".

Les seuls moyens connus de fusionner en place (sans espace supplémentaire) sont trop complexes pour être réduits à un programme pratique. (pris d'ici )

Même si c'est trop complexe, quel est le concept de base pour faire le tri de la fusion sur place ?

0 votes

Jolie question, je me la suis posée moi-même en lisant une question d'hier : stackoverflow.com/questions/2566459/

0 votes

Juste à titre de référence, voici une belle implémentation d'un tri stable par fusion en place . Compliqué, mais pas trop mal. J'ai fini par mettre en place à la fois un triage stable par fusion en place et un tri rapide stable en place en Java. Veuillez noter que la complexité est O(n (log n)^2).

0 votes

Il existe une méthode assez simple décrite ici : xinok.wordpress.com/2014/08/17/

168voto

Larry LIU Xinyu Points 413

Knuth a laissé cela comme un exercice (Vol 3, 5.2.5). Il existe des tris de fusion en place. Ils doivent être implémentés avec précaution.

Tout d'abord, la fusion naïve en place telle que décrite aquí n'est pas la bonne solution. Elle rétrograde les performances à O(N 2 ) .

L'idée est de trier une partie du tableau tout en utilisant le reste comme zone de travail pour la fusion.

Par exemple, comme la fonction de fusion suivante.

void wmerge(Key* xs, int i, int m, int j, int n, int w) {
    while (i < m && j < n)
        swap(xs, w++, xs[i] < xs[j] ? i++ : j++);
    while (i < m)
        swap(xs, w++, i++);
    while (j < n)
        swap(xs, w++, j++);
}  

Il prend le tableau xs les deux sous-réseaux triés sont représentés comme des plages [i, m) y [j, n) respectivement. La zone de travail commence à partir de w . Comparé à l'algorithme de fusion standard donné dans la plupart des manuels, celui-ci échange le contenu entre le sous-réseau trié et la zone de travail. En conséquence, la zone de travail précédente contient les éléments triés fusionnés, tandis que les éléments précédents stockés dans la zone de travail sont déplacés vers les deux sous-réseaux.

Toutefois, deux contraintes doivent être satisfaites :

  1. La zone de travail doit se situer dans les limites du tableau. En d'autres termes, elle doit être suffisamment grande pour contenir les éléments échangés sans provoquer d'erreur hors limites.
  2. La zone de travail peut être superposée à l'un ou l'autre des deux tableaux triés ; toutefois, elle doit veiller à ce qu'aucun des éléments non immergés ne soit écrasé.

Avec cet algorithme de fusion défini, il est facile d'imaginer une solution qui peut trier la moitié du tableau. La question suivante est de savoir comment traiter le reste de la partie non triée stockée dans la zone de travail comme indiqué ci-dessous :

... unsorted 1/2 array ... | ... sorted 1/2 array ...

Une idée intuitive est de trier récursivement une autre moitié de la zone de travail, ainsi il n'y a que 1/4 des éléments qui n'ont pas encore été triés.

... unsorted 1/4 array ... | sorted 1/4 array B | sorted 1/2 array A ...

Le point clé à ce stade est que nous devons fusionner les éléments 1/4 triés B avec les éléments 1/2 triés A tôt ou tard.

La zone de travail restante, qui ne contient que 1/4 des éléments, est-elle assez grande pour fusionner A et B ? Malheureusement, il ne l'est pas.

Toutefois, la deuxième contrainte mentionnée ci-dessus nous donne un indice, à savoir que nous pouvons l'exploiter en disposant la zone de travail de manière à ce qu'elle chevauche l'un ou l'autre des sous-réseaux si nous pouvons garantir à la séquence de fusion que les éléments non fusionnés ne seront pas écrasés.

En fait, au lieu de trier la seconde moitié de la zone de travail, nous pouvons trier la première moitié, et placer la zone de travail entre les deux tableaux triés comme ceci :

... sorted 1/4 array B | unsorted work area | ... sorted 1/2 array A ...

Cette configuration permet d'organiser efficacement le chevauchement de la zone de travail avec le sous-réseau A. Cette idée est proposée dans [Jyrki Katajainen, Tomi Pasanen, Jukka Teuhola. ``Practical in-place mergesort''. Nordic Journal of Computing, 1996].

Il ne reste donc plus qu'à répéter l'étape ci-dessus, ce qui réduit la zone de travail de 1/2, 1/4, 1/8, Lorsque la zone de travail devient suffisamment petite (par exemple, il ne reste que deux éléments), nous pouvons passer à un tri d'insertion trivial pour terminer cet algorithme.

Voici l'implémentation en ANSI C basée sur cet article.

void imsort(Key* xs, int l, int u);

void swap(Key* xs, int i, int j) {
    Key tmp = xs[i]; xs[i] = xs[j]; xs[j] = tmp;
}

/* 
 * sort xs[l, u), and put result to working area w. 
 * constraint, len(w) == u - l
 */
void wsort(Key* xs, int l, int u, int w) {
    int m;
    if (u - l > 1) {
        m = l + (u - l) / 2;
        imsort(xs, l, m);
        imsort(xs, m, u);
        wmerge(xs, l, m, m, u, w);
    }
    else
        while (l < u)
            swap(xs, l++, w++);
}

void imsort(Key* xs, int l, int u) {
    int m, n, w;
    if (u - l > 1) {
        m = l + (u - l) / 2;
        w = l + u - m;
        wsort(xs, l, m, w); /* the last half contains sorted elements */
        while (w - l > 2) {
            n = w;
            w = l + (n - l + 1) / 2;
            wsort(xs, w, n, l);  /* the first half of the previous working area contains sorted elements */
            wmerge(xs, l, l + n - w, n, u, w);
        }
        for (n = w; n > l; --n) /*switch to insertion sort*/
            for (m = n; m < u && xs[m] < xs[m-1]; ++m)
                swap(xs, m, m - 1);
    }
}

Où wmerge est défini précédemment.

Le code source complet peut être trouvé aquí et l'explication détaillée peut être trouvée aquí

D'ailleurs, cette version n'est pas la plus rapide des tris par fusion car elle nécessite plus d'opérations d'échange. D'après mon test, elle est plus rapide que la version standard, qui alloue des espaces supplémentaires à chaque récursion. Mais elle est plus lente que la version optimisée, qui double le tableau original à l'avance et l'utilise pour les fusions ultérieures.

0 votes

J'ai retiré le lien pour ce chapitre. Le contenu se trouve dans le chapitre 13 du livre : sites.google.com/site/algoxy/home/elementary-algorithms.pdf

8 votes

Knuth left this as an exercise (Vol 3, 5.2.5). se réfère à l'ex. 13. 40] Mettre en œuvre la méthode de tri interne suggérée [à la fin de cette section], produisant qui trie les données aléatoires en O(N) unités de temps mith seulement O(sqrt(N)) des emplacements mémoire supplémentaires. ? ( 40 indiquant Il s'agit d'un problème assez difficile ou long qui peut être utilisé comme projet de fin d'études en classe. )

4 votes

Je pense que la complexité temporelle de l'algorithme in-place mentionné sur le site penguin.ew est O(log n * n^2), puisque nous avons log n fusions et que chaque fusion est de l'ordre de O(n ^2), n'est-ce pas ?

63voto

Steve Jessop Points 166970

Avec son "grand résultat", cet article décrit quelques variantes du tri par fusion sur place (PDF) :

http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.22.5514&rep=rep1&type=pdf

Triage sur place avec moins de déplacements

Jyrki Katajainen, Tomi A. Pasanen

Il est démontré qu'un tableau de n éléments peut être trié en utilisant O(1) d'espace supplémentaire, O(n log n / log log n) déplacements d'éléments, et n log 2 n + O(n log log n) comparaisons. Il s'agit de la première algorithme de tri in-place nécessitant o(n log n) déplacements dans le pire des cas tout en garantissant O(n log n) comparaisons, mais en raison des facteurs constants facteurs constants impliqués, l'algorithme est l'algorithme présente surtout un intérêt théorique.

Je pense que c'est également pertinent. J'en ai un exemplaire imprimé qui traîne, qui m'a été transmis par un collègue, mais je ne l'ai pas lu. Il semble couvrir la théorie de base, mais je ne suis pas assez familier avec le sujet pour juger de son exhaustivité :

http://comjnl.oxfordjournals.org/cgi/content/abstract/38/8/681

Fusion optimale et stable

Antonios Symvonis

Cet article montre comment fusionner de manière stable deux séquences A et B de tailles m et n, m n, respectivement, avec O(m+n) affectations, O(mlog(n/m+1)) comparaisons et en utilisant seulement une quantité constante constante d'espace supplémentaire. Le site résultat correspond à toutes les limites inférieures connues...

14voto

IVlad Points 20932

Ce n'est vraiment ni facile ni efficace, et je vous suggère de ne pas le faire à moins d'y être vraiment obligé (et vous n'y êtes probablement pas obligé à moins que ce soit un devoir à la maison puisque les applications de la fusion in situ sont surtout théoriques). Ne pouvez-vous pas utiliser quicksort à la place ? Quicksort sera plus rapide de toute façon avec quelques optimisations plus simples et sa mémoire supplémentaire est O(log N) .

Quoi qu'il en soit, si vous devez le faire, alors vous devez le faire. Voilà ce que j'ai trouvé : un y deux . Je ne suis pas familier avec le tri de fusion inplace, mais il semble que l'idée de base soit d'utiliser des rotations pour faciliter la fusion de deux tableaux sans utiliser de mémoire supplémentaire.

Notez que cela est encore plus lent que le tri classique par fusion qui n'est pas inplace.

15 votes

Quicksort n'est pas stable. C'est vraiment est important pour beaucoup de code de production.

9 votes

Quicksort peut être stable, et je crois que le tri par fusion n'est pas nécessairement stable s'il est en place.

0 votes

Bons liens. Pourquoi quelqu'un a-t-il dit que c'était difficile ?

11voto

Donal Fellows Points 56559

L'étape critique est d'obtenir le fusionner lui-même pour être en place. Ce n'est pas aussi difficile que le laissent entendre ces sources, mais on perd quelque chose en essayant.

En regardant une étape de la fusion :

[...liste- trié ...| x ...liste- A ...| y ...liste- B ...]

Nous savons que le trié la séquence est inférieure à tout le reste, que x est inférieur à tout ce qui se trouve dans A et que y est inférieur à tout ce qui se trouve dans B . Dans le cas où x est inférieur ou égal à y vous déplacez simplement votre pointeur au début de l'élément A sur un seul. Dans le cas où y est inférieur à x vous devez mélanger y passé l'ensemble de A à trié . Cette dernière étape est ce qui rend cette méthode coûteuse (sauf dans les cas dégénérés).

Il est généralement plus économique (surtout lorsque les tableaux ne contiennent que des mots par élément, par exemple un pointeur vers une chaîne de caractères ou une structure) d'échanger un peu d'espace contre du temps et d'avoir un tableau temporaire séparé entre lequel on fait des allers-retours.

6 votes

Votre fusion en lieu et place a une complexité de O(m*n) dans le pire des cas, où m est la taille A et n la taille B. C'est le cas lorsque le premier élément de A est plus grand que le dernier élément de B. La complexité peut être améliorée à O(k*log(k)+m+n), où k=min(m,n) en ajoutant un tas entre A et B. Ce tas devrait contenir des éléments de A, qui sont plus grands que les éléments restants de B, mais plus petits que les éléments restants de A. Si A est épuisé en premier, alors le tas doit être déplacé à la fin de B. Sinon, le tas doit être déplacé au début de A. Ensuite, les éléments du tas doivent être sortis en place et inversés pour terminer la fusion.

2 votes

@valyala Notez que lorsque vous utilisez un tas, le tri n'est plus stable. De plus, si vous utilisez un tas, vous pouvez opter pour heap sort au lieu de merge sort.

0 votes

Je veux juste noter que la fusion in-place est possible dans une complexité temporelle asymptotique optimale, cf. c++ - Est-il possible de faire une fusion in situ sans stockage temporaire ? - Stack Overflow

8voto

Thomas Mueller Points 18666

Juste à titre de référence, voici une belle implémentation d'un tri stable par fusion en place . Compliqué, mais pas trop mal.

J'ai fini par mettre en œuvre à la fois un triage stable par fusion en place et un tri rapide stable en place en Java. Veuillez noter que la complexité est O(n (log n)^2).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X