Mein Wissen über Big-O ist begrenzt, und wenn Log-Terme in der Gleichung auftauchen, stört mich das noch mehr.
Kann mir vielleicht jemand in einfachen Worten erklären, was ein O(log n)
Algorithmus ist? Woher kommt der Logarithmus?
Dies kam speziell auf, als ich versuchte, diese mittelfristige Übungsfrage zu lösen:
X (1..n) und Y (1..n) enthalten zwei Listen von ganzen Zahlen, die jeweils in nicht absteigender Reihenfolge sortiert sind. Geben Sie einen O (log n) -Zeitalgorithmus an, um den Median (oder die n-te kleinste Ganzzahl) aller 2n kombinierten Elemente zu ermitteln. Zum Beispiel ist X = (4, 5, 7, 8, 9) und Y = (3, 5, 8, 9, 10), dann ist 7 der Median der kombinierten Liste (3, 4, 5, 5, 7) 8, 8, 9, 9, 10). [Hinweis: Konzepte der binären Suche verwenden]
quelle
O(log n)
kann gesehen werden als: Wenn Sie die Problemgröße verdoppelnn
, benötigt Ihr Algorithmus nur eine konstante Anzahl von Schritten mehr.Antworten:
Ich muss zustimmen, dass es ziemlich seltsam ist, wenn Sie zum ersten Mal einen O (log n) -Algorithmus sehen ... woher kommt dieser Logarithmus? Es stellt sich jedoch heraus, dass es verschiedene Möglichkeiten gibt, einen Protokollbegriff in Big-O-Notation anzuzeigen. Hier sind ein paar:
Wiederholt durch eine Konstante teilen
Nimm eine beliebige Zahl n; sagen wir, 16. Wie oft können Sie n durch zwei teilen, bevor Sie eine Zahl kleiner oder gleich eins erhalten? Für 16 haben wir das
Beachten Sie, dass dies in vier Schritten abgeschlossen werden muss. Interessanterweise haben wir auch das log 2 16 = 4. Hmmm ... was ist mit 128?
Dies dauerte sieben Schritte und log 2 128 = 7. Ist das ein Zufall? Nee! Dafür gibt es einen guten Grund. Angenommen, wir teilen eine Zahl n durch 2 i-mal. Dann erhalten wir die Zahl n / 2 i . Wenn wir nach dem Wert von i auflösen wollen, wobei dieser Wert höchstens 1 ist, erhalten wir
Mit anderen Worten, wenn wir eine ganze Zahl i so wählen, dass i ≥ log 2 n ist, dann haben wir nach dem Teilen von n in zwei i-Hälften einen Wert, der höchstens 1 beträgt. Das kleinste i, für das dies garantiert ist, ist ungefähr log 2 n Wenn wir also einen Algorithmus haben, der durch 2 dividiert, bis die Zahl ausreichend klein wird, können wir sagen, dass er in O (log n) -Schritten endet.
Ein wichtiges Detail ist, dass es keine Rolle spielt, durch welche Konstante Sie n teilen (solange sie größer als eins ist). Wenn Sie durch die Konstante k dividieren, sind log k n Schritte erforderlich, um 1 zu erreichen. Daher benötigt jeder Algorithmus, der die Eingabegröße wiederholt durch einen Bruchteil dividiert, O (log n) -Iterationen, um zu beenden. Diese Iterationen können viel Zeit in Anspruch nehmen, sodass die Nettolaufzeit nicht O (log n) sein muss, sondern die Anzahl der Schritte logarithmisch ist.
Wo kommt das her? Ein klassisches Beispiel ist die binäre Suche , ein schneller Algorithmus zum Durchsuchen eines sortierten Arrays nach einem Wert. Der Algorithmus funktioniert folgendermaßen:
Zum Beispiel, um im Array nach 5 zu suchen
Wir würden uns zuerst das mittlere Element ansehen:
Da 7> 5 und das Array sortiert ist, wissen wir, dass die Zahl 5 nicht in der hinteren Hälfte des Arrays liegen kann, sodass wir sie einfach verwerfen können. Diese Blätter
Nun schauen wir uns hier das mittlere Element an:
Da 3 <5 ist, wissen wir, dass 5 nicht in der ersten Hälfte des Arrays erscheinen kann, also können wir das Array der ersten Hälfte zum Verlassen werfen
Wieder schauen wir uns die Mitte dieses Arrays an:
Da dies genau die Zahl ist, nach der wir suchen, können wir berichten, dass sich tatsächlich 5 im Array befindet.
Wie effizient ist das? Nun, bei jeder Iteration werfen wir mindestens die Hälfte der verbleibenden Array-Elemente weg. Der Algorithmus stoppt, sobald das Array leer ist oder wir den gewünschten Wert finden. Im schlimmsten Fall ist das Element nicht vorhanden, daher halbieren wir die Größe des Arrays so lange, bis uns die Elemente ausgehen. Wie lange dauert das? Nun, da wir das Array immer wieder halbieren, werden wir höchstens in O (log n) -Iterationen ausgeführt, da wir das Array nicht mehr als O (log n) -Zeiten halbieren können, bevor wir es ausführen aus Array-Elementen.
Algorithmen, die der allgemeinen Technik des Teilens und Eroberens folgen (das Problem in Stücke schneiden, diese Teile lösen und dann das Problem wieder zusammensetzen), enthalten aus demselben Grund logarithmische Terme - Sie können ein Objekt nicht weiter einschneiden halb mehr als O (log n) mal. Vielleicht möchten Sie die Zusammenführungssortierung als gutes Beispiel dafür betrachten.
Werte jeweils eine Ziffer verarbeiten
Wie viele Ziffern enthält die Basis-10-Nummer n? Nun, wenn die Zahl k Ziffern enthält, dann haben wir die größte Ziffer als Vielfaches von 10 k . Die größte k-stellige Zahl ist 999 ... 9, k-mal, und dies ist gleich 10 k + 1 - 1. Wenn wir also wissen, dass n k Ziffern enthält, dann wissen wir, dass der Wert von n ist höchstens 10 k + 1 - 1. Wenn wir nach k in n auflösen wollen, erhalten wir
Daraus ergibt sich, dass k ungefähr der Logarithmus zur Basis 10 von n ist. Mit anderen Worten ist die Anzahl der Stellen in n O (log n).
Lassen Sie uns zum Beispiel über die Komplexität des Hinzufügens von zwei großen Zahlen nachdenken, die zu groß sind, um in ein Maschinenwort zu passen. Angenommen, wir haben diese Zahlen in Basis 10 dargestellt und nennen die Zahlen m und n. Eine Möglichkeit, sie hinzuzufügen, besteht in der Grundschulmethode: Schreiben Sie die Zahlen jeweils eine Ziffer aus und arbeiten Sie dann von rechts nach links. Um beispielsweise 1337 und 2065 hinzuzufügen, schreiben wir zunächst die Zahlen als
Wir addieren die letzte Ziffer und tragen die 1:
Dann addieren wir die vorletzte ("vorletzte") Ziffer und tragen die 1:
Als nächstes fügen wir die vorletzte ("vorletzte") Ziffer hinzu:
Schließlich fügen wir die vorletzte Ziffer ("preantepenultimate" ... ich liebe Englisch) hinzu:
Wie viel Arbeit haben wir getan? Wir erledigen insgesamt O (1) Arbeit pro Ziffer (dh eine konstante Menge an Arbeit), und es gibt O (max {log n, log m}) Gesamtziffern, die verarbeitet werden müssen. Dies ergibt eine Gesamtkomplexität von O (max {log n, log m}), da wir jede Ziffer in den beiden Zahlen besuchen müssen.
Viele Algorithmen erhalten einen O (log n) -Term, wenn sie in einer Basis jeweils eine Ziffer arbeiten. Ein klassisches Beispiel ist die Radix-Sortierung , bei der Ganzzahlen jeweils eine Ziffer sortiert werden. Es gibt viele Varianten der Radix-Sortierung, aber sie laufen normalerweise in der Zeit O (n log U), wobei U die größtmögliche Ganzzahl ist, die sortiert wird. Der Grund dafür ist, dass jeder Durchgang der Sortierung O (n) Zeit benötigt und insgesamt O (log U) -Iterationen erforderlich sind, um jede der O (log U) -Ziffern der größten zu sortierenden Zahl zu verarbeiten. Viele fortschrittliche Algorithmen, wie Gabows Algorithmus für kürzeste Wege oder die Skalierungsversion des Ford-Fulkerson-Max-Flow-Algorithmus , weisen in ihrer Komplexität einen Protokollbegriff auf, da sie jeweils eine Ziffer bearbeiten.
Bei Ihrer zweiten Frage, wie Sie dieses Problem lösen, sollten Sie sich diese verwandte Frage ansehen, in der eine fortgeschrittenere Anwendung untersucht wird. Angesichts der allgemeinen Struktur der hier beschriebenen Probleme können Sie jetzt besser verstehen, wie Sie über Probleme nachdenken, wenn Sie wissen, dass das Ergebnis einen Protokollbegriff enthält. Ich würde daher davon abraten, die Antwort zu prüfen, bis Sie sie gegeben haben einige Gedanken.
Hoffe das hilft!
quelle
Wenn wir über Big-Oh-Beschreibungen sprechen, sprechen wir normalerweise über die Zeit , die benötigt wird, um Probleme einer bestimmten Größe zu lösen . Und für einfache Probleme wird diese Größe normalerweise nur durch die Anzahl der Eingabeelemente charakterisiert, und das wird normalerweise als n oder N bezeichnet. (Offensichtlich ist das nicht immer der Fall - Probleme mit Graphen werden oft durch die Anzahl der Eckpunkte V und charakterisiert Anzahl der Kanten, E; aber im Moment werden wir über Listen von Objekten sprechen, mit N Objekten in den Listen.)
Wir sagen, dass ein Problem "groß ist - Oh von (irgendeine Funktion von N)", wenn und nur wenn :
Für alle N> einige beliebige N_0 gibt es eine Konstante c, so dass die Laufzeit des Algorithmus kleiner als diese Konstante c-mal ist (eine Funktion von N.)
Mit anderen Worten, denken Sie nicht an kleine Probleme, bei denen der "ständige Aufwand" beim Einrichten des Problems von Bedeutung ist, sondern an große Probleme. Und wenn es um große Probleme, Big-Oh (einige Funktion von N) Mittel zu denken , dass die Laufzeit ist noch immer weniger als einige konstanten Zeiten diese Funktion. Immer.
Kurz gesagt, diese Funktion ist eine Obergrenze bis zu einem konstanten Faktor.
"Big-Oh von log (n)" bedeutet also dasselbe, was ich oben gesagt habe, außer dass "eine Funktion von N" durch "log (n)" ersetzt wird.
Ihr Problem fordert Sie also auf, über die binäre Suche nachzudenken. Denken wir also darüber nach. Nehmen wir an, Sie haben beispielsweise eine Liste von N Elementen, die in aufsteigender Reihenfolge sortiert sind. Sie möchten herausfinden, ob in dieser Liste eine bestimmte Nummer vorhanden ist. Eine Möglichkeit , das zu tun , das ist nicht eine binäre Suche ist nur jedes Element der Liste zu scannen und sehen , ob es Ihre Zielnummer ist. Sie könnten Glück haben und es beim ersten Versuch finden. Im schlimmsten Fall überprüfen Sie jedoch N zu verschiedenen Zeiten. Dies ist keine binäre Suche, und es ist kein großes Oh von log (N), da es keine Möglichkeit gibt, es in die oben skizzierten Kriterien zu zwingen.
Sie können diese beliebige Konstante als c = 10 auswählen. Wenn Ihre Liste N = 32 Elemente enthält, ist dies in Ordnung: 10 * log (32) = 50, was größer als die Laufzeit von 32 ist. Wenn jedoch N = 64 , 10 * log (64) = 60, was weniger als die Laufzeit von 64 ist. Sie können c = 100 oder 1000 oder eine Unmenge auswählen, und Sie werden immer noch einige N finden, die diese Anforderung verletzen. Mit anderen Worten, es gibt kein N_0.
Wenn wir jedoch eine binäre Suche durchführen, wählen wir das mittlere Element aus und führen einen Vergleich durch. Dann werfen wir die Hälfte der Zahlen weg und machen es immer wieder und so weiter. Wenn Ihr N = 32 ist, können Sie dies nur ungefähr fünfmal tun, was log (32) ist. Wenn Ihr N = 64, können Sie dies nur tun , etwa 6 - mal, etc. Nun Sie können diese beliebige Konstante c holen, so dass die Anforderung immer für große Werte von N. erfüllt ist
Bei all dem Hintergrund bedeutet O (log (N)) normalerweise, dass Sie eine Möglichkeit haben, eine einfache Sache zu tun, die Ihre Problemgröße halbiert. Genau wie bei der binären Suche oben. Sobald Sie das Problem halbiert haben, können Sie es immer wieder halbieren. Entscheidend ist jedoch, dass Sie keinen Vorverarbeitungsschritt ausführen können, der länger als diese O-Zeit (log (N)) dauern würde. So können Sie beispielsweise Ihre beiden Listen nicht in eine große Liste mischen, es sei denn, Sie finden einen Weg, dies auch in der Zeit O (log (N)) zu tun.
(HINWEIS: Fast immer bedeutet Log (N) Log-Base-Two, was ich oben annehme.)
quelle
In der folgenden Lösung werden alle Zeilen mit einem rekursiven Aufruf auf der Hälfte der angegebenen Größen der Unterarrays von X und Y ausgeführt. Andere Zeilen werden in einer konstanten Zeit ausgeführt. Die rekursive Funktion ist T (2n) = T (2n / 2) + c = T (n) + c = O (lg (2n)) = O (lgn).
Sie beginnen mit MEDIAN (X, 1, n, Y, 1, n).
quelle
Der Log-Begriff taucht in der Analyse der Algorithmuskomplexität sehr häufig auf. Hier einige Erklärungen:
1. Wie repräsentieren Sie eine Zahl?
Nehmen wir die Nummer X = 245436. Diese Notation von "245436" enthält implizite Informationen. Diese Informationen explizit machen:
Welches ist die Dezimalerweiterung der Zahl. Die Mindestmenge an Informationen, die wir zur Darstellung dieser Zahl benötigen, beträgt 6 Ziffern. Dies ist kein Zufall, da jede Zahl unter 10 ^ d in d- Ziffern dargestellt werden kann.
Wie viele Ziffern sind also erforderlich, um X darzustellen? Das entspricht dem größten Exponenten von 10 in X plus 1.
Beachten Sie auch, dass dies die präziseste Art ist, die Nummer in diesem Bereich zu kennzeichnen. Jede Reduzierung führt zu Informationsverlust, da eine fehlende Ziffer 10 anderen Zahlen zugeordnet werden kann. Beispiel: 12 * kann 120, 121, 122,…, 129 zugeordnet werden.
2. Wie sucht man in (0, N - 1) nach einer Zahl?
Mit N = 10 ^ d verwenden wir unsere wichtigste Beobachtung:
Dies bedeutet, dass bei der Suche nach einer Zahl in der Ganzzahlzeile zwischen 0 und N - 1 mindestens log (N) versucht, sie zu finden. Warum? Jeder Suchalgorithmus muss bei der Suche nach der Nummer eine Ziffer nach der anderen auswählen.
Die Mindestanzahl an Stellen, die ausgewählt werden muss, ist log (N). Daher ist die minimale Anzahl von Operationen, die ausgeführt werden, um nach einer Zahl in einem Raum der Größe N zu suchen, log (N).
Können Sie die Komplexität der binären Suche, der ternären Suche oder der Deka-Suche erraten?
Sein O (log (N))!
3. Wie sortieren Sie eine Reihe von Zahlen?
Wenn Sie aufgefordert werden, eine Reihe von Zahlen A in ein Array B zu sortieren, sieht es folgendermaßen aus: ->
Elemente permutieren
Jedes Element im ursprünglichen Array muss seinem entsprechenden Index im sortierten Array zugeordnet werden. Für das erste Element haben wir also n Positionen. Um den entsprechenden Index in diesem Bereich von 0 bis n - 1 korrekt zu finden, benötigen wir… log (n) -Operationen.
Das nächste Element benötigt Protokolloperationen (n-1), das nächste Protokoll (n-2) usw. Die Summe ergibt sich:
Dies kann an nlog (n) - n angenähert werden .
Welches ist O (n * log (n))!
Daher schließen wir, dass es keinen Sortieralgorithmus geben kann, der besser als O (n * log (n)) ist. Und einige Algorithmen mit dieser Komplexität sind die beliebte Zusammenführungssortierung und Heap-Sortierung!
Dies sind einige der Gründe, warum log (n) in der Komplexitätsanalyse von Algorithmen so häufig auftaucht. Das gleiche kann auf Binärzahlen erweitert werden. Ich habe hier ein Video dazu gemacht.
Warum erscheint log (n) während der Analyse der Algorithmuskomplexität so oft?
Prost!
quelle
Wir nennen die Zeitkomplexität O (log n), wenn die Lösung auf Iterationen über n basiert, wobei die in jeder Iteration geleistete Arbeit einen Bruchteil der vorherigen Iteration darstellt, da der Algorithmus auf die Lösung hinarbeitet.
quelle
Kann noch nicht kommentieren ... Nekro ist es! Die Antwort von Avi Cohen ist falsch. Versuchen Sie:
Keine der Bedingungen ist wahr, also schneidet MEDIAN (X, p, q, Y, j, k) beide Fünfer. Dies sind nicht abnehmende Sequenzen, nicht alle Werte sind unterschiedlich.
Versuchen Sie auch dieses Beispiel mit gerader Länge mit unterschiedlichen Werten:
Jetzt schneidet MEDIAN (X, p, q, Y, j + 1, k) die vier.
Stattdessen biete ich diesen Algorithmus an und nenne ihn mit MEDIAN (1, n, 1, n):
quelle