2009-10-21 3 views
9

Ich versuche, die oberste sagen, 100 Punkte aus einer Liste von Punkten, die von meinem Programm generiert werden. Leider ist die Liste riesig (in der Größenordnung von Millionen bis Milliarden), so dass das Sortieren ein zeitintensiver Teil des Programms ist.Schnellster Weg, um die größten X-Nummern aus einer sehr großen unsortierten Liste zu erhalten?

Was ist der beste Weg, um die Top 100 Punkte zu sortieren? Die einzigen beiden Methoden, die ich mir vorstellen kann, sind entweder zuerst alle Partituren zu einem riesigen Array zu generieren und dann zu sortieren und die Top 100 zu nehmen. Oder zweitens X Nummer der Partitur zu generieren, sie zu sortieren und die Spitze abzuschneiden 100 Punkte setzen dann fort, mehr Punkte zu erzeugen, sie der gekürzten Liste hinzuzufügen und sie dann wieder zu sortieren.

Wie auch immer ich es tue, es dauert noch mehr Zeit, als ich möchte, irgendwelche Ideen, wie es auf eine noch effizientere Art und Weise zu tun? (Ich habe noch nie zuvor Programmierkurse belegt, vielleicht wissen diejenigen von Ihnen, die einen Doktortitel haben, über effiziente Algorithmen, um das zu tun, zumindest hoffe ich darauf).

Schließlich, was ist der Sortieralgorithmus von der Standardfunktion sort() in C++?

Danke,

-Faken

Edit: Nur für jeden, der neugierig ist ...

ich einige Zeit Studien über die zuvor und nach und hier sind die Ergebnisse:

altes Programm (Preforms nach jeder Außenschleifeniteration Sortieren):

top 100 scores: 147 seconds 
top 10 scores: 147 seconds 
top 1 scores: 146 seconds 
Sorting disabled: 55 seconds 

neues Programm (Tracking von nur Spitzenwert der Implementierung und Verwendung der Standardsortierfunktion):

top 100 scores: 350 seconds <-- hmm...worse than before 
top 10 scores: 103 seconds 
top 1 scores: 69 seconds 
Sorting disabled: 51 seconds 

neue Rewrite (Optimierungen in Daten gespeichert, handgeschriebener Sortier-Algorithmus):

top 100 scores: 71 seconds <-- Very nice! 
top 10 scores: 52 seconds 
top 1 scores: 51 seconds 
Sorting disabled: 50 seconds 

auf einem Kern Fertig 2 , 1,6 GHz ... Ich kann nicht warten, bis mein Kern i7 860 ankommt ...

Es gibt eine Menge anderer, noch aggressiverer Optimierungen für mich (hauptsächlich im Bereich der Reduzierung der Iterationen) run), aber wie es jetzt steht, ist die Geschwindigkeit mehr als g Oh genug, ich könnte mich nicht einmal darum kümmern, diese Algorithmus-Optimierungen zu erarbeiten.

Danke an eveyrone für ihre Eingabe!

+0

Nur neugierig, was ist der Bereich der Zahlen, die Sie produzieren? Scheint, dass die Top 100 aus einer Liste von einer Milliarde Zahlen an der Spitze viele wiederholte Werte haben würde, es sei denn, Ihre Werte sind an sich sehr große Zahlen. –

+0

Mir war nicht bewusst, dass es eine Standardsortierung() gibt. Welche Bibliothek benutzt du? Es ist wahrscheinlich eine schnelle Art. –

+0

Mein Zahlenbereich ist variabel, ich habe einige Gewichtungswerte, die ich anpassen kann, um die Bereiche zu ändern. Für jetzt ist es zwischen 3000 bis etwa 40000. Der Nummerntyp ist Int, so dass ich den vollen Bereich verwenden kann. Die Standardbibliothek, die verwendet wird, ist die . – Faken

Antwort

25
  1. nehmen Sie die ersten 100 Bewertungen, und sortieren Sie sie in einem Array.
  2. die nächste Kerbe, und einschub sortiert sie in die Anordnung (bei dem „kleinen“ Ende ausgehend)
  3. den 101st Wert fallen
  4. mit dem nächsten Wert weiter, bei 2 bis
  5. getan

Im Laufe der Zeit wird die Liste mehr und mehr dem 100 größten Wert ähneln. Daher finden Sie öfter, dass die Einfügesortierung sofort abgebrochen wird. Der neue Wert ist kleiner als der kleinste Wert der Kandidaten für die obersten 100.

+0

+1 für die Feststellung, dass es nicht notwendig ist, mehr als die Top 100 Elemente zu verfolgen. Ich wünschte, ich könnte zusätzliche Punkte geben, um auch die Insertion zu empfehlen. –

+0

Schön, ich liebe die Schönheit, einfach und effizient! – Faken

+0

Der entartete Fall ist, wenn Ihre ursprüngliche Liste in umgekehrter Reihenfolge ist. Das dauert 100-mal länger als der durchschnittliche Fall, wird aber immer noch O (n) sein. –

0

Sie wollen die absolut größten X-Nummern, also ich vermute, Sie wollen keine Art von Heuristik. Wie unsortiert ist die Liste? Wenn es ziemlich zufällig ist, ist deine beste Wette wirklich, nur eine schnelle Sortierung auf der ganzen Liste zu machen und die besten X-Ergebnisse zu holen.

Wenn Sie Ergebnisse während der Listengenerierung filtern können, ist das viel besser. Speichern Sie nur X-Werte und vergleichen Sie sie jedes Mal, wenn Sie einen neuen Wert erhalten, mit diesen X-Werten. Wenn es weniger als alle von ihnen ist, wirf es weg. Wenn es größer als einer von ihnen ist, werfen Sie den neuen kleinsten Wert aus.

Wenn X klein genug ist, können Sie Ihre Liste der X-Werte sogar so sortieren, dass Sie Ihre neue Zahl mit einer sortierten Liste von Werten vergleichen. Sie können dann eine O (1) -Prüfung durchführen, um zu sehen, ob der neue Wert vorhanden ist kleiner als der Rest und schmeißt es raus. Andernfalls kann eine schnelle binäre Suche finden, wohin der neue Wert in der Liste geht, und dann können Sie den ersten Wert des Arrays wegwerfen (unter der Annahme, dass das erste Element das kleinste Element ist).

+0

Vorausgesetzt, dass Sie jedes Element in der Liste betrachten müssen, wäre es nicht schneller, nur durch die Liste zu iterieren, wobei ein Array der größten 100 bisher und ein Zeiger auf den kleinsten der 100 ausgetauschten erhalten bleibt Nummer? –

+0

Ja, und das erfordert, dass die Liste der 100 auch sortiert bleibt. – AlbertoPL

0

Platzieren Sie die Daten in einer ausgewogenen Baumstruktur (wahrscheinlich Red-Bla ck Baum), der die Sortierung an Ort und Stelle tut. Insertionen sollten O (lg n) sein. Das Ergreifen der höchsten x-Werte sollte ebenfalls O (lg n) sein.

Sie können den Baum von Zeit zu Zeit beschneiden, wenn Sie feststellen, dass Sie irgendwann Optimierungen benötigen.

+0

Ich habe erwähnt, dass ich keine Programmierkurse belegt habe, sorry, dass du mir über den Kopf gegangen bist .... – Faken

+0

Wenn du irgendeine Art von Bibliothek hast, die ein Array oder eine Liste sortiert, hat die Bibliothek wahrscheinlich auch so etwas wie eine TreeMap das wird den Trick machen. –

0

Wenn Sie nur den Wert der Top 100 Scores (und keine damit verbundenen Daten) melden müssen, und wenn Sie wissen, dass die Scores alle in einem endlichen Bereich wie [0,100] liegen, dann ist dies ein einfacher Weg es ist mit "zählende Sortierung" ...

Grundsätzlich erstellen Sie ein Array, das alle möglichen Werte darstellt (zB ein Array der Größe 101, wenn die Werte zwischen 0 und 100 liegen können), und initialisieren Sie alle Elemente des Arrays mit ein Wert von 0. Dann iteriere durch die Liste der Punkte und inkrementiere den entsprechenden Eintrag in der Liste der erreichten Punkte. Das heißt, kompilieren Sie die Anzahl der Male, die jedes Ergebnis in dem Bereich erreicht worden ist. Wenn Sie dann vom Ende des Arrays bis zum Anfang des Arrays arbeiten, können Sie den oberen X-Score auswählen. Hier ist ein Pseudo-Code:

 
    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; 
3

Deklarieren Sie ein Array, wo Sie die 100 besten Ergebnisse setzen können. Durchlaufen Sie die riesige Liste und prüfen Sie, ob jedes Element in die Top 100 eingefügt werden kann. Verwenden Sie eine einfache Einfügesortierung, um ein Element zur obersten Liste hinzuzufügen.

So etwas wie diese (C# -Code, aber Sie erhalten die Idee):

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

ich es auf meinem Computer getestet (Code 2 Duo 2,54 MHz Win 7 x64) und ich kann in 100.000.000 Artikel bearbeiten 369 ms.

+0

Hmm, also erzeuge zuerst das gesamte Score-Array, bevor ich die Insertion sortiere ... Ich denke, ich muss mir überlegen, welche Methode die meisten Cache-Hits erzeugen würde, bevor ich sie implementiere. Vielen Dank. – Faken

+0

@Faken: Ich weiß nicht, ob es etwas mit Cache-Hits zu tun hat, aber anscheinend ist dieser Code 700 mal schneller als Jack Lloyds Code mit einem Haufen ... – Guffa

7

Sie können dies in O (n) Zeit, ohne Sortierung, mit einem Haufen:

#!/usr/bin/python 

import heapq 

def top_n(l, n): 
    top_n = [] 

    smallest = None 

    for elem in l: 
     if len(top_n) < n: 
      top_n.append(elem) 
      if len(top_n) == n: 
       heapq.heapify(top_n) 
       smallest = heapq.nsmallest(1, top_n)[0] 
     else: 
      if elem > smallest: 
       heapq.heapreplace(top_n, elem) 
       smallest = heapq.nsmallest(1, top_n)[0] 

    return sorted(top_n) 


def random_ints(n): 
    import random 
    for i in range(0, n): 
     yield random.randint(0, 10000) 

print top_n(random_ints(1000000), 100) 

Zeiten auf meinem Rechner (Core2 Q6600, Linux, Python 2.6, gemessen mit bash time builtin):

  • 100000 Elemente: 0,29 Sekunden
  • 1000000 Elemente: 2,8 Sekunden
  • 10000000 Elemente: 25.
  • 2 Sekunden

Bearbeiten/Zusatz: In C++ können Sie std::priority_queue in der gleichen Weise verwenden, wie Python heapq Modul hier verwendet wird. Sie möchten die std::greater Sortierung anstelle der Standard std::less verwenden, so dass die top() Elementfunktion das kleinste Element anstelle der größten Element zurückgibt. Die C++ - Prioritätswarteschlange hat nicht den Gegenwert heapreplace, der das oberste Element durch ein neues Element ersetzt. Stattdessen möchten Sie pop das oberste (kleinste) Element und dann push den neu gesehenen Wert angeben. Ansonsten übersetzt der Algorithmus ziemlich sauber von Python nach C++.

+1

@strager Für jede Konstante X, sagen 100, der Haufen Operationen können als konstante Zeit behandelt werden, da sie log (X) oder X * log (X) sind; mit X-Konstante werden diese asymptotisch als O (1) behandelt. Und das ist wirklich keine Sortiermethode, es sei denn, Sie setzen X = N, in diesem Fall ist natürlich X keine Konstante. –

+0

@Lloyd, Ja, das habe ich gemerkt. = X – strager

1

Sie können wie folgt es in Haskell tun:

largest100 xs = take 100 $ sortBy (flip compare) xs 

Das sieht aus wie es alle Zahlen sortiert in absteigender Reihenfolge (die „Flip vergleichen“ Bit kehrt die Argumente der Standardvergleichsfunktion), und dann kehrt die ersten 100 Einträge aus der Liste. Aber Haskell wird langsam ausgewertet, so dass die sortBy-Funktion gerade genug sortiert, um die ersten 100 Zahlen in der Liste zu finden, und dann stoppt.

Puristen werden feststellen, dass Sie auch die Funktion schreiben konnte als

largest100 = take 100 . sortBy (flip compare) 

Dies ist nur das Gleiche bedeutet, sondern zeigt die Haskell-Stil eine neue Funktion aus den Bausteinen anderer Funktionen anstatt Gabe Komponieren Variablen um den Ort herum.

0

Ich beantwortete diese Frage als Antwort auf eine Interviewfrage im Jahr 2008. Ich implementierte eine templatized priority queue in 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 
    } 
} 
4

Hier ist die 'natürliche' C++ Art und Weise, dies zu tun:

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); 

Dies ist linear in der Anzahl der Partituren.

Der von std :: sort verwendete Algorithmus ist nicht durch den Standard spezifiziert, aber libstdC++ (von g ++ verwendet) verwendet einen "adaptiven Introsort", der im Wesentlichen ein Median-von-3-Quicksort bis zu einem bestimmten Level ist, gefolgt von einer Einfügesortierung.

+0

ja, wollte nur so antworten! – f0b0s

3

Da Geschwindigkeit ist hier von wesentlicher Bedeutung, und 40.000 möglichen Highscore-Werte ist vollständig von jedem der heutigen Computer gewartet werden kann, würde ich auf Bucket Sortierung für Einfachheit zurückgreifen. Meine Vermutung ist, dass einen der bisher vorgeschlagenen Algorithmen übertreffen würde. Der Nachteil ist, dass Sie eine obere Grenze für die Highscore-Werte festlegen müssen. So

, nehmen wir an, Ihre max Highscore-Wert ist 40.000:

Machen Sie ein Array von 40.000 Einträgen. Wiederhole deine Highscore-Werte. Jedes Mal, wenn du auf Highscore x stößt, erhöhe dein Array [x] um eins. Danach müssen Sie nur die obersten Einträge in Ihrem Array zählen, bis Sie 100 gezählte Highscores erreicht haben.

+0

Nun, ein Eimer Sortierung würde funktionieren, um meine Top 100 Ergebnisse zu finden, aber es würde mir nur die besten Ergebnisse bringen. Ich denke, es war meine Schuld, ich habe das Problem nicht so genau definiert, wie ich es hätte tun sollen. Jede Punktzahl wird von 3 Werten abgeleitet, jede dieser Punktzahlen muss diese 3 Werte haben, die zusammen mit der Punktzahl markiert sind, daher würde eine Bucket-Sortierung nicht meinen Bedürfnissen entsprechen. Aber Ihr Recht, diese Methode würde trotz allem anderen Methoden überlegen sein, wenn der Bereich klein ist und ich Klassen nicht sortieren würde. – Faken

+0

Hmm ... auf den zweiten Gedanken, es könnte funktionieren, wenn ich eine Art Liste an jedem der Eimer zum Speichern anderer Daten ... implementiert würde, aber das wäre extrem speicherintensiv wäre, wenn ich einen Grenzwert irgendwo, aber sogar dann wäre ich nicht in der Lage, einen hohen Bereich zu schätzen, ohne über den gesamten Datensatz zu iterieren. – Faken

+0

Aber dann wieder könnte ich immer einen Cutoff nach jedem Say implementieren, äußere Schleife Iteration, die überprüft, wo meine Top 100 Scores sind und eine if-Anweisung zu überprüfen, ob der nächste Score innerhalb dieser Highscore-Wert war ... das könnte tatsächlich funktionieren noch effizienter! Der einzige Nachteil wäre die Speichernutzung, die aktuell beste Antwort verwendet nur maximal 400Kb Speicher insgesamt ... aber andererseits, mit 8 GB RAM, was sind ein paar hundert MB? (ähm, naja, ich schätze Cache hat viel damit zu tun ... das frühere Programm würde allerdings sehr schön im L2 Cache sitzen). Wie auch immer, es ist interessant ... – Faken