Hat std :: deque zu Beginn tatsächlich eine konstante Zeiteinfügung?

8

Der Standard sagt:

Eine Deque ist ein Sequenzcontainer, der Iteratoren mit wahlfreiem Zugriff unterstützt (27.2.7). Darüber hinaus werden Einfüge- und Löschvorgänge mit konstanter Zeit am Anfang oder am Ende unterstützt. Einfügen und Löschen in der Mitte dauert linear.

In derselben Klausel heißt es jedoch auch:

Alle Komplexitätsanforderungen in dieser Klausel werden ausschließlich in Bezug auf die Anzahl der Operationen an den enthaltenen Objekten angegeben. [Beispiel: Der Kopierkonstruktor vom Typ vector<vector<int>>hat eine lineare Komplexität, obwohl die Komplexität des Kopierens jedes enthaltenen vector<int>selbst linear ist. - Beispiel beenden]

Bedeutet dies nicht, dass das Einfügen zu Beginn beispielsweise deque<int>eine lineare Zeit in Anspruch nehmen darf, solange nicht mehr als eine konstante Anzahl von Operationen an den ints ausgeführt wird, die sich bereits in der Deque befinden, und an dem das neue intObjekt eingefügt wird ?

Angenommen, wir implementieren eine Deque unter Verwendung eines "Vektors der Größe-K-Vektoren". Es scheint, dass einmal alle K-mal, die wir am Anfang einfügen, am Anfang ein neuer Vektor der Größe K hinzugefügt werden muss, sodass alle anderen Vektoren der Größe K verschoben werden müssen. Dies würde bedeuten, dass die zeitliche Komplexität des Einfügens zu Beginn O (N / K) amortisiert wird, wobei N die Gesamtzahl der Elemente ist, K jedoch konstant ist, so dass dies nur O (N) ist. Es scheint jedoch, dass der Standard dies zulässt, da durch das Verschieben eines Vektors der Größe K keines seiner Elemente verschoben wird und die "Komplexitätsanforderungen" "nur in Bezug auf die Anzahl der Operationen" für die enthaltenen intObjekte angegeben werden.

Erlaubt der Standard dies wirklich? Oder sollten wir es als eine strengere Anforderung interpretieren, dh eine konstante Anzahl von Operationen an den enthaltenen Objekten plus eine konstante zusätzliche Zeit?

Brian
quelle
2
Ihre Interpretation klingt plausibel. Um dem Feuer mehr Treibstoff hinzuzufügen, hat [deque.modifiers] eine Einfügung in der Mitte der Deque macht alle Iteratoren und Verweise auf Elemente der Deque ungültig. Ein Einfügen an beiden Enden der Deque macht alle Iteratoren der Deque ungültig, hat jedoch keine Auswirkung auf die Gültigkeit von Verweisen auf Elemente der Deque. Was wie Ihr Vektorbeispiel funktioniert, da das Verschieben die Iteratoren, aber nicht die Referenzen ungültig macht.
NathanOliver
Ich denke, das Beispiel ist ein wenig verwirrend, weil es einmal "linear" für linear in Bezug auf die Anzahl der Elemente des, vector<vector<int>>aber dann linear in Bezug auf die Elemente des Inneren verwendet vector<int>. Wenn Sie nur die Anzahl der Elemente des äußeren Vektors berücksichtigen, würde ich das Kopieren des inneren Vektors als konstant betrachten, obwohl ich mich möglicherweise irre, ist es hier schon spät
idclev 463035818

Antworten:

2

Angenommen, wir implementieren eine Deque unter Verwendung eines "Vektors der Größe-K-Vektoren".

Das wäre keine gültige Implementierung. Das Einfügen an der Vorderseite eines vectorungültig macht alle Zeiger / Referenzen im Container. dequeist erforderlich, um keine Zeiger / Referenzen beim Einfügen von vorne ungültig zu machen .

Aber lassen Sie uns das jetzt ignorieren.

Es scheint jedoch, dass dies vom Standard zugelassen wird, da durch das Verschieben eines Vektors der Größe K keines seiner Elemente verschoben wird und die "Komplexitätsanforderungen" "nur in Bezug auf die Anzahl der Operationen" für die enthaltenen int-Objekte angegeben werden .

Ja, das wäre erlaubt. In der Tat sind reale Implementierungen von dequenicht so unähnlich (obwohl sie std::vectorsich aus offensichtlichen Gründen nicht selbst verwenden). Der Grundriss einer dequeImplementierung besteht aus einer Reihe von Zeigern auf Blöcke (mit Platz für Wachstum sowohl vorne als auch hinten), wobei jeder Block bis zu X Elemente sowie Zeiger auf die nächsten / vorherigen Blöcke enthält (um einzelne Blöcke zu erstellen). Elementiteration schnell).

Wenn Sie vorne oder hinten genügend Elemente einfügen, muss das Array der Blockzeiger wachsen. Dies erfordert eine Operation, die eine lineare Zeit im Verhältnis zur Anzahl der Elemente in der ist deque, aber nicht tatsächlich für die Elemente selbst ausgeführt wird, sodass sie nicht zählt. Die Spezifikation hat nichts über die Komplexität dieser Operation zu sagen.

Ohne diese Bestimmung bin ich mir nicht sicher, ob der einzigartige Funktionsumfang deque(schnelles Einfügen von Vorder- und Rückseite und Direktzugriff) implementiert werden kann.

Nicol Bolas
quelle
"Eine Einfügung an beiden Enden der Deque macht alle Iteratoren der Deque ungültig, hat jedoch keinen Einfluss auf die Gültigkeit von Verweisen auf Elemente der Deque."
Brian
Offensichtlich dauert das Einsetzen vorne oder hinten manchmal linear. Wenn das Array von Zeigern tatsächlich vorne Platz hat, sollte die Zeit konstant abgeschrieben werden. Wenn vorne kein Platz vorhanden ist, wird die Zeit nicht konstant abgeschrieben. Ich frage, ob letzteres erlaubt ist.
Brian
@Brian: " Aber wenn das Array von Zeigern tatsächlich vorne Platz hat, sollte die Zeit konstant amortisiert werden. " Das bedeutet nicht " amortisierte Konstante ". Dies bedeutet, dass der Abstand zwischen den Ns, wenn er nicht konstant sein muss, immer größer wird. Wenn durch vectordie Implementierung der automatischen Größenänderung nur eine konstante Anzahl zusätzlicher Elemente hinzugefügt würde, wäre das Einfügen keine "amortisierte Konstante". Es geht also nicht nur darum, Platz zu haben. Sie müssen jedes Mal mehr Platz einfügen .
Nicol Bolas
@Brian: Und ja, es gibt nichts über die dequeAnforderungen, dass jede Einfügung in Bezug auf die Anzahl der Elemente im Container "amortisiert konstant" ist. Sie ist nur in Bezug auf die Anzahl der Operationen an Elementen im Container konstant.
Nicol Bolas
"Amortisierte Konstante" bedeutet, dass es eine Konstante c gibt, so dass, wenn Sie N Operationen ausführen, die Gesamtzeit höchstens cN beträgt. Wie ich bereits sagte, wenn die Deque vorne keinen Platz hält, können Einfügungen vorne offensichtlich nicht konstant abgeschrieben werden. Wenn die Deque vorne genügend Platz hat (mehr als eine konstante Menge, wie Sie sagen), ist es möglich, dass sie konstant abgeschrieben wird. In beiden Fällen ist sie jedoch in Bezug auf Operationen an Elementen konstant.
Brian
1

Ich denke, Sie erreichen ein wenig, wie Sie die Bedeutung von Komplexitätsdomänen interpretieren. Sie versuchen, zwischen "linearer Zeit" und "linearer Komplexität" zu unterscheiden, von der ich nicht überzeugt bin, dass sie viel Sinn macht.

Der Standard ist klar, dass das Einfügen an der Vorderseite zeitlich konstant ist, und ich denke, wir sind uns alle einig. Die letztere Passage sagt uns nur, dass das, was jede dieser "konstanten" Mengen von Operationen darunter beinhaltet, einfach nicht durch den Standard spezifiziert oder eingeschränkt wird.

Und das ist nicht ungewöhnlich. Jeder Algorithmus arbeitet auf einer gewissen Abstraktionsbasis. Selbst wenn wir einen Algorithmus schreiben würden, der auf einzelne Maschinenanweisungen hinausläuft, und wir sagten, dass immer nur N Maschinenanweisungen von unserem Algorithmus generiert wurden, würden wir nicht untersuchen, welche Art von Komplexität jede einzelne Komplexität im Prozessor und hat Fügen Sie das zu unseren Ergebnissen hinzu. Wir würden nicht sagen, dass einige Operationen auf quantenmolekularer Ebene mehr bewirken und unser O (n) -Algorithmus daher tatsächlich O (N × M 3 ) oder so ist. Wir haben uns entschieden, diese Abstraktionsebene nicht zu berücksichtigen. Und wenn diese Komplexität nicht von den Eingaben des Algorithmus abhängt, ist das völlig in Ordnung.

In Ihrem Fall ist die Größe der verschobenen / kopierten inneren Vektoren nicht wirklich relevant, da sich diese nicht von Natur aus ändern, wenn die Deque wächst. Die Anzahl der inneren Vektoren reicht aus, aber die Größe jedes einzelnen ist eine unabhängige Eigenschaft. Daher ist es irrelevant, die Komplexität des Einfügens eines neuen Elements in den äußeren Vektor zu beschreiben.

Variiert die tatsächliche Ausführungszeit (die selbst in einigen algorithmischen Begriffen beschrieben werden könnte, wenn Sie dies wünschen) in Abhängigkeit vom Inhalt der kopierten inneren Vektoren? Ja natürlich. Dies hat jedoch nichts damit zu tun, wie die Aufgabe, den äußeren Vektor zu erweitern, in der Arbeitslast wächst, wenn der äußere Vektor selbst wächst.

Hier sagt der Standard also, dass er nicht N oder N 2 kopiert oder sogar N innere Vektoren protokolliert , wenn Sie einen anderen vorne platzieren. Es heißt, dass sich die Anzahl dieser Operationen nicht ändern wird, wenn Ihre Deque wächst. Es heißt auch, dass es für die Zwecke dieser Regel egal ist, was das Kopieren / Verschieben der inneren Vektoren tatsächlich beinhaltet oder wie groß sie sind.

Bei der Komplexitätsanalyse geht es nicht um Leistung. Bei der Komplexitätsanalyse geht es darum, wie die Leistung skaliert.

Asteroiden mit Flügeln
quelle
Bootnote: Wenn Sie wirklich wollen, können Sie die Komplexität der Operation als nicht O (1) , sondern als O (A + B + C + D + E + F + ...) beschreiben, wobei sich diese inneren Variablen auf die Größe der beziehen betroffene vorhandene Elemente oder eine andere Funktion von ihnen ... aber das wäre (a) nicht nützlich, (b) neben dem Punkt der Regel, der in dieser Passage definiert wird, und (c) Zeitverschwendung seit dem Standard hat uns bereits ziemlich genau mitgeteilt, dass es keine Auswirkungen auf diese vorhandenen Elemente geben wird.
Asteroiden mit Flügeln