Effizientes Entfernen von Duplikaten mit geringem Speicheraufwand

9

Ich möchte eine Liste von Ganzzahlen effizient nach Duplikaten filtern, sodass nur die resultierende Menge gespeichert werden muss.

Ein Weg dies kann gesehen werden:

  • wir haben einen Bereich von ganzen Zahlen mit N groß (sagen wir 2 40 )S={1,,N}N240
  • wir haben eine Funktion mit angeblich vielen Kollisionen (die Bilder sind gleichmäßig in S verteilt )f:SSS
  • wir müssen dann speichern , das heißt { f ( x ) | x S }f[S]{f(x)|xS}

Ich habe eine ziemlich genaue (probabilistische) Schätzung dessen, was ist und kann daher Datenstrukturen im Voraus zuweisen (sagen wir | f [ S ] |2 30 ).|f[S]||f[S]|230

Ich hatte einige Ideen, bin mir aber nicht sicher, was der beste Ansatz wäre:

  • Ein Bitset kommt nicht in Frage, da der Eingabesatz nicht in den Speicher passt.
  • eine Hash-Tabelle, aber (1) es erfordert etwas Speicher-Overhead, sagen wir 150% von und (2) die Tabelle muss beim Erstellen untersucht werden, was aufgrund des Speicheraufwands zusätzliche Zeit erfordert.|f[S]|
  • eine "on the fly" -Sorte, vorzugsweise mit -Komplexität (Nichtvergleichssortierung). In Bezug darauf bin ich mir nicht sicher, was der Hauptunterschied zwischen Bucket Sort und Flashsort ist .O(N)
  • Ein einfaches Array mit einem binären Suchbaum, für das jedoch erforderlich ist .O(Nlog|f[S]|)
  • Möglicherweise kann die Verwendung von Bloom-Filtern oder einer ähnlichen Datenstruktur hilfreich sein, um das Problem zu lösen (mit falsch positiven Ergebnissen).

Einige Fragen zu Stackoverflow scheinen sich mit solchen Dingen zu befassen ( /programming/12240997/sorting-array-in-on-run-time , /programming/3951547/java) -array-find-duplicates ), aber keines scheint meinen Anforderungen zu entsprechen.

doc
quelle
2
Müssen Sie f [S] aufzählen (was auch immer es ist) oder schnell erkennen können, ob sich ein x darin befindet?
Gilles 'SO - hör auf böse zu sein'
@ Gilles: Ich glaube, da in f [S] keine offensichtliche Struktur gefunden werden kann, sind die beiden Lösungen äquivalent.
Doc
Ihre Zahlen summieren sich nicht. Das erwartete Bild einer Zufallsfunktion in einer Domäne der Größe ist in etwa ( 1 - 1 / e ) N . Ein weiteres Problem ist, dass das Durchlaufen von 2 56 zu lange dauern wird, es sei denn, Sie verfügen über einen Supercomputer oder einen großen Cluster. N(11/e)N256
Yuval Filmus
1
Die Zeit für den binären Suchbaum wäre , was in der Praxis nahe an O ( N log N ) liegen kann oder nicht, aber immer noch genauer ist. O(Nlog|f[S]|)O(NlogN)
jmad
1
Ist ein linearer Zeitalgorithmus mit nicht auch unerschwinglich? (Nach meinen Berechnungen würden Sie gut 2 Jahre brauchen, selbst wenn Sie ein Element von S in 1 Nanosekunde betrachten!). N256S
Aryabhata

Antworten:

1

Warum nicht Mülleimer und Kette?

Die Idee besteht darin, positive ganze Zahlen, die durch Bits darstellbar sind, in einem Array A von 2 k Einträgen zu speichern, die Wertebereiche darstellen: Eintrag A [ y ] , y 0 , repräsentiert den Bereich [ 2 m y , 2 m ( y) + 1 ) - 1 ] . Für jede 1 x < 2 n können wir x = 2 m y schreibenn=k+mEIN2kEIN[y]]y0[2my,2m(y+1)- -1]]1x<2n , wo y hat k Bits und z hat m Bits. Versuchen Sie, z (nicht x !) An Position y zu speichern:x=2my+zykzmzxy

  • Wenn bereits ist, tun Sie nichts: x ist ein Duplikat.EIN[y]]=zx

  • Wenn nicht initialisiert ist, speichern Sie z bei A [ y ] .EIN[y]]zEIN[y]]

  • Andernfalls speichert einen Index in eine separate Anordnung zur Ketten des ‚s (die bei kollidiert ist y ) in verknüpften Listen. Sie müssen die Liste mit der Überschrift A [ y ] linear durchsuchen und je nachdem, was die Suche aufdeckt, möglicherweise z in die Liste einfügen .zyEIN[y]]z

Am Ende, ist leicht durch Einschleifen durch die initialisierten Einträge zu erholen A und - lediglich durch zwei Bitstrings Verketten - Zusammenbauen jeden Z an der Stelle gefunden y (entweder direkt oder innerhalb einer Kette referenzierten dort) in die ursprünglichen Wert x = 2 m y + z .f(S.)EINzyx=2my+z

Wenn die Verteilung nahezu gleichmäßig ist und N überschreitet , kommt es zu keiner starken Verkettung (dies kann auf die übliche Weise beurteilt werden), und die Ketten sind tendenziell kurz. Wenn die Verteilung ungleichmäßig ist, funktioniert der Algorithmus immer noch, kann jedoch ein quadratisches Timing erreichen. Wenn dies möglich ist, verwenden Sie etwas Effizienteres als Ketten (und zahlen Sie ein wenig Overhead für die Lagerung).2kN.

Der benötigte Speicher beträgt höchstens Bits für A und 2 2 k Bits für die Ketten (unter der Annahme von m k ). Dies ist genau der Speicherplatz, der zum Speichern von 2 k- Werten mit jeweils n Bits benötigt wird. Wenn Sie von der Einheitlichkeit überzeugt sind, können Sie den Speicher für die Ketten zu wenig zuordnen. Wenn eine Ungleichmäßigkeit möglich ist, möchten Sie möglicherweise k erhöhen und die Kettenspeicherung vollständig befürworten.2nEIN22kmk2knk

Eine alternative Art, über diese Lösung nachzudenken, besteht darin, dass es sich um eine Hash-Tabelle mit einer besonders schönen Hash-Funktion handelt (nehmen Sie die höchstwertigen Bits), und aus diesem Grund müssen wir nur die niedrigstwertigen m = n - k Bits in speichern Der Tisch.km=n- -k

Es gibt Möglichkeiten, Speicher für die Ketten mit dem Speicher für zu überlagern, aber es scheint die Mühe nicht wert zu sein, da dies nicht viel Platz spart (vorausgesetzt, m ist viel kleiner als k ) und die Entwicklung des Codes erschwert. debuggen und pflegen.EINmk

whuber
quelle
1
Ich denke, der vorletzte Absatz ist hier der zentrale und sollte wahrscheinlich ganz oben stehen (als Idee). Ich kenne den Begriff "bin and chain" nicht (obwohl er nach dem Lesen des Beitrags sinnvoll ist). Diese Idee kann auf Versuche ausgedehnt werden .
Raphael
Dies ist also bei schlecht verteilten Eingängen. Ich sehe nicht, wie effizient das ist. Θ(n2)
Einpoklum
@einpoklum Diese Antwort beschreibt explizit die Bedingungen, unter denen die Lösung effizient ist.
Whuber