web-dev-qa-db-fra.com

pourquoi le tri par fusion est-il préféré au tri rapide pour trier les listes liées

J'ai lu ce qui suit dans un forum:

Le tri par fusion est très efficace pour les infrastructures de données immuables comme les listes chaînées

et

Le tri rapide est généralement plus rapide que le tri par fusion lorsque les données sont stockées en mémoire. Cependant, lorsque l'ensemble de données est énorme et stocké sur des périphériques externes tels qu'un disque dur, le tri par fusion est le gagnant incontestable en termes de vitesse. Il minimise les lectures coûteuses du lecteur externe

et

lors de l'utilisation sur des listes liées, le tri par fusion ne nécessite qu'une petite quantité constante de stockage auxiliaire

Quelqu'un peut-il m'aider à comprendre l'argument ci-dessus? pourquoi le tri par fusion est-il préféré pour trier d'énormes listes liées? et comment minimise-t-il les lectures coûteuses sur un disque externe? En gros, je veux comprendre pourquoi on choisirait le tri par fusion pour trier une grande liste chaînée.

60
maxpayne

Le tri rapide fonctionne bien pour le tri sur place. En particulier, la plupart des opérations peuvent être définies en termes d'échange de paires d'éléments dans un tableau. Pour ce faire, cependant, vous "parcourez" normalement le tableau avec deux pointeurs (ou index, etc.). L'un commence au début du tableau et l'autre à la fin. Les deux se dirigent ensuite vers le milieu (et vous avez terminé une étape de partition particulière lorsqu'ils se rencontrent). Cela coûte cher avec les fichiers, car les fichiers sont principalement orientés vers la lecture dans une direction, du début à la fin. Commencer par la fin et chercher en arrière est généralement relativement coûteux.

Au moins dans sa plus simple incarnation, le tri par fusion est à peu près le contraire. Le moyen facile de l'implémenter ne nécessite que de parcourir les données dans une direction, mais implique de diviser les données en deux parties distinctes, de les trier, puis de les fusionner à nouveau.

Avec une liste chaînée, il est facile de prendre (par exemple) des éléments alternés dans une liste chaînée, et de manipuler les liens pour créer deux listes chaînées à partir de ces mêmes éléments. Avec un tableau, réorganiser les éléments afin que les éléments alternatifs entrent dans des tableaux séparés est facile si vous êtes prêt à créer une copie aussi grande que les données d'origine, mais sinon plutôt plus simple.

De même, la fusion avec des tableaux est facile si vous fusionnez des éléments des tableaux source dans un nouveau tableau avec les données dans l'ordre - mais le faire en place sans créer une toute nouvelle copie des données est une toute autre histoire. Avec une liste liée, la fusion d'éléments entre deux listes sources en une seule liste cible est triviale - encore une fois, vous manipulez simplement les liens, sans copier les éléments.

Quant à l'utilisation de Quicksort pour produire les exécutions triées pour un tri de fusion externe, cela fonctionne, mais c'est (décidément) sous-optimal en règle générale. Pour optimiser un tri par fusion, vous souhaitez normalement maximiser les longueurs de chaque "exécution" triée lorsque vous le produisez. Si vous lisez simplement les données qui tiendront en mémoire, triez-les rapidement et écrivez-les, chaque exécution sera limitée à (un peu moins que) la taille de la mémoire disponible.

En règle générale, vous pouvez faire un peu mieux que cela. Vous commencez par lire un bloc de données, mais au lieu d'utiliser un Quicksort dessus, vous créez un tas. Ensuite, lorsque vous écrivez chaque élément du tas dans le fichier "run" trié, vous lisez n autre élément dans votre fichier d'entrée. S'il est plus grand que l'élément que vous venez d'écrire sur le disque, insérez-le dans votre segment de mémoire existant et recommencez.

Les éléments plus petits (c'est-à-dire qui appartiennent avant les éléments qui ont déjà été écrits) sont conservés séparément et intégrés dans un deuxième tas. Lorsque (et uniquement lorsque) votre premier segment est vide et que le second segment a pris toute la mémoire, vous quittez l'écriture d'éléments dans le fichier "run" existant et commencez sur un nouveau.

L'efficacité exacte de cette opération dépend de l'ordre initial des données. Dans le pire des cas (entrée triée dans l'ordre inverse), cela ne sert à rien du tout. Dans le meilleur des cas (entrée déjà triée), il vous permet de "trier" les données en une seule fois via l'entrée. Dans un cas moyen (entrée dans un ordre aléatoire), il vous permet de doubler environ la longueur de chaque série triée, ce qui améliorera généralement la vitesse de environ 20-25% (bien que le pourcentage varie en fonction de la taille vos données sont supérieures à la mémoire disponible).

48
Jerry Coffin

Quicksort dépend de la possibilité d'indexer dans un tableau ou une structure similaire. Lorsque c'est possible, il est difficile de battre Quicksort.

Mais vous ne pouvez pas indexer directement dans une liste chaînée très rapidement. Autrement dit, si myList est une liste liée, alors myList[x], s'il était possible d'écrire une telle syntaxe, impliquerait de commencer en tête de liste et de suivre les premiers liens x. Cela devrait être fait deux fois pour chaque comparaison que Quicksort fait, et cela coûterait cher très vite.

Même chose sur le disque: Quicksort devrait rechercher et lire chaque élément qu'il souhaite comparer.

Le tri par fusion est plus rapide dans ces situations car il lit les éléments de manière séquentielle, ce qui permet généralement à log2 (N) de passer sur les données. Il y a beaucoup moins d'E/S impliquées et beaucoup moins de temps passé à suivre les liens dans une liste de liens.

Quicksort est rapide lorsque les données tiennent dans la mémoire et peuvent être adressées directement. Mergesort est plus rapide lorsque les données ne tiennent pas en mémoire ou lorsqu'il est coûteux d'accéder à un élément.

Notez que les tris de fichiers volumineux chargent généralement autant que possible d'un fichier en mémoire, triez-le rapidement et écrivez-le dans un fichier temporaire, et répétez jusqu'à ce qu'il ait parcouru l'intégralité du fichier. À ce stade, il existe un certain nombre de blocs, chacun étant trié, et le programme effectue ensuite une fusion à N pour produire la sortie triée.

20
Jim Mischel

Un tri rapide déplace les enregistrements au milieu de la liste. Pour déplacer un élément vers l'index X, il doit commencer à 0 et itérer un enregistrement à la fois.

Un mergesort divise la liste en plusieurs petites listes et ne compare que la tête des éléments des listes.

La configuration d'un tri par fusion coûte généralement plus cher que l'itération requise par un tri rapide. Cependant, lorsqu'une liste est suffisamment grande ou que les lectures sont coûteuses (comme sur un disque), le temps nécessaire au tri rapide pour itérer devient un facteur majeur.

3
cadrell0