Mögliches Duplikat:
Rollender Median-Algorithmus in C.
Vorausgesetzt, Ganzzahlen werden aus einem Datenstrom gelesen. Finden Sie den Median der bisher gelesenen Elemente auf effiziente Weise.
Lösung Ich habe gelesen: Wir können einen maximalen Heap auf der linken Seite verwenden, um Elemente darzustellen, die kleiner als der effektive Median sind, und einen minimalen Heap auf der rechten Seite, um Elemente darzustellen, die größer als der effektive Median sind.
Nach der Verarbeitung eines eingehenden Elements unterscheidet sich die Anzahl der Elemente in Heaps höchstens um 1 Element. Wenn beide Heaps die gleiche Anzahl von Elementen enthalten, finden wir den Durchschnitt der Stammdaten des Heaps als effektiven Median. Wenn die Heaps nicht ausgeglichen sind, wählen wir den effektiven Median aus der Wurzel des Heaps aus, der mehr Elemente enthält.
Aber wie würden wir einen maximalen und einen minimalen Haufen konstruieren, dh wie würden wir hier den effektiven Median kennen? Ich denke, wir würden 1 Element in max-heap und dann das nächste 1 Element in min-heap einfügen und so weiter für alle Elemente. Korrigieren Sie mich, wenn ich hier falsch liege.
Antworten:
Es gibt verschiedene Lösungen, um aus gestreamten Daten einen laufenden Median zu ermitteln. Ich werde am Ende der Antwort kurz darauf eingehen.
Die Frage bezieht sich auf die Details einer bestimmten Lösung (Max-Heap / Min-Heap-Lösung) und wie die Heap-basierte Lösung funktioniert, wird nachfolgend erläutert:
Fügen Sie für die ersten beiden Elemente links ein kleineres zum maxHeap und rechts ein größeres zum minHeap hinzu. Verarbeiten Sie dann die Stream-Daten nacheinander.
Dann können Sie zu jedem Zeitpunkt den Median wie folgt berechnen:
Jetzt werde ich über das Problem im Allgemeinen sprechen, wie zu Beginn der Antwort versprochen. Das Ermitteln des laufenden Medians aus einem Datenstrom ist ein schwieriges Problem, und das effiziente Finden einer genauen Lösung mit Speicherbeschränkungen ist im allgemeinen Fall wahrscheinlich unmöglich. Wenn die Daten jedoch einige Eigenschaften aufweisen, die wir nutzen können, können wir effiziente, spezialisierte Lösungen entwickeln. Wenn wir beispielsweise wissen, dass die Daten ein integraler Typ sind, können wir die Zählsortierung verwenden, die Ihnen einen konstanten Speicherkonstanten-Zeitalgorithmus geben kann. Die Heap-basierte Lösung ist eine allgemeinere Lösung, da sie auch für andere Datentypen (Doppel) verwendet werden kann. Und schließlich, wenn der genaue Median nicht erforderlich ist und eine Annäherung ausreicht, können Sie einfach versuchen, eine Wahrscheinlichkeitsdichtefunktion für die Daten zu schätzen und den Median damit zu schätzen.
quelle
Wenn Sie nicht alle Elemente gleichzeitig speichern können, wird dieses Problem erheblich schwieriger. Bei der Heap-Lösung müssen Sie alle Elemente gleichzeitig im Speicher halten. Dies ist in den meisten realen Anwendungen dieses Problems nicht möglich.
Verfolgen Sie stattdessen beim Anzeigen von Zahlen, wie oft Sie jede Ganzzahl sehen. Angenommen, 4-Byte-Ganzzahlen, das sind 2 ^ 32 Buckets oder höchstens 2 ^ 33 Ganzzahlen (Schlüssel und Anzahl für jedes int), was 2 ^ 35 Bytes oder 32 GB entspricht. Es wird wahrscheinlich viel weniger sein, da Sie den Schlüssel nicht speichern oder für die Einträge zählen müssen, die 0 sind (dh wie ein Standarddikt in Python). Das Einfügen jeder neuen Ganzzahl dauert konstant lange.
Um den Median zu finden, verwenden Sie zu jedem Zeitpunkt einfach die Anzahl, um zu bestimmen, welche Ganzzahl das mittlere Element ist. Dies dauert eine konstante Zeit (wenn auch eine große Konstante, aber dennoch konstant).
quelle
Wenn die Varianz der Eingabe statistisch verteilt ist (z. B. normal, logarithmisch normal usw.), ist die Reservoirabtastung eine vernünftige Methode zur Schätzung von Perzentilen / Medianwerten aus einem beliebig langen Strom von Zahlen.
"Reservoir" ist dann eine laufende, einheitliche (faire) Stichprobe aller Eingaben - unabhängig von der Größe. Das Finden des Medians (oder eines Perzentils) ist dann eine einfache Angelegenheit, das Reservoir zu sortieren und den interessanten Punkt abzufragen.
Da das Reservoir eine feste Größe hat, kann die Sortierung als effektiv O (1) betrachtet werden - und diese Methode wird sowohl mit konstanter Zeit als auch mit konstantem Speicherverbrauch ausgeführt.
quelle
Der effizienteste Weg, ein Perzentil eines Stroms zu berechnen, den ich gefunden habe, ist der P²-Algorithmus: Raj Jain, Imrich Chlamtac: Der P²-Algorithmus zur dynamischen Berechnung von Quantilen und Histogrammen ohne Speichern von Beobachtungen. Kommun. ACM 28 (10): 1076 & ndash; 1085 (1985)
Der Algorithmus ist einfach zu implementieren und funktioniert sehr gut. Es ist jedoch eine Schätzung, denken Sie also daran. Aus der Zusammenfassung:
quelle
Wenn wir den Median der n zuletzt gesehenen Elemente ermitteln möchten , hat dieses Problem eine genaue Lösung, bei der nur die n zuletzt gesehenen Elemente gespeichert werden müssen. Es ist schnell und skaliert gut.
Eine indizierbare Skiplist unterstützt das Einfügen, Entfernen und indizierte Suchen beliebiger Elemente durch O (ln n) unter Beibehaltung der sortierten Reihenfolge. In Verbindung mit einer FIFO-Warteschlange , die den n-ten ältesten Eintrag verfolgt, ist die Lösung einfach:
Hier finden Sie Links zum vollständigen Arbeitscode (eine leicht verständliche Klassenversion und eine optimierte Generatorversion mit dem indizierbaren Skiplist-Code):
http://code.activestate.com/recipes/576930-efficient-running-median-using-an-indexable-skipli/
http://code.activestate.com/recipes/577073 .
quelle
Eine intuitive Möglichkeit, darüber nachzudenken, besteht darin, dass bei einem vollständig ausgeglichenen binären Suchbaum die Wurzel das Medianelement ist, da es die gleiche Anzahl kleinerer und größerer Elemente gibt. Wenn der Baum nicht voll ist, ist dies nicht ganz der Fall, da in der letzten Ebene Elemente fehlen.
Wir können also stattdessen den Median und zwei ausgeglichene Binärbäume haben, einen für Elemente, die kleiner als der Median sind, und einen für Elemente, die größer als der Median sind. Die beiden Bäume müssen gleich groß sein.
Wenn wir eine neue Ganzzahl aus dem Datenstrom erhalten, vergleichen wir sie mit dem Median. Wenn es größer als der Median ist, fügen wir es dem rechten Baum hinzu. Wenn sich die beiden Baumgrößen um mehr als 1 unterscheiden, entfernen wir das min-Element des rechten Baums, machen es zum neuen Median und setzen den alten Median in den linken Baum. Ähnliches gilt für kleinere.
quelle
Effizient ist ein Wort, das vom Kontext abhängt. Die Lösung für dieses Problem hängt von der Anzahl der ausgeführten Abfragen im Verhältnis zur Anzahl der Einfügungen ab. Angenommen, Sie fügen gegen Ende des Medians N-Zahlen und K-mal ein. Die Komplexität des Heap-basierten Algorithmus wäre O (N log N + K).
Betrachten Sie die folgende Alternative. Stellen Sie die Zahlen in ein Array und führen Sie für jede Abfrage den linearen Auswahlalgorithmus aus (z. B. mithilfe des QuickSort-Pivots). Jetzt haben Sie einen Algorithmus mit der Laufzeit O (KN).
Wenn nun K ausreichend klein ist (seltene Abfragen), ist der letztere Algorithmus tatsächlich effizienter und umgekehrt.
quelle
Kannst du das nicht mit nur einem Haufen machen? Update: nein. Siehe den Kommentar.
Invariante: Nach dem Lesen von
2*n
Eingaben enthält der Min-Heap dien
größte davon.Schleife: 2 Eingänge lesen. Fügen Sie beide dem Heap hinzu und entfernen Sie die min. Dies stellt die Invariante wieder her.
Wenn also
2n
Eingaben gelesen wurden, ist die min des Heaps die n-te größte. Es muss eine zusätzliche Komplikation geben, um die beiden Elemente um die Medianposition zu mitteln und Abfragen nach einer ungeraden Anzahl von Eingaben zu bearbeiten.quelle