J'essaie d'obtenir le top dire, 100 scores d'une liste de scores générés par mon programme. Malheureusement, la liste est énorme (de l'ordre de millions de milliards), le tri est donc une partie intensive de temps du programme.
Quelle est la meilleure façon de faire le tri pour obtenir les 100 meilleurs scores? p>
Les seules deux méthodes que je peux penser jusqu'à présent sont soit de la première génération de toutes les scores dans un réseau massif, puis de le trier et de prendre le top 100. ou de seconde, générant x nombre de scores, le triant et Tronquer les 100 meilleurs scores, puis continuez à générer plus de scores, en les ajoutant à la liste tronquée, puis à le régler à nouveau. P>
de toute façon je le fais, cela prend encore plus de temps que je le souhaiterais, des idées sur la façon de le faire de manière encore plus efficace? (Je n'ai jamais pris des cours de programmation avant, peut-être que ceux d'entre vous avec Comp Sci Degrees connaissent des algorithmes efficaces pour le faire, du moins c'est ce que j'espère). P>
Enfin, quel est l'algorithme de tri utilisé Par la fonction de tri standard () en C ++? p>
Merci, P>
-Faken P>
EDIT: Juste pour quiconque est curieux ... P>
J'ai fait quelques essais de temps sur les avant et après et voici les résultats: P>
Programme ancien (triant des préformes après chaque itération de boucle extérieure): P>
top 100 scores: 71 seconds <-- Very nice! top 10 scores: 52 seconds top 1 scores: 51 seconds Sorting disabled: 50 seconds
11 Réponses :
Vous voulez le plus grand nombre de x absolu, donc je suppose que vous ne voulez pas une sorte de heuristique. Quelle est la non traitée la liste? Si c'est assez aléatoire, votre meilleur pari est vraiment simplement de faire un tri rapide sur toute la liste et de saisir les résultats du top X. P>
Si vous pouvez filtrer des scores pendant la génération de la liste, c'est bien mieux. Seulement des valeurs X jamais stockez X et chaque fois que vous obtenez une nouvelle valeur, comparez-la à ces valeurs x. Si c'est moins que tous, lancez-le. Si c'est plus grand que l'un d'entre eux, jetez la nouvelle valeur la plus petite. p>
Si X est suffisamment petit, vous pouvez même conserver votre liste de valeurs X triés afin que vous comparez votre nouveau numéro à une liste triée de valeurs, vous pouvez créer un chèque O (1) pour voir si la nouvelle valeur est plus petite. que tout le reste et ainsi le jeter. Sinon, une recherche binaire rapide peut trouver où la nouvelle valeur se déroule dans la liste, puis vous pouvez jeter la première valeur de la matrice (en supposant que le premier élément est le plus petit élément). P>
Étant donné que vous devez regarder tous les éléments de la liste, cela ne serait-il pas plus rapide que d'itérer à travers la liste de maintenir une gamme de 100 plus grands et un pointeur sur le plus petit des 100 échanges supplémentaires lorsque vous rencontrez un plus grand nombre. ?
Oui, et cela nécessitera que la liste de 100 reste aussi triée.
Au fil du temps, la liste ressemblera à la plus grande valeur de plus en plus, plus souvent, vous constatez que l'insertion Trier immédiatement abandonne, constatant que la nouvelle valeur est inférieure à la plus petite valeur des candidats du Top 100. p>
+1 Pour noter qu'il n'est pas nécessaire de garder une trace de rien de plus que les 100 premiers éléments. J'aimerais que je puisse donner des points supplémentaires pour suggérer un tri d'insertion aussi.
Nice, j'aime la beauté de ça, simpliste et efficace!
Le cas dégénéré est lorsque votre liste d'origine est en ordre de tri inversé. Cela prendra 100 fois plus long que le cas moyen, mais sera toujours O (n).
En réalité, vous pouvez trouver le point d'insertion à J in O (logn) temps et déplacer des éléments de J + 1 pour compter-1 dans une position, laissant ainsi le plus petit au comte-1. Et vous ne pouvez le faire que si le nouvel élément est supérieur à l'élément au Count-1. Mais si vous allez faire cela, vous pouvez aussi bien utiliser un tas, car Jack Lloyd recommande.
L'utilisation d'un pool fixe de godets de liste liés ici pourrait éventuellement aider le temps de vélo (non algorithmique); Mais cela passe micro et nécessite une analyse comparative. Bonne réponse et commentaires.
@HUGHDBROWN mais à 100 éléments, cela pourrait ne pas atteindre le C dans le O pour le faire valoir la peine.
@pst: désolé. Pas de bague de décodeur. Je ne sais pas ce que vos C et O sont. En supposant que vous commenciez à commenter la recherche binaire, ce sera mieux qu'un type d'insertion. Si vous commenciez à déplacer les éléments vers le bas, vous pouvez obtenir tout LINQ-Y et faire ceci: top100 = top100.take (j) .concat (nouveau int [] {newelement}). Concat (top100.skip (J-1) .take (taille-j-1)). Toarra y (); code> bien sûr, comme vous pouvez le constater par mes commentaires ci-dessus et ma réponse ci-dessous, je préfère utiliser une file d'attente de tas / prioritaires insérer dans des matrices triées.
@HUGHDBROWN: Je me demande si la recherche binaire fera de manière significative meilleure. Si vous TOUJOURS i> Faites une recherche binaire (non spéciale lorsque le nouvel élément est trop petit), vous ferez généralement 7 comparaisons par nouvel élément, tandis que le tri d'insertion fera 1 comparaison. De plus, lorsqu'il insère un nouvel élément, vous devez déplacer des éléments K par un indice dans les deux cas. Si les scores sont des INTS (par exemple), les déplacez-les peuvent être effectivement plus chers que de les comparer.
@Martin c. Lowis: "Si vous faites toujours une recherche binaire ..." Mais ce n'est pas ce que je suggère. J'ai dit: "Et vous ne pouviez pas le faire que si le nouvel élément est supérieur à l'élément au comte-1." C'est exactement le cas que vous appelez. Et si les deux méthodes doivent déplacer le même nombre d'INT de la même distance, alors pourquoi est-il préférable de faire une sorte d'insertion lorsque "les déplacer peut-il être plus coûteux que les comparer"? La recherche binaire fait probablement 7 comparaisons VS 50 pour le tri d'insertion et le nombre d'INT déplacés est identique. Mais, la solution de Jack Lloyd est meilleure algorithmique.
@HUGHDBROWOWIN: Je n'ai pas dit que l'insertion Trier est meilleur - je n'ai prétendu que la recherche binaire ne sera pas significativement meilleure (mais peut-être légèrement). Je crois aussi qu'il y aura beaucoup moins de 50 comparaisons pour le tri d'insertion en moyenne, car de nouveaux scores seront probablement petits comparés aux scores que vous avez déjà.
++ beau travail, principalement votre observation que la plupart du temps, le tri de l'insertion s'arrête immédiatement. En ce qui concerne la vitesse d'insertion, la recherche binaire peut être plus rapide que linéaire, mais elles sont à la fois O (1) car n est délimitée par 100.
Placez les données dans une structure d'arborescence équilibrée (probablement arbre rouge-noir) qui fait le tri en place. Les insertions devraient être O (LG N). Grabbing Les scores x les plus élevés doivent également être O (LG N). P>
Vous pouvez pruneaux l'arbre de temps en temps si vous trouvez des optimisations à un moment donné. P>
J'ai mentionné que je n'ai pas pris de cours de programmation, désolée, vous êtes allé au-dessus de ma tête ....
Si vous avez une sorte de bibliothèque qui vous permet de trier un tableau ou une liste, la bibliothèque a probablement probablement quelque chose comme un Treemap qui fera le tour.
Si vous n'avez besoin que de signaler la valeur des 100 meilleurs scores (et non de données associées), et si vous savez que les scores seront tous dans une plage finie telle que [0.100], puis un moyen facile de le faire. est avec "Tri de comptage" ...
Fondamentalement, créez une matrice représentant toutes les valeurs possibles (par exemple, une matrice de taille 101 si les scores peuvent aller de 0 à 100 inclus) et initialiser tous les éléments du tableau avec une valeur de 0. Ensuite, itérer dans la liste des scores, incrémentation de l'entrée correspondante dans la liste des scores obtenus. Autrement dit, compilez le nombre de fois que chaque score dans la gamme a été atteint. Ensuite, travaillant à partir de la fin du tableau au début de la matrice, vous pouvez choisir le score X TOP X. Voici un pseudo-code: P>
let type Score be an integer ranging from 0 to 100, inclusive. let scores be an array of Score objects let scorerange be an array of integers of size 101. for i in [0,100] set scorerange[i] = 0 for each score in scores set scorerange[score] = scorerange[score] + 1 let top be the number of top scores to report let idx be an integer initialized to the end of scorerange (i.e. 100) while (top > 0) and (idx>=0): if scorerange[idx] > 0: report "There are " scorerange[idx] " scores with value " idx top = top - scorerange[idx] idx = idx - 1;
Déclarez un tableau où vous pouvez mettre les 100 meilleurs scores. Boucle à travers la liste énorme et recherchez chaque élément si elle est admissible à être insérée dans le top 100. Utilisez un type d'insertion simple pour ajouter un élément à la liste supérieure.
Quelque chose comme ça (C # Code, mais vous obtenez l'idée ): P>
Score[] toplist = new Score[100]; int size = 0; foreach (Score score in hugeList) { int pos = size; while (pos > 0 && toplist[pos - 1] < score) { pos--; if (pos < 99) toplist[pos + 1] = toplist[pos]; } if (size < 100) size++; if (pos < size) toplist[pos] = score; }
Hmm, donc générer la totalité du tableau de points d'abord avant que je fasse la trieuse d'insertion ... Je suppose que je devrais regarder la méthode produirait la méthode de la plupart des cache avant de la mettre en œuvre. Merci.
@Faken: Je ne sais pas si cela a quelque chose à voir avec le cache Hits, mais apparemment, ce code est 700 fois plus rapide que le code de Jack Lloyd à l'aide d'un tas ...
Vous pouvez le faire dans O (n) temps, sans aucun tri, à l'aide d'un tas: fois sur ma machine (Core2 Q6600, Linux, Python 2.6, mesuré avec Bash < code> temps code> intégré): p> Edit / Addition: En C ++, vous pouvez utiliser
STD :: Priority_Queue Code> de la même manière que le module code> HePQ code> de Python est utilisé ici. Vous voudrez utiliser la commande
std :: plus grand code> à la place du fichier
std :: moins code>, de sorte que la fonction
top () code> fonction de membre retourne le plus petit élément au lieu du plus grand. La file d'attente prioritaire de C ++ n'a pas l'équivalent de
HealePlace code>, qui remplace l'élément supérieur avec un nouveau, vous souhaitez donc
POP CODE> L'élément supérieur (le plus petit) et ensuite
pousser code> la valeur nouvellement vue. Autre que l'algorithme se traduit assez proprement de Python à C ++. P> P>
@trager Pour toute constante X, disons 100, les opérations de tas peuvent être traitées comme une durée constante, car elles sont journalisées (x) ou x * log (x); Avec x constante, ceux-ci sont traités asymptotiquement comme O (1). Et ce n'est pas une méthode de tri, vraiment, sauf si vous définissez X = N, auquel cas, bien sûr, X n'est pas une constante.
@Lloyd, oui, je me suis rendu compte que. = X
Vous pouvez le faire dans HASKELLL comme ceci:
largest100 = take 100 . sortBy (flip compare)
J'ai répondu à cette question en réponse à une question d'entretien en 2008. J'ai mis en place un File d'attente prioritaire template en C # .
using System; using System.Collections.Generic; using System.Text; namespace CompanyTest { // Based on pre-generics C# implementation at // http://www.boyet.com/Articles/WritingapriorityqueueinC.html // and wikipedia article // http://en.wikipedia.org/wiki/Binary_heap class PriorityQueue<T> { struct Pair { T val; int priority; public Pair(T v, int p) { this.val = v; this.priority = p; } public T Val { get { return this.val; } } public int Priority { get { return this.priority; } } } #region Private members private System.Collections.Generic.List<Pair> array = new System.Collections.Generic.List<Pair>(); #endregion #region Constructor public PriorityQueue() { } #endregion #region Public methods public void Enqueue(T val, int priority) { Pair p = new Pair(val, priority); array.Add(p); bubbleUp(array.Count - 1); } public T Dequeue() { if (array.Count <= 0) throw new System.InvalidOperationException("Queue is empty"); else { Pair result = array[0]; array[0] = array[array.Count - 1]; array.RemoveAt(array.Count - 1); if (array.Count > 0) trickleDown(0); return result.Val; } } #endregion #region Private methods private static int ParentOf(int index) { return (index - 1) / 2; } private static int LeftChildOf(int index) { return (index * 2) + 1; } private static bool ParentIsLowerPriority(Pair parent, Pair item) { return (parent.Priority < item.Priority); } // Move high priority items from bottom up the heap private void bubbleUp(int index) { Pair item = array[index]; int parent = ParentOf(index); while ((index > 0) && ParentIsLowerPriority(array[parent], item)) { // Parent is lower priority -- move it down array[index] = array[parent]; index = parent; parent = ParentOf(index); } // Write the item once in its correct place array[index] = item; } // Push low priority items from the top of the down private void trickleDown(int index) { Pair item = array[index]; int child = LeftChildOf(index); while (child < array.Count) { bool rightChildExists = ((child + 1) < array.Count); if (rightChildExists) { bool rightChildIsHigherPriority = (array[child].Priority < array[child + 1].Priority); if (rightChildIsHigherPriority) child++; } // array[child] points at higher priority sibling -- move it up array[index] = array[child]; index = child; child = LeftChildOf(index); } // Put the former root in its correct place array[index] = item; bubbleUp(index); } #endregion } }
Voici le moyen "naturel" C ++ de faire ceci:
std::vector<Score> v; // fill in v std::partial_sort(v.begin(), v.begin() + 100, v.end(), std::greater<Score>()); std::sort(v.begin(), v.begin() + 100);
Ouais, je voulais juste répondre de cette manière!
Etant donné que la vitesse est de l'essence ici, et 40 000 valeurs de hautcore possibles sont totalement maintenues par l'un des ordinateurs d'aujourd'hui, je serais recourir à Seau Seau pour la simplicité. Je suppose que cela serait Donc, supposons que votre valeur maximale hautcore est 40.000: p>
Faites un tableau de 40.000 entrées. Boucle à travers vos valeurs de haut-agrêtes. Chaque fois que vous rencontrez des highscore X, augmentez votre réseau [x] par un. Après cela, tout ce que vous avez à faire est de compter les meilleures entrées de votre matrice jusqu'à ce que vous ayez atteint 100 hauts-types comptés. P>
Eh bien, un tricateur de seau travaillerait à trouver mes 100 meilleurs scores, mais cela ne me donnerait que les meilleurs scores. Je suppose que c'était de ma faute, je n'ai pas défini le problème comme étant exactement que je devrais avoir. Chaque score est dérivé de 3 valeurs, chacune de ces scores doit avoir ces 3 valeurs étiquetées avec le score, ainsi qu'un type de godet ne conviendrait pas à mes besoins. Mais votre droite, cette méthode surperfondirait de manière défiant les autres méthodes si la plage était petite et je n'ai pas triché de cours.
Hmm ... à la deuxième pensée, cela peut fonctionner si j'ai mis en place une sorte de liste attachée à chacun des godets pour stocker d'autres données ... mais cela serait extrêmement intensif de mémoire à moins que je ne mettais un point de coupure quelque part, mais même alors alors , je ne pourrais pas deviner une gamme élevée sans itération sur l'ensemble du jeu de données.
Mais encore une fois, je pouvais toujours mettre en œuvre une coupure après chaque mot, itération de la boucle extérieure qui vérifie où mes 100 meilleurs scores sont et une instruction IF pour vérifier si le score suivant était dans cette valeur de score élevée ... Cela pourrait réellement fonctionner pour être même plus efficace! Le seul inconvénient serait une utilisation de la mémoire, la meilleure réponse actuelle utilise uniquement un maximum de 400 kb de mémoire de mémoire ... mais encore une fois, avec 8 Go de RAM, ce qui est de quelques centaines de MB? (Euh, eh bien, je suppose que le cache aurait beaucoup à voir avec cela ... Le programme antérieur s'asseoirait très bien dans le cache L2 entièrement). De toute façon, c'est intéressant ...
Pas vraiment. N'oubliez pas que dans votre tableau, vous ne stockerez que les pointeurs sur toutes les structures que vous avez choisies d'utiliser pour encapsuler vos données. Dans ce nouveau scénario, vous aurez une gamme de pointeurs dans chacun de vos 40.000 emplacements potentiels. Ainsi, vous aurez de la place pour 40.000 pointeurs 32 bits au lieu de 40.000 entiers 32 bits. En ce qui concerne les données elles-mêmes, il devrait être stocké de toute façon, donc aucune mémoire redondante n'est passée là-bas. Vous pouvez également implémenter une fonction de soupape de sécurité qui redimensionnera votre tableau 40.000 par I.E. 10.000 S'il y a des valeurs supérieures à la valeur estimée la plus élevée.
+1 de moi. Bucketsorts (et ses cousins) sont sous-utilisés. Bien que dans la pratique si STD :: partiel_sort est "assez rapide", j'irais avec ça juste parce que c'est plus simple.
Juste curieux, quelle est la gamme des nombres que vous produisez? Semble que la prise du top 100 d'une liste d'un milliard de chiffres aurait beaucoup de valeurs répétées au sommet, à moins que vos scores ne soient en eux-mêmes de très grands nombres.
Je n'étais pas au courant, il y a un type standard (). Quelle bibliothèque utilisez-vous? C'est probablement un tri rapide.
Ma plage de chiffres est variable, j'ai quelques scores de pondération que je peux me contenter de changer les gammes. Pour l'instant, c'est entre 3000 et environ 40000. Le type de numéro est INT afin que je puisse utiliser toute la gamme. La bibliothèque standard en utilisant est l'algorithme>.
Vous avez donc un ensemble possible de 37 000 scores. Si vous avez un milliard de scores, en supposant une distribution normale, votre ensemble complet serait le même score. 37 000 correspondent à un milliard de plus que 27 000 fois
Yup, mais la chose est que les scores ne sont pas uniformément répartis mais plutôt distribués normaux (courbe de cloche). Je cherche les meilleurs scores, il n'y a donc pas beaucoup de doublons.
Comment ces scores sont-ils générés? Sont-ils stockés, et si oui, comment? S'ils sont stockés dans une base de données, je voudrais simplement écrire une requête pour renvoyer le top 100 et supposer que la requête Optimizer le fera aussi bien que vous pouvez le faire à la main (et la complexité du code / le temps de développement / les bugs être beaucoup moins)
Les valeurs sont générées comme mon programme itère sur un ensemble de données. Ils peuvent être stockés, mais je n'ai besoin d'aucune autre chose que les 100 premiers chiffres.