
Was kann ich tun, wenn der Suchbaum auf den gesamten Arbeitsspeicher angewachsen ist und benachbarte Racks im Serverraum verwurzelt werden sollen? Was tun mit einem invertierten ressourcenhungrigen Index? Sollte ich mich mit der Android-Entwicklung verbinden, wenn der Benutzer "Telefonspeicher voll" erhÀlt und die Anwendung nur die HÀlfte der Ladung eines wichtigen Containers enthÀlt?
Ist es generell möglich, die Datenstruktur so zu komprimieren, dass sie deutlich weniger Platz beansprucht, aber nicht ihre inhĂ€renten Vorteile verliert? Damit bleibt der Zugriff auf die Hash-Tabelle schnell und der ausgeglichene Baum behĂ€lt seine Eigenschaften. Ja das kannst du Hierzu erschien die Richtung der Informatik "PrĂ€gnante Datenstrukturen", die die kompakte Darstellung von Datenstrukturen erforschte. Es hat sich seit Ende der 80er Jahre weiterentwickelt und befindet sich derzeit in der BlĂŒte von Big Data und Highload.
In der Zwischenzeit wird es einen Helden auf Habr geben, der dreimal hintereinander sprechen kann
[sÉkÉsËkt]?
TĂŒr zur Welt der Kompaktheit
Eine Datenstruktur wird daher als kompakt (prÀgnant) betrachtet, wenn sie:
- Es belegt eine Anzahl von Bits in der NĂ€he der informationstheoretischen Untergrenze.
- Es ist kein vorheriges Auspacken fĂŒr den vollen Gebrauch erforderlich.
Dies bedeutet, dass verlustfreie Komprimierungsalgorithmen nichts mit kompakten Datenstrukturen zu tun haben. SchlieĂlich geht es darum, Daten aus einem komprimierten Zustand zur Verarbeitung wiederherzustellen.
Bekannte Mainstream-Implementierungen von Diagrammen, Hash-Tabellen und anderen Dingen sind ebenfalls nicht gut. Nehmen Sie mindestens Zeiger auf untergeordnete Elemente im Suchbaum. Sie essen anstĂ€ndige Orte auf: O ( M N ) wo M - die LĂ€nge des Zeigers und N - die Anzahl der Knoten im Baum. Aber die prĂ€gnante Baumimplementierung ermöglicht es uns, das asymptotische Verhalten zu verbessern 2 N + o ( N ) was nahe an der theoretischen Untergrenze liegt 2NâÎ(logN) fĂŒr Holz aus N Knoten. Mit ZeigerlĂ€nge M=8 Byte bedeutet dies, sich von zu bewegen O(8N) zu einer ganz anderen Ordnung der Asymptotik - nur 2N in Anbetracht dessen o(N) - vernachlĂ€ssigbarer Wert in Bezug auf N .
Kompakte (prĂ€gnante) Datenstrukturen sind komprimierte Darstellungen fĂŒr Bitvektoren, Multisets, planare Graphen und andere klassische Lieblingsstrukturen. Oft sind sie statisch, einmal gebaut und Ă€ndern sich wĂ€hrend des Gebrauchs nicht. Es gibt Ausnahmen - prĂ€gnante Strukturen mit schnellen Operationen zum HinzufĂŒgen und Entfernen von Elementen.
Die meisten kompakten Strukturen basieren auf dem Konzept des sogenannten kompakten indexierbaren Wörterbuchs. Dies ist ein Sonderfall einer Bitmap (Bitmap, Bitset). Die Bitmap selbst ist ideal, um zu ĂŒberprĂŒfen, ob sich Elemente in einer bestimmten Menge befinden. Wenn ein Element in einer Menge enthalten ist, wird der Bitwert an einem bestimmten Index auf 1 gesetzt. Andernfalls wird er auf 0 zurĂŒckgesetzt. Ein wichtiges Beispiel ist die Inode-Bitmap ext4, UFS und andere Unix-Dateisysteme. Es speichert Daten darĂŒber, welche EintrĂ€ge in der Inode-Tabelle belegt und welche frei sind.
Ein kompaktes indexierbares Wörterbuch ist dasselbe Bitmap, wird jedoch durch zwei Operationen ergÀnzt: Rang und Auswahl. Diese Operationen sind die Elefanten, auf denen die prÀgnante Welt ruht. Grob gesagt, Rang ist eine ZÀhlung der Anzahl der Elemente, und Auswahl ist eine Suche nach einem Element:
- rankx(i) Gibt die Anzahl der Bits zurĂŒck, die gleich sind x deren Indizes liegen auf einem Segment [0;i] . Als x - der Wert des Bits, dann kann er ausschlieĂlich gleich 0 oder 1 sein.
- selectx(j) Gibt den Index zurĂŒck j bisschen gleich x . Der gesunde Menschenverstand sagt, dass es keine Null gibt, es gibt nur die erste. Deshalb $ inline $ j> 0 $ inline $ : Berechnung erfolgt von eins. AuĂerdem, j darf die Gesamtzahl der Bits im Wörterbuch nicht ĂŒberschreiten x .
Angenommen, wir haben ein indexierbares Wörterbuch, in dem 4 der 7 Bits gesetzt sind. Dann rank1 und select1 nimmt die folgenden Werte an:
Ein Beispiel fĂŒr ein indexierbares Wörterbuch und eine Berechnung dafĂŒr rank1 , select1 .
Ein aufmerksamer Leser wird feststellen, dass select die Umkehrung des Ranges ist. Wenn rank1(5)=4 dann select1(4)=5 .
Jemand hatte Deja Vu beim Anblick von rank1(i) ? Und das alles, weil diese Operation das Hamming-Gewicht verallgemeinert - die Anzahl der Zeichen ungleich Null in der Sequenz. Bei binÀren Sequenzen wird Hammings Gewicht auch als Popcount (Populationszahl) bezeichnet.
Rang / Auswahl gilt auch fĂŒr verworfene Bits. Hier ist ein Berechnungsbeispiel rank0 und select0 fĂŒr Bits gleich 0:
Ein Beispiel fĂŒr ein kompaktes indexierbares Wörterbuch und dessen Berechnung rank0 , select0 .
Sah einen Baum in Bitiks
Mit diesem Wissen bauen wir einen kompakten PrĂ€fixbaum! PrĂ€fixbĂ€ume eignen sich zum Auffinden von Zeichenfolgen nach PrĂ€fix. Mit ihrer Hilfe wird hĂ€ufig eine Dropdown-Liste mit Suchtipps (sjest) implementiert. Der Ansatz zur Succinctisierung des PrĂ€fixbaums ist extrem verallgemeinert und zeigt maximal alle Rosinen kompakter Strukturen. Im Gegensatz zu einem binĂ€ren Baum, fĂŒr den bestimmte Formeln abgeleitet werden, die das Gesamtbild stören.
Drei Methoden zur kompakten Darstellung von BĂ€umen sind am beliebtesten:
- BP (ausgeglichene Klammern) - ausgeglichene Klammerfolgen.
- DFUDS (Depth-First-Unary-Degree-Sequenz) - eine Sequenz von einheitencodierten Knoten sortiert nach Tiefensuche.
- LOUDS (Level-Ordered Unary Degree Sequences) - Sequenzen von nach Level sortierten, in Einheiten kodierten Knoten.
Was ist die verdĂ€chtige logische Kette der Ăbersetzung von "unĂ€rem Grad" zu "einfach codiertem Knoten"? Na dann. Ein einheitlicher Grad in diesen Namen bedeutet, dass Baumknoten mit einer Folge von Einheiten nach der Anzahl der untergeordneten Knoten codiert werden, wobei im Trailer immer eine Null angegeben ist.
Diese drei Methoden zur Darstellung von BÀumen werden durch die Anwesenheit schneller Operationen vereint: Finde einen Elternteil; finde den ersten Nachkommen; finde den letzten Nachkommen; Suchen Sie den linken und rechten benachbarten Knoten. Die grundsÀtzliche Möglichkeit und Wirksamkeit anderer Operationen unterscheidet sich von Methode zu Methode.
Kommen wir zur Methode LOUDS. Wenn Sie es verstanden haben, wird es nicht schwierig sein, mit den beiden anderen umzugehen. DarĂŒber hinaus feierten die LOUDS-BĂ€ume im vergangenen Jahr ihren 30. Geburtstag! ZusĂ€tzliche nĂŒtzliche Operationen fĂŒr LOUDS-BĂ€ume sind in implementiert O(1) : finde die Anzahl der Nachkommen des Knotens; Berechnen Sie die Anzahl der Nachkommen des Knotens unter allen Nachkommen (erster Nachkomme, zweiter Nachkomme, i th usw.); zu finden i der Nachwuchs. Der Nachteil von LOUDS ist das Fehlen eines effektiven Algorithmus zum ZĂ€hlen der Anzahl der Teilbaumknoten.
Das Wesentliche der Methode ist einfach: Speichern Sie die SchlĂŒssel der Baumknoten und alle wertvollen Informationen in einem regulĂ€ren Array und stellen Sie die Baumstruktur als Folge von Bits dar. Insgesamt haben wir zwei statische Strukturen. Es ist jedoch nicht erforderlich, den Baumknoten Speicher fĂŒr Zeiger zuzuweisen: Die ĂbergĂ€nge zwischen ihnen werden mithilfe von Formeln unter aktiver Verwendung von rank / select implementiert.
Warnung, PrÀfixbaum:
PrÀfixbaum bereit zur Komprimierung mit der LOUDS-Methode.
Bereiten Sie den Baum fĂŒr die Darstellung in binĂ€rer Form vor:
- Befestigen Sie den Baum an der falschen Wurzel. Er wird sehr bald seine Rolle spielen.
- Wir nummerieren alle Knoten des Baums, Ebene fĂŒr Ebene, von links nach rechts, wie in BFS (Breitensuche). Die gefĂ€lschte Wurzel wird ignoriert und die reale Wurzel wird durch Null indiziert.
- Kodieren Sie die Knoten. Der Baumknoten wird durch eine Folge von Einheiten kodiert, die direkten Nachkommen plus Null entsprechen. Wenn der Knoten vier Kinder hat, wird er als 11110 codiert, und wenn keiner - als 0. Die falsche Wurzel wird zuerst codiert. Es hat einen einzelnen Nachkommen, der Code lautet also 10.
Ein PrÀfixbaum mit nummerierten Knoten. Knoten sind codiert.
Beim Durchlaufen eines Baums auf Ebene wird ein kompaktes indexierbares Wörterbuch gebildet - eine Folge von Bits von codierten Knoten, die von oben nach unten und von links nach rechts geklebt werden. Wir haben eine 21-Bit-Sequenz. Ăbrigens heiĂt es LBS (LOUDS Bit String).
Kompaktes indexierbares Wörterbuch fĂŒr PrĂ€fixbaum.
Der kompakte LOUDS-PrĂ€fixbaum wird erstellt. LBS fĂŒr Holz mit N Knoten (Fake zĂ€hlt nicht) nimmt 2N+1 bisschen. Das Interessanteste bleibt: Formeln zum Ăberqueren eines Baumes werden zu einer Bitmap.
Suche nach dem ersten Kind . Ăbergang von einem Knoten i zu seinem ersten Kindknoten wird nach der Formel ausgefĂŒhrt:
firstChild(i)=select0(i+1)âi
i - Dies ist die Knotennummer auf der vorherigen Platte, die violett markiert ist.
Suchen Sie den ersten Nachkommen des Knotens mit Index 3 (den Buchstaben "a"):
firsthild(3)=select0(3+1)â3=select0(4)â3=9â3=6
Der erste untergeordnete Knoten befindet sich am Index 6, und dies ist der Buchstabe "k". Wir wenden die Formel fĂŒr die Wurzel des Baumes an:
firstChild(0)=select0(0+1)â0=select0(1)=1
Wir fanden ein Blatt mit dem Index 1, dem Buchstaben âundâ. Konvergiert! Es wurde klar, warum eine falsche Wurzel benötigt wurde: fĂŒr die Magie der Indizierung von Knoten. Um seltsame Fehler zu vermeiden, bevor Sie zu den Nachkommen des Knotens ĂŒbergehen i Es wĂ€re schön, die Anzahl dieser Nachkommen herauszufinden. TatsĂ€chlich liefert die Formel fĂŒr die BlĂ€tter des Baumes, was nicht ĂŒberraschend ist, ein unzureichendes Ergebnis. Um den nĂ€chsten Nachkommen nach dem ersten zu finden, mĂŒssen Sie 1 hinzufĂŒgen. Dies ist logisch, da die Nachkommen eines Knotens immer in der NĂ€he sind, ohne LĂŒcken. Wenn Sie sie durchlaufen, mĂŒssen Sie jedoch rechtzeitig anhalten und feststellen, welcher Nachkomme als letzter gilt.
Suchen Sie nach dem letzten Nachkommen eines Knotens i Der Durchlauf erfolgt in zwei Schritten: Bestimmen des Index der letzten Einheit im Knotencode - es ist das, was den angegebenen Nachkommen bezeichnet; und dann den Index des Kindes selbst bestimmen:
lastChildPos(i)=select0(i+2)â1
Nachdem der Index der letzten Einheit im Knotencode empfangen wurde, muss ĂŒberprĂŒft werden, ob das Bit an diesem Index tatsĂ€chlich gesetzt ist. Wenn nicht, dann bietet sich die Schlussfolgerung an: Dies ist ein Knoten ohne Nachkommen, ein Blatt. Wenn das Bit gesetzt ist, fahren Sie fort:
lastChild(i)=rank1(lastChildPos(i)â1)
Suchen Sie den letzten Nachkommen von Knoten 2 (den Buchstaben "k").
lastChildPos(2)=select0(2+2)â1=select0(4)â1=9â1=$
Das Bit bei Index 8 ist 1, daher ist Knoten 2 kein Blatt, und wir können den Index seines letzten untergeordneten Elements finden:
lastChild(i)=Rang1(8â1)=5
Die Anzahl der Nachkommen. Der einfachste Weg, die Anzahl der Nachkommen zu bestimmen, besteht darin, den Index seines ersten Nachkommen vom Index des letzten Nachkommen des Knotens zu subtrahieren und 1 hinzuzufĂŒgen:
childrenCount(i)=letztesKind(i)âerstesKind(i)+1
Angenommen, der Knoten i Es gibt einen benachbarten Knoten i+1 befindet sich auf der gleichen Baumebene wie i . Dann die Anzahl der Nachkommen i kann als Differenz zwischen den Indizes der ersten Nachkommen von Knoten definiert werden i+1 und i :
childrenCount(i)=firsthild(i+1)âfirsthild(i)
Die Nachkommen des Knotens werden ebenfalls fortlaufend nummeriert. Wenn der erste Nachkomme i - Das j dann der zweite - j+1 und so weiter bis zum Nachkommen eines auf dieser Ebene benachbarten Knotens i+1 (falls vorhanden).
Die Anzahl der Nachkommen des Blattes "und" mit Index 1 ist voraussichtlich Null:
childrenCount(1)=(select0(2+1)â2)â(select0(1+1)â1)=3â3=0
Ăbergeordnete Suche nach einem Knoten i organisiert durch die Formel:
parent(i)=rank0(select1(i+1)â1)â1
Wir werden es verwenden, um nach dem Elternteil von Knoten 6 (dem Buchstaben "k") zu suchen:
parent(6)=rank0(select1(7)â1)â1=rank0(9)â1=3
Dies ist Knoten 3, der Buchstabe "a".
Mit Kenntnis der Formeln fĂŒr die untergeordneten und ĂŒbergeordneten Indizes ist es nicht schwierig, den gesamten Baum zu durchlaufen. Das Wichtigste ist, die Verarbeitung der Randbedingungen fĂŒr Wurzel und BlĂ€tter nicht zu vergessen.
Ein paar Kopeken ĂŒber BP- und DFUDS-Methoden. Beide Methoden haben rĂ€umliche Asymptotik - 2N+o(N) StĂŒck fĂŒr Holz aus N Knoten, und beide sind in der Darstellung eines Baumknotens in Form von öffnenden und schlieĂenden Klammern Ă€hnlich.
BP (ausgeglichene Klammern) konvertiert einen Baum in eine Folge von Klammern, ein Paar fĂŒr jeden Knoten. Dazu geht der Baum in die Tiefe; Jeder Knoten wird zweimal besucht. Beim ersten Besuch wird die öffnende Klammer aufgezeichnet, beim zweiten Besuch die schlieĂende Klammer. Dazwischen stehen Klammern von Kindern.
Es ist praktisch, die Reihenfolge der Klammern in Form einer Bitmap darzustellen, wobei 1 die öffnende Klammer und 0 die schlieĂende Klammer ist. Alle Formeln fĂŒr die Arbeit mit BP sind fĂŒr eine schnelle Suche geschĂ€rft. Im Gegensatz zu LOUDS können Sie mit BP die GröĂe eines Teilbaums schnell berechnen und den nĂ€chsten gemeinsamen Vorfahren von zwei Knoten bestimmen. Aber finde i -th Nachkomme ist viel komplizierter als in LOUDS.
DFUDS (Depth-First Unary Degree Sequence) Ă€hnelt sowohl BP als auch LOUDS. Mit BP kombiniert es einen Baumgang in der Tiefe und seine Klammerdarstellung. Das Prinzip der Klammern ist dasselbe wie das Prinzip der Kodierung von Knoten in LOUDS. Bevor wir den Baum durchlaufen, fĂŒgen wir der Klammerfolge vorab eine öffnende Klammer hinzu. Beim Ăberqueren von Knoten schreiben wir dann offene Klammern entsprechend der Anzahl der Nachkommen plus eine schlieĂende. Es stellt sich heraus, dass der Ort, an dem Nachkommen in DFUDS gespeichert werden, höher ist als der von BP. Die Berechnung der GröĂe des Teilbaums und die Suche nach dem nĂ€chsten gemeinsamen Vorfahren werden fĂŒr ausgefĂŒhrt O(1) . Bestimmen Sie jedoch die Höhe des Teilbaums und suchen Sie nach dem ĂŒbergeordneten Baum j Levellastige Operationen fĂŒr DFUDS.
Zur Verdeutlichung vergleichen wir die LOUDS-, BP- und DFUDS-Methoden am Beispiel des Minibaums.
Die Knoten des Baumes sind orange nummeriert, als ob sie in der Breite (fĂŒr LOUDS) laufen, in blau - als ob sie in der Tiefe laufen (fĂŒr BP und DFUDS).
LOUDS-, BP- und DFUDS-Baumansichten.
Seien Sie nicht ĂŒberrascht, wenn Sie Unterschiede in Formeln in englischsprachigen Werken sehen. Unter Mathematikern gibt es Liebhaber der Indexierung ab einem. Und einige Entwickler betrachten die Wörter rank und range consonant, so dass sie den Rang halbieren. [0;i) . Aufgrund der Notwendigkeit, die Symmetrie von Rang / Auswahl beizubehalten, berechnen sie select(i) wie select(i+1) . Einige Formeln in dieser Form sehen jedoch kĂŒrzer aus.
Sparse Array: schĂŒtteln, aber nicht mischen
Ein spĂ€rliches Array ist eine andere Struktur, die buchstĂ€blich fĂŒr die Komprimierung erstellt wurde. Die GröĂe eines solchen Arrays ist manchmal um GröĂenordnungen gröĂer als die Anzahl der gefĂŒllten Elemente. Und leere Elemente nehmen entweder einen Standardwert an oder sind mit so etwas wie null markiert. Ein spĂ€rliches Array zeichnet sich am Horizont ab, wann immer dies erforderlich ist, um viele Objekte und die Beziehungen zwischen ihnen zu speichern. Die Graphen von Freunden in sozialen Netzwerken, Suchmaschinen-Ranking-Algorithmen, Excel-Ă€hnlichen Tabellen, elektrischen Schaltungssimulatoren mit Milliarden von Transistoren in einem Chip fallen einem sofort ein.
Oft sind solche Arrays zyklopisch im Lovecraft-Stil, mit einer naiven Implementierung passen sie nicht in den Arbeitsspeicher und bleiben praktisch leer. Je nach Speicher- und Geschwindigkeitsanforderungen werden spÀrliche Arrays zu viel kompakteren Hash-Tabellen, Adjazenzlisten, BinÀrbÀumen ... oder prÀgnanten Arrays.
Nehmen wir an, wir haben eine spĂ€rliche Reihe von Zeichenfolgen. FĂŒgen Sie ein kompaktes indexierbares Wörterbuch hinzu. Was wird es geben?
Sparse Array mit einer Bitmap.
Ohne direkten Zugriff auf das ursprĂŒngliche Array ist es jetzt einfach zu ĂŒberprĂŒfen, ob ein Element im gewĂŒnschten Index vorhanden ist. Nichts verhindert das Verkleinern des ursprĂŒnglichen Arrays, indem alle ungefĂŒllten Elemente weggeworfen werden:
Ein Array ohne leere Elemente.
Berechnen eines Index in einem komprimierten Array. Nach der ĂberprĂŒfung auf das Vorhandensein eines Elements wĂ€re es hilfreich, auf dessen Wert im ursprĂŒnglichen Array zuzugreifen, d. H. Den Index abzubilden i im indexierten Wörterbuchindex j in einem komprimierten Array. Kein Wunder, dass Rang dafĂŒr verwendet wird:
j=Rang1(i)â1
Lassen Sie uns ĂŒberprĂŒfen, wie es mit dem 8. Element aussieht: bitmap[8]=0 . Im ursprĂŒnglichen Array gibt es also kein solches Element. Was ist mit Element 7? bitmap[7]=1 . Holen Sie sich seinen Wert: rank1(7)â1=3â1=2 . Bei Index 2 ist "go".
Berechnung des Index im Quell-Array. Sicherlich mĂŒssen Sie im Array nach Elementen nach Wert suchen! Wenn die Daten nicht sortiert sind, beschrĂ€nkt sich die Suche auf die Suche nach O(N) wo N - die Anzahl der nicht leeren Elemente. FĂŒr gefundenes Objekt j Möglicherweise muss der Index abgerufen werden i als ob das Array nicht geschrumpft wĂ€re. Verwenden Sie dazu select, das Gegenteil von rank:
i=select1(j+1)
Suchen Sie zum Beispiel die Zeile âC ++â. In einem kompakten Array befindet es sich am Index 0. Und sein Index im ursprĂŒnglichen Array ist select1(0+1)=3 .
Schon an einem Beispiel mit Zeilen spĂŒrbare Speichereinsparungen. Wenn das Array Klassen mit vielen Feldern speichern soll, werden die Einsparungen erheblich gröĂer. DarĂŒber hinaus ist die Suche in einem kompakten Array schneller als in einem groĂen und spĂ€rlichen Array: Es wird vom Prozessor besser zwischengespeichert. Ein bitindexiertes Wörterbuch passt eher in eine Cache-Zeile als ein regulĂ€res Array von Zahlen, Zeichenfolgen oder ausgefallenen benutzerdefinierten Typen.
NatĂŒrlich zur Aufbewahrung 2 30 Elemente beschriebene Methode ist nicht geeignet. Ihre Anwendbarkeit endet dort, wo Probleme mit dem Wachstum des Index auftreten. Aber natĂŒrlich hat diese Methode zum Komprimieren von Arrays und ihren Variationen eine eigene Nische. Ein alltĂ€gliches Beispiel ist die Implementierung des BitTorrent-Protokolls. Die Bitmap enthĂ€lt Informationen zu den heruntergeladenen Dateisegmenten, und Peers tauschen die Indizes ihrer Segmente aus. Ein Weltraumbeispiel sind die segmentierten DatenĂŒbertragungsoptionen, die von Rovern, Voyagern und der New Horizons-Station verwendet werden, um trans-Neptune-FreiflĂ€chen zu pflĂŒgen.
Die beschriebenen Beispiele fĂŒr die Succinctization eines PrĂ€fixbaums und eines spĂ€rlichen Arrays basieren auf einer gemeinsamen Grundlage. Es basiert auf einem unerschĂŒtterlichen Glauben an die Wirksamkeit von Rank / Select-Operationen. Ohne sie platzt die ganze Theorie der kompakten, aber ausreichend schnellen Strukturen aus allen NĂ€hten. Die Angemessenheit der Verwendung kompakter Strukturen auĂerhalb von Dissertationen hĂ€ngt vom Rang und der Auswahlgeschwindigkeit ab.
TatsĂ€chlich können diese Operationen Ă€uĂerst effizient implementiert werden: Rang fĂŒr Rang O ( 1 ) ; WĂ€hlen Sie - fĂŒr O ( l o g ( l o g N ) ) , was auch fast konstant ist. Und natĂŒrlich ist es nicht ohne Tricks. Und da jede Arbeit mit einer komplizierten Handlung eine leichte Untertreibung enthalten muss, höre ich hier auf.
Interessante Fakten
Was ist die theoretische Untergrenze der belegten Ressourcen in Bezug auf die Informationstheorie? Angenommen, eine Datenstruktur speichert viel N Elemente. Um sie ohne Kollisionen zu identifizieren, benötigen Sie eine Anzahl von Bits, nicht weniger als X = l o g 2 N . X und es gibt diese sehr untere Grenze, die durch die Hartley-Formel bestimmt wird. In einigen speziellen FĂ€llen kann die Struktur mit Informationen ĂŒber die Art der gespeicherten Daten noch effizienter komprimiert werden.
Ist der prĂ€gnante String eine Datenstruktur? Es enthĂ€lt N Zeichen und endet mit einem Null-ASCII-Zeichen. Also dauert es N + 1 Orte, und daher ... sie ist prĂ€gnant und insbesondere implizit! Was uns zur nĂ€chsten Frage fĂŒhrt.
Sind alle prÀgnanten Strukturen gleich kompakt? Das prÀgnante Forschungsgebiet definiert bis zu drei Arten kompakter Strukturen mit unterschiedlicher rÀumlicher KomplexitÀt:
- kompakt - O ( N ) . Lineare KomplexitĂ€t von N - die Anzahl der gespeicherten Artikel. Die meisten "frei" in Bezug auf die Anforderungen fĂŒr die Komprimierung der Struktur.Sozusagen ein Warm-up vor einem echten Hardcore. Wenn es sich um Zeilen handelt, ist das folgende Beispiel geeignet: eine Folge von Zeilen variabler LĂ€nge. Zeichenfolgen werden nacheinander ohne Trennzeichen gespeichert. Um nach einzelnen Zeilen zu suchen, wird eine Bitmap gebildet, in der alle Bits mit Ausnahme von Bits mit Indizes, die dem Zeilenanfang entsprechen, auf 0 zurĂŒckgesetzt werden. Diese Struktur nimmtO ( 2 N ) (Der Faktor 2 bei der Verwendung von Landau-Symbolen wird am besten weggelassen, ist aber klarer) und ermöglicht die Implementierung einer Schnellauswahl, um den Anfang jeder Zeile in der Sequenz zu bestimmen.
- prĂ€gnant - N + o ( N ) . â , succinct data structures . : (Pascal string), . N + l o g ( N ) .
- implicit â N + O ( 1 ) . , . : (heap) . N + 1 .
? , , . succinct- . , -, FM-, RMQ (range minimum queries), LCP (longest common prefix), , succinct'. -.
Nachwort
, , . Und nicht nur. , , X, .
succinct â , «- ». Succinct â . , , succinct. , . (IME) Google, . MAPS.ME succinct- .
, . ., 97 % -: . 3 %.
Was weiter?
, succinct:
, :