Manchmal ist es einfach, die zeitliche Komplexität eines Algorithmus zu erkennen, wenn ich ihn sorgfältig untersuche. Algorithmen mit zwei verschachtelten Schleifen von sind offensichtlich . Algorithmen , die alle möglichen Kombinationen von explore Gruppen von zwei Werten ist offensichtlich .
Ich weiß jedoch nicht, wie ich einen Algorithmus mit -Komplexität "erkennen" soll . Eine rekursive Mergesort-Implementierung ist beispielsweise eine. Was sind die gemeinsamen Merkmale von Mergesort- oder anderen -Algorithmen, die mir einen Hinweis geben, wenn ich einen analysiere?
Ich bin mir sicher, dass es mehr als eine Möglichkeit gibt, wie ein Algorithmus von Komplexität sein kann. Daher sind alle Antworten willkommen. Übrigens suche ich allgemeine Eigenschaften und Tipps, keine strengen Beweise.
quelle
Antworten:
Ihr archetypisches ist ein Divide-and-Conquer- Algorithmus, der die Arbeit in linearer Zeit aufteilt (und neu kombiniert) und über die Teile rekursiert. So funktioniert das Sortieren beim Zusammenführen: O ( n ) Zeit damit verbringen , die Eingabe in zwei ungefähr gleiche Teile zu teilen, jedes Teil rekursiv zu sortieren und Θ ( n ) Zeit damit zu verbringen , die beiden sortierten Hälften zu kombinieren.Θ(nlogn) O(n) Θ(n)
Intuitiv setzt sich die Idee der Teilung und Eroberung fort und jede Teilungsstufe benötigt insgesamt eine lineare Zeit, da die Zunahme der Anzahl der zu teilenden Teile genau der Abnahme der Größe der Teile entspricht, da die durch die Teilung benötigte Zeit linear ist. Die Gesamtlaufzeit ergibt sich aus den Gesamtkosten einer Teilungsstufe multipliziert mit der Anzahl der Teilungsstufen. Da sich die Größe der Teile zu jedem Zeitpunkt halbiert, gibt es Teilungsstufen, so dass die Gesamtlaufzeit n ≤ log ( n ) beträgt . (Bis auf eine multiplikative Konstante ist die Basis des Logarithmus irrelevant.)log2(n) n⋅log(n)
Um die Laufzeit eines solchen Algorithmus in Gleichungen () umzurechnen, kann man sie rekursiv ausdrücken: T ( n ) = 2 T ( n / 2 ) + Θ ( n ) . Es ist klar, dass dieser Algorithmus mehr als lineare Zeit benötigt, und wir können sehen, wie viel mehr, wenn wir durch n dividieren : T ( n )T(n) T(n)=2T(n/2)+Θ(n) n
Wennn sichverdoppelt, nimmtT(n)/num einen konstanten Betrag zu:T(n)/nnimmt logarithmisch zu, oder mit anderen Worten,T(n)=Θ(nlogn).
Dies ist ein Beispiel für ein allgemeineres Muster: den Hauptsatz . Für jeden rekursive Algorithmus, der unterteilt ihre Eingabe der Größe in ein Stück mit einer Größe n / b , und nimmt eine Zeit f ( n ) die Spaltung und Rekombination, die Laufzeit genügt auszuführen T ( n ) = ein ⋅ T ( n / b ) + f ( n ) . Dies führt zu einer geschlossenen Form, die von den Werten von a und b und der Form von abhängtn a n/b f(n) T(n)=a⋅T(n/b)+f(n) a b . Wenn a = b und f ( n ) = Θ ( n ) ist , besagt der Hauptsatz, dass T ( n ) = Θ ( n log n ) ist .f a=b f(n)=Θ(n) T(n)=Θ(nlogn)
quelle
Two other categories of algorithms that takeΘ(nlogn) time:
Algorithms where each item is processed in turn, and it takes logarithmic time to process each item (e.g. HeapSort or many of the plane sweep computational geometry algorithms).
Algorithms where the running time is dominated by a sorting pre-processing step. (For example, in Kruskal's algorithm for minimum spanning tree, we may sort the edges by weight as the first step).
quelle
Eine andere Kategorie: Algorithmen, in denen die Ausgabe Größe hatΘ(nlogn) , and therefore Θ(nlogn) running time is linear in the output size.
Although the details of such algorithms often use divide-and-conquer techniques, they don't necessarily have to. The run-time fundamentally comes from the question being asked, and so I think it is worth mentioning separately.
This comes up in data structures which are based on an augmented binary search tree, where each node stores a linear size data structure to search over the leaves in that node's subtree. Such data structures come up often in geometric range searching, and are often based on a decomposition scheme. See Agarwal's Survey.
For a concrete example, consider the range-tree, built to answer two dimensional orthogonal range queries. Although the space was later reduced using some compression techniques to pack multiple objects into a single word, the textbook (and most intuitive) version of the data structure requiresO(nlogn) space (each leaf is stored in an auxiliary structure at each node on the path from the leaf to the root, or in O(logn) places), and the construction algorithm takes time linear in the space requirement.
quelle
A complexity ofO(nlogn) arises from divide and conquer algorithms which divide their input into k pieces of roughly equal size in time O(n) , operate on these pieces recursively, and then combine them in time O(n) . If you only operate on some of the pieces, the running time drops to O(n) .
quelle
Those are typically algorithms of the "divide and conquer" variety, where the cost of dividing and combining subsolutions isn't "too large". Take a look at this FAQ to see what kinds of recurrences give rise to this behaviour.
quelle
Typically, Divide-and-conquer algorithms will yield O(N log N) complexity.
quelle
A generic example of a loop (not an algorithm per se) that runs inO(nlogn) is the following:
(note that this nested loops are notO(n2) . Also note that it isn't divide-and-conquer nor recursive.)
I can't give a concrete example of an algorithm that uses this loop, but it comes up often when coding custom algorithms.
quelle