web-dev-qa-db-fra.com

Est-il plus rapide de trier une liste après avoir inséré des éléments ou les avoir ajoutés à une liste triée

Si j'ai une liste triée (par exemple, tri rapide pour trier), si j'ai beaucoup de valeurs à ajouter, est-il préférable de suspendre le tri et de les ajouter à la fin, puis de trier ou d'utiliser un découpage binaire pour placer les éléments correctement pendant que en les ajoutant. Cela fait-il une différence si les articles sont aléatoires ou déjà plus ou moins en ordre?

61
Steve

Si vous ajoutez suffisamment d'éléments pour créer efficacement la liste à partir de zéro, vous devriez pouvoir obtenir de meilleures performances en triant la liste par la suite.

Si les éléments sont généralement en ordre, vous pouvez modifier à la fois la mise à jour incrémentielle et le tri régulier pour en profiter, mais franchement, cela ne vaut généralement pas la peine. (Vous devez également faire attention à des choses comme vous assurer qu'un ordre inattendu ne peut pas faire prendre beaucoup de temps à votre algorithme plus long, q.v. quicksort naïf)

La mise à jour incrémentielle et le tri régulier de liste sont tous les deux O (N log N) mais vous pouvez obtenir un meilleur facteur constant en triant tout par la suite (je suppose ici que vous avez une structure de données auxiliaire afin que votre mise à jour incrémentielle puisse accéder aux éléments de liste plus rapidement que O (N) ...). De manière générale, le tri simultané offre beaucoup plus de liberté de conception que le maintien de la commande de manière incrémentielle, car la mise à jour incrémentielle doit maintenir une commande complète à tout moment, mais pas le tri groupé en une seule fois.

Si rien d'autre, n'oubliez pas qu'il existe de nombreux types de lots en vrac hautement optimisés.

32
comingstorm

Il est généralement préférable d'utiliser un tas . en bref, il répartit le coût du maintien de l'ordre entre le poussoir et le cueilleur. Les deux opérations sont O (log n), au lieu de O (n log n), comme la plupart des autres solutions.

20
Javier

Si vous ajoutez des groupes, vous pouvez utiliser un tri par fusion. Triez la liste des éléments à ajouter, puis copiez à partir des deux listes, en comparant les éléments pour déterminer celui qui sera copié ensuite. Vous pouvez même copier sur place si vous redimensionnez votre tableau de destination et travaillez de l'arrière vers l'arrière.

L'efficacité de cette solution est O (n + m) + O (m log m) où n est la taille de la liste d'origine et m est le nombre d'éléments insérés.

Edit: Comme cette réponse ne suscite aucun amour, je pensais que je l'étofferais avec un exemple de code C++. Je suppose que la liste triée est conservée dans une liste liée plutôt que dans un tableau. Cela change l'algorithme pour ressembler plus à une insertion qu'à une fusion, mais le principe est le même.

// Note that itemstoadd is modified as a side effect of this function
template<typename T>
void AddToSortedList(std::list<T> & sortedlist, std::vector<T> & itemstoadd)
{
    std::sort(itemstoadd.begin(), itemstoadd.end());
    std::list<T>::iterator listposition = sortedlist.begin();
    std::vector<T>::iterator nextnewitem = itemstoadd.begin();
    while ((listposition != sortedlist.end()) || (nextnewitem != itemstoadd.end()))
    {
        if ((listposition == sortedlist.end()) || (*nextnewitem < *listposition))
            sortedlist.insert(listposition, *nextnewitem++);
        else
            ++listposition;
    }
}
10
Mark Ransom

En principe, il est plus rapide de créer un arbre que de trier une liste. Les insertions d'arbre sont O(log(n)) pour chaque insert, conduisant à O global (n log (n)). Tri dans O (n log ( n)).

C'est pourquoi Java a TreeMap, (en plus des implémentations TreeSet, TreeList, ArrayList et LinkedList d'une liste.)

  • Un TreeSet garde les choses dans l'ordre de comparaison des objets. La clé est définie par l'interface Comparable.

  • Une LinkedList conserve les choses dans l'ordre d'insertion.

  • Un ArrayList utilise plus de mémoire, est plus rapide pour certaines opérations.

  • Un TreeMap, de même, supprime le besoin de trier par clé. La carte est construite dans l'ordre des touches lors des insertions et maintenue dans l'ordre trié à tout moment.

Cependant, pour une raison quelconque, l'implémentation Java de TreeSet est un peu plus lente que l'utilisation d'un ArrayList et d'un tri.

[Il est difficile de spéculer sur la raison pour laquelle cela serait considérablement plus lent, mais c'est le cas. Il devrait être légèrement plus rapide d'un passage dans les données. Ce genre de chose représente souvent le coût de la gestion de la mémoire surpassant l'analyse algorithmique.]

5
S.Lott

Je dirais, testons-le! :)

J'ai essayé avec quicksort, mais trier un tableau presque trié avec quicksort n'est ... enfin, pas vraiment une bonne idée. J'en ai essayé un modifié, coupant à 7 éléments et utilisant le tri par insertion pour cela. Pourtant, des performances horribles. Je suis passé au tri par fusion. Il peut nécessiter beaucoup de mémoire pour le tri (ce n'est pas en place), mais les performances sont bien meilleures sur les tableaux triés et presque identiques sur les tableaux aléatoires (le tri initial a pris presque le même temps pour les deux, le tri rapide n'était que légèrement plus rapide ).

Cela montre déjà une chose: la réponse à vos questions dépend fortement de l'algorithme de tri que vous utilisez. S'il aura de mauvaises performances sur des listes presque triées, l'insertion à la bonne position sera beaucoup plus rapide que l'ajout à la fin, puis le tri à nouveau; et le tri par fusion peut ne pas être une option pour vous, car il peut nécessiter beaucoup trop de mémoire externe si la liste est énorme. BTW J'ai utilisé une implémentation de tri par fusion personnalisée, qui n'utilise que la moitié du stockage externe pour l'implémentation naïve (qui nécessite autant de stockage externe que la taille du tableau lui-même).

Si le tri par fusion n'est pas une option et que le tri rapide n'est pas une option, la meilleure alternative est probablement le tri en tas.

Mes résultats sont les suivants: ajouter les nouveaux éléments simplement à la fin, puis trier à nouveau le tableau était plusieurs fois plus rapide que de les insérer dans la bonne position. Cependant, mon tableau initial avait 10 mio éléments (triés) et j'ajoutais un autre mio (non trié). Donc, si vous ajoutez 10 éléments à un tableau de 10 mio, les insérer correctement est beaucoup plus rapide que de tout trier à nouveau. La réponse à votre question dépend donc également de la taille du tableau initial (trié) et du nombre de nouveaux éléments que vous souhaitez y ajouter.

4
Mecki

C'est à peu près la même chose. L'insertion d'un élément dans une liste triée est O (log N), et cela pour chaque élément de la liste, N, (créant ainsi la liste) serait O (N log N) qui est la vitesse du tri rapide (ou du tri par fusion qui est plus proche de cette approche).

Si vous les insériez à la place, ce serait O (1), mais après un tri rapide, ce sera toujours O (N log N).

J'irais avec la première approche, car elle a le potentiel d'être légèrement plus rapide. Si la taille initiale de votre liste, N, est beaucoup plus grande que le nombre d'éléments à insérer, X, alors l'approche d'insertion est O (X log N). Le tri après insertion en tête de liste est O (N log N). Si N = 0 (IE: votre liste est initialement vide), la vitesse d'insertion dans l'ordre trié, ou le tri par la suite est la même.

1
bmdhacks

Si la liste est a) déjà triée et b) de nature dynamique, l'insertion dans une liste triée devrait toujours être plus rapide (trouver le bon endroit (O (n)) et insérer (O (1))).

Cependant, si la liste est statique, un mélange du reste de la liste doit se produire (O (n) pour trouver le bon endroit et O(n) pour faire glisser les choses vers le bas).

Quoi qu'il en soit, l'insertion dans une liste triée (ou quelque chose comme un arbre de recherche binaire) devrait être plus rapide.

O (n) + O(n) doit toujours être plus rapide que O (N log n).

1
warren

À un niveau élevé, c'est un problème assez simple, car vous pouvez considérer le tri comme une recherche itérative. Lorsque vous souhaitez insérer un élément dans un tableau, une liste ou une arborescence ordonné, vous devez rechercher le point auquel l'insérer. Ensuite, vous l'introduisez, à moindre coût, espérons-le. Vous pourriez donc penser à un algorithme de tri comme prenant simplement un tas de choses et, un par un, recherchant la position appropriée et les insérant. Ainsi, un tri par insertion (O (n * n)) est une recherche linéaire itérée (O (n)). Arbre, tas, fusion, radix et tri rapide (O (n * log (n))) peuvent être considérés comme une recherche binaire itérée (O (log (n))). Il est possible d'avoir un tri O(n), si la recherche sous-jacente est O(1) comme dans une table de hachage ordonnée. (Un exemple de c'est trier 52 cartes en les jetant dans 52 cases.)

Donc, la réponse à votre question est d'insérer les choses une par une, plutôt que de les enregistrer puis de les trier ne devrait pas faire beaucoup de différence, dans un sens big-O. Vous pouvez bien sûr avoir des facteurs constants à gérer, et ceux-ci peuvent être importants.

Bien sûr, si n est petit, comme 10, toute la discussion est idiote.

0
Mike Dunlavey

L'insertion d'un élément dans une liste triée prend O(n) temps, pas O(log n) temps. Vous devez trouver l'endroit où le mettre, en prenant O(log n) temps. Mais ensuite, vous devez déplacer tous les éléments - en prenant O(n) temps. Ainsi, l'insertion tout en conservant le tri est n ° O(n ^ 2), où tout en les insérant puis en triant est O(n log n).

Selon votre implémentation de tri, vous pouvez obtenir encore mieux que O(n log n) si le nombre d'insertions est beaucoup plus petit que la taille de la liste. Mais si tel est le cas, cela n'a aucune importance.

Faites donc l'insertion de tous et triez la solution si le nombre d'inserts est important, sinon cela n'aura probablement pas d'importance.

0
hazzen

Vous devez les ajouter avant, puis utiliser un tri radix qui devrait être optimal

http://en.wikipedia.org/wiki/Radix_sort#Efficiency

0
Peter Parker

S'il s'agit de .NET et que les éléments sont des entiers, il est plus rapide de les ajouter à un dictionnaire (ou si vous êtes sur .Net 3.0 ou supérieur, utilisez le HashSet si cela ne vous dérange pas de perdre des doublons) Cela vous donne un tri automatique.

Je pense que les cordes fonctionneraient également de la même manière. La beauté est que vous obtenez O(1) insertion et tri de cette façon.

0
Michael Brown

(Si la liste dont vous parlez est comme C # List<T>.) L'ajout de certaines valeurs aux bonnes positions dans une liste triée avec de nombreuses valeurs va nécessiter moins d'opérations. Mais si le nombre de valeurs ajoutées devient important, il en faudra plus.

Je suggérerais d'utiliser non pas une liste mais une structure de données plus appropriée dans votre cas. Comme un arbre binaire, par exemple. Une structure de données triée avec un temps d'insertion minimal.

0
Ihar Bury