Aufrechterhaltung einer effizienten Bestellung, bei der Sie Elemente „zwischen“ zwei anderen Elementen in die Bestellung einfügen können?

8

Stellen Sie sich vor, ich habe eine Bestellung für eine Reihe von Elementen wie folgt:

Geben Sie hier die Bildbeschreibung ein

Wo ein Pfeil bedeutet . Es ist auch transitiv: .XY( X < Y )( Y < Z )X<Y(X<Y)(Y<Z)(X<Z)

Um Anfragen wie effizient zu beantworten , ist eine Art Beschriftung oder Datenstruktur erforderlich. Sie können beispielsweise die Knoten von links nach rechts zählen könnte, und daher kann man einfach integer Vergleich tun , um die Abfrage zu beantworten: . Es würde ungefähr so ​​aussehen:A ? < DA<?DA<?D1<4T

Geben Sie hier die Bildbeschreibung ein

Wobei die Nummer die Bestellung ist und der Buchstabe nur ein Name ist.

Was aber, wenn Sie Elemente "zwischen" zwei anderen Elementen in die Reihenfolge einfügen müssen, wie folgt:

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

Wie können Sie eine solche Bestellung aufrechterhalten? Bei einfacher Nummerierung stößt man auf das Problem, dass keine Ganzzahlen "zwischen" zu verwenden sind.2,3

Realz Slaw
quelle

Antworten:

7

Dies ist als "Auftragspflege" -Problem bekannt . Es gibt eine relativ einfache Lösung, bei der sowohl für Abfragen als auch für Einfügungen eine Amortisationszeit von . Mit "relativ einfach" meine ich, dass Sie einige Bausteine ​​verstehen müssen, aber dass der Rest nicht schwer zu sehen ist, wenn Sie diese erhalten.O(1)

http://courses.csail.mit.edu/6.851/spring12/lectures/L08.html

Die Grundidee ist eine zweistufige Datenstruktur. Die oberste Ebene ist wie die AVL-Baumlösung von Realz Slaw, aber

  • Die Knoten sind direkt mit Bitfolgen der Länge in einer Reihenfolge gekennzeichnet, die ihrer Reihenfolge im Baum entspricht. Der Vergleich dauert also konstantO(lgn)

  • Ein Baum mit weniger Rotationen als ein AVL-Baum wird verwendet, z. B. ein Sündenbockbaum oder ein Baum mit Gewichtsausgleich, sodass Neukennzeichnungen seltener vorkommen.

Die unterste Ebene sind die Blätter des Baumes. Diese Ebene verwendet die gleiche Länge von Beschriftungen, , enthält jedoch nur Elemente in jedem Blatt in einer einfachen verknüpften Liste. Dies gibt Ihnen genug zusätzliche Bits, um aggressiv neu zu kennzeichnen.O ( lg n )O(lgn)O(lgn)

Die Blätter werden mit jedem -Einsatz zu groß oder zu klein , was zu einer Änderung der obersten Ebene führt, die die amortisierte -Zeit ( Worst-Case-Zeit) benötigt. Amortisiert ist dies nur .O ( lg n ) Ω ( n ) O ( 1 )O(lgn)O(lgn)Ω(n)O(1)

Es gibt viel komplexere Strukturen für die Durchführung von Aktualisierungen in Worst-Case-Zeit.O(1)

jbapple
quelle
7

Anstelle einer einfachen Nummerierung können Sie die Zahlen auch über einen großen Bereich (konstante Größe) verteilen, z. B. das Minimum und das Maximum einer Ganzzahl der CPU. Dann können Sie weiterhin Zahlen "dazwischen" setzen, indem Sie die beiden umgebenden Zahlen mitteln. Wenn die Zahlen zu voll werden (z. B. wenn Sie zwei benachbarte Ganzzahlen haben und keine Zahl dazwischen liegt), können Sie die gesamte Reihenfolge einmalig neu nummerieren und die Zahlen gleichmäßig über den Bereich verteilen.

Natürlich können Sie auf die Einschränkung stoßen, dass alle Zahlen im Bereich der großen Konstante verwendet werden. Erstens ist dies normalerweise kein Problem, da die Ganzzahlgröße auf einem Computer groß genug ist, sodass sie wahrscheinlich ohnehin nicht in den Speicher passt, wenn Sie mehr Elemente hätten. Wenn es sich jedoch um ein Problem handelt, können Sie sie einfach mit einem größeren ganzzahligen Bereich neu nummerieren.

Wenn die Eingabereihenfolge nicht pathologisch ist, werden durch diese Methode möglicherweise die Umnummerierungen abgeschrieben.

Fragen beantworten

Ein einfacher ganzzahliger Vergleich kann die Abfrage beantworten .(X<?Y)

Die Abfragezeit wäre sehr schnell ( ), wenn Maschinen-Ganzzahlen verwendet werden, da es sich um einen einfachen Ganzzahl-Vergleich handelt. Die Verwendung eines größeren Bereichs würde größere Ganzzahlen erfordern, und der Vergleich würde erfordern .O ( log | i n t e g e r | )O(1)O(log|integer|)

Einfügen

Zunächst würden Sie die verknüpfte Liste der Bestellung pflegen, die in der Frage gezeigt wird. Das Einfügen hier wäre angesichts der Knoten, zwischen denen das neue Element platziert werden soll, .O(1)

Das Beschriften des neuen Elements erfolgt normalerweise schnell da Sie die neue Zahl einfach berechnen können, indem Sie die umgebenden Zahlen mitteln. Gelegentlich gehen Ihnen möglicherweise die Zahlen "dazwischen" aus, wodurch die Prozedur zur Umnummerierung der Zeit ausgelöst wird.O(1)O(n)

Umnummerierung vermeiden

Sie können Floats anstelle von Ganzzahlen verwenden. Wenn Sie also zwei "benachbarte" Ganzzahlen erhalten, können diese gemittelt werden. So können Sie eine Umnummerierung vermeiden, wenn Sie mit zwei ganzzahligen Gleitkommazahlen konfrontiert sind: Teilen Sie sie einfach in zwei Hälften. Schließlich wird der Gleitkommatyp jedoch nicht mehr genau genug sein und zwei "benachbarte" Gleitkommazahlen können nicht gemittelt werden (der Durchschnitt der umgebenden Zahlen wird wahrscheinlich gleich einer der umgebenden Zahlen sein).

Sie können auch eine Ganzzahl mit "Dezimalstelle" verwenden, bei der Sie zwei Ganzzahlen für ein Element verwalten. eine für die Zahl und eine für die Dezimalstelle. Auf diese Weise können Sie eine Umnummerierung vermeiden. Die Dezimalzahl wird jedoch möglicherweise überlaufen.

Durch die Verwendung einer Liste von Ganzzahlen oder Bits für jedes Etikett kann die Umnummerierung vollständig vermieden werden. Dies entspricht im Wesentlichen der Verwendung von Dezimalzahlen mit unbegrenzter Länge. Der Vergleich würde lexikographisch durchgeführt, und die Vergleichszeiten erhöhen sich auf die Länge der beteiligten Listen. Dies kann jedoch die Kennzeichnung aus dem Gleichgewicht bringen. Einige Beschriftungen erfordern möglicherweise nur eine Ganzzahl (keine Dezimalstelle), andere haben möglicherweise eine Liste mit langer Länge (lange Dezimalstellen). Dies ist ein Problem, und auch hier kann eine Umnummerierung hilfreich sein, indem die Nummerierung (hier Listen mit Nummern) gleichmäßig über einen ausgewählten Bereich verteilt wird (Bereich bedeutet hier möglicherweise Länge von Listen), sodass die Listen nach einer solchen Umnummerierung alle gleich lang sind .


Diese Methode wird tatsächlich in diesem Algorithmus verwendet ( Implementierung , relevante Datenstruktur ); Im Verlauf des Algorithmus muss eine beliebige Reihenfolge eingehalten werden, und der Autor verwendet dazu Ganzzahlen und Umnummerierungen.


Wenn Sie versuchen, sich an Zahlen zu halten, ist Ihr Schlüsselplatz etwas eingeschränkt. Man könnte stattdessen Zeichenfolgen variabler Länge verwenden, wobei die Vergleichslogik "a" <"ab" <"b" verwendet wird. Es müssen noch zwei Probleme gelöst werden: A. Schlüssel können beliebig lang werden. B. Der Vergleich langer Schlüssel kann kostspielig werden

Realz Slaw
quelle
3

Sie können einen AVL-Baum ohne Schlüssel oder ähnliches verwalten.

Es würde wie folgt funktionieren: Der Baum behält eine Reihenfolge auf den Knoten bei, wie es ein AVL-Baum normalerweise tut, aber anstatt dass der Schlüssel bestimmt, wo der Knoten "liegen" soll, gibt es keine Schlüssel, und Sie müssen die Knoten explizit "nach" einfügen "ein anderer Knoten (oder mit anderen Worten" zwischen "zwei Knoten), wobei" nach "bedeutet, dass er in der Reihenfolge des Durchlaufs des Baums nach ihm kommt. Der Baum behält somit die Reihenfolge für Sie auf natürliche Weise bei und würde sich aufgrund der eingebauten Rotationen der AVL auch ausgleichen. Dadurch wird alles automatisch gleichmäßig verteilt.

Einfügen

Zusätzlich zum regelmäßigen Einfügen in die Liste, wie in der Frage gezeigt, würden Sie einen separaten AVL-Baum pflegen. Das Einfügen in die Liste selbst erfolgt , da Sie die Knoten "before" und "after" haben.O(1)

Die Einfügezeit in den Baum beträgt , genau wie das Einfügen in einen AVL-Baum. Beim Einfügen wird ein Verweis auf den Knoten erstellt, nach dem Sie einfügen möchten, und Sie fügen den neuen Knoten einfach links vom linken Knoten des rechten untergeordneten Knotens ein. Dieser Ort ist "nächster" in der Reihenfolge des Baums (er ist der nächste in der Reihenfolge der Durchquerung). Führen Sie dann die typischen AVL-Rotationen aus, um den Baum neu auszugleichen. Sie können einen ähnlichen Vorgang für "Einfügen vor" ausführen. Dies ist hilfreich, wenn Sie etwas in den Anfang der Liste einfügen müssen und kein Knoten "vor" vorhanden ist.O(logn)

Fragen beantworten

Um Fragen zu zu beantworten , finden Sie einfach alle Vorfahren von und im Baum und analysieren die Position, an der die Vorfahren im Baum voneinander abweichen. derjenige, der nach "links" abweicht, ist der kleinere von beiden.(X<?Y)XY

Diese Prozedur benötigt Zeit, um den Baum zur Wurzel zu klettern und die Ahnenlisten zu erhalten. Es ist zwar wahr, dass dies langsamer als ein ganzzahliger Vergleich erscheint, aber die Wahrheit ist, dass es dasselbe ist; Nur dieser ganzzahlige Vergleich auf einer CPU wird durch eine große Konstante begrenzt, um sie . Wenn Sie diese Konstante überlaufen lassen, müssen Sie mehrere Ganzzahlen ( Ganzzahlen) beibehalten und dasselbe Vergleiche. Alternativ können Sie die Baumhöhe um einen konstanten Betrag "binden" und auf die gleiche Weise "betrügen" wie die Maschine mit ganzen Zahlen: Jetzt werden Abfragen als .O(logn)O(1)O(logn)O(logn)O(1)

Demonstration des Einfügevorgangs

Zur Demonstration können Sie einige Elemente mit ihrer Reihenfolge aus der Liste in die Frage einfügen:

Schritt 1

Beginnen Sie mitD

Liste:

Listen Sie Schritt 1 auf

Baum:

Baum Schritt 1

Schritt 2

Fügen Sie , .C<C<D

Liste:

Listen Sie Schritt 2 auf

Baum:

Baum Schritt 2

Beachten Sie, dass Sie explizit vor , nicht weil der Buchstabe C vor D steht, sondern weil in der Liste steht.CDC<D

Schritt 3

Fügen Sie , .A<A<C

Liste:

Listen Sie Schritt 3 auf

Baum:

Baum Schritt 3 vor der Drehung

AVL-Drehung:

Baum Schritt 3 nach der Drehung

Schritt 4

Fügen Sie , .A < B < CBA<B<C

Liste:

Listen Sie Schritt 4 auf

Baum:

Baum Schritt 4

Keine Rotationen notwendig.

Schritt 5

Fügen Sie ,D < E < ED<E<

Liste:

Listen Sie Schritt 5 auf

Baum:

Baum Schritt 5

Schritt 6

Fügen Sie ,B < F < CFB<F<C

BBFB

Liste:

Listen Sie Schritt 6 auf

Baum:

Baum Schritt 6 vor der Drehung

AVL-Rotation:

Baum Schritt 6 nach der Drehung

Demonstration der Vergleichsoperation

A<?F

ancestors(A) = [C,B]
ancestors(F) = [C,B]
last_common_ancestor = B
B.left = A
B.right = F
... A < F #left is less than right

D<?F

ancestors(D) = [C]
ancestors(F) = [C,B]
last_common_ancestor = C
C.left = D
C.right = B #next ancestor for F is to the right
... D < F #left is less than right

B<?A

ancestors(B) = [C]
ancestors(A) = [B,C]
last_common_ancestor = B
B.left = A
... A < B #left is always less than parent

Graphquellen

Realz Slaw
quelle
@saadtaame behoben und unten Punktdateiquellen hinzugefügt. Vielen Dank für den Hinweis.
Realz Slaw