Kapitel 6 Algorithmen und Programme Ene, mene, miste was rappelt in der Kiste, ene, mene, meck und Du bist weg. Schneller Ausschlussalgorithmus Für die Formulierung von Lösungsverfahren, die auf einem Computer realisiert werden sollen, bedient man sich des Algorithmus“. Der euklidische Algorithmus ist ein herausragendes ” Beispiel. Hier skizzieren wir, worauf es bei Algorithmen ankommt, und geben erste Beispiele, im Laufe der nächsten Kapitel werden wir weitere Beispiele kennenlernen. Für die Analyse von Algorithmen ist der Begriff Komplexität“ von Bedeutung.1 ” 6.1 Algorithmen Ein Computer ist ein Werkzeug zur Verarbeitung und Speicherung von Information. Um ihn zu nutzen, ist er mit Verarbeitungsvorschriften zu füttern“. Wir formulie” ren solche Vorschriften in der Regel unter dem Stichwort Algorithmus. Ein Algorithmus2 für eine vorgegebene bestimmte Art von Aufgaben ist eine endliche Abfolge von wohldefinierten, ausführbaren Vorschriften, die bei Abarbeitung, ausgehend von einem Eingangszustand (Input) nach einer endlichen Anzahl von Verarbeitungsschritten einen Ausgangszustand (Output) bestimmen, der als Lösung der durch den Eingangszustand charakterisierten Aufgabe angesehen werden kann. Abbildung 6.1: AL–Khwarizmi 1 Die Begriffe Algorithmen, Programme, Programmiersprachen, . . .“ Informatikern zu erläutern, bedeutet ” Eulen nach Athen tragen“. Wir tun dies, um sie in den mathematischen Kontext einzuordnen. 2 Die Bezeichnung leitet sich aus dem Namen Al–Khwarizmi (Al–Khwarizmi,780? — 850?), einem der bedeutensten Mathematiker des anfangenden Mittelalters, ab. 88 Algorithmen sind unabhängig von einer konkreten Programmiersprache und einem konkreten Computertyp, auf denen sie ausgeführt werden. Algorithmen stehen im Zentrum der Informatik, der Numerischen und der Diskreten Mathematik. Ein Algorithmus, der einen gewissen Kultstatus“ beanspru” chen kann, ist der euklidische Algorithmus, den wir schon kennnengelernt haben, wir nutzen einen Algorithmus zur Bestimmung des PageRank einer Web–Seite, wenn wir die Suchmaschine google nutzen. Wir finden Algorithmen auch sonst im Alltag vor, wenn wir etwa einen Kuchen backen: Backrezepte sind, wenn sie gut aufgeschrieben sind, Algorithmen für den Prozess des Kuchenbackens. Beispiel 6.1.1 Betrachte folgende Liste von Anweisungen: EIN: Natürliche Zahl n . step 1 k := 1, a := n . step 2 Ist a ( = 1, dann gehe zu AUS. 3a + 1 falls a ungerade step 3 a := a/2 falls a gerade step 4 k := k + 1, gehe zu step 2. AUS: k: Länge der erzeugten Zahlenfolge. Die Rechenschritte erklären sich selbst: ausgehend von n wird eine Folge von natürlichen Zahlen erzeugt, eine so genannte Collatz/Uhlam/Warring-Folge. Ist dies ein Algorithmus? NEIN, denn es ist nicht sichergestellt, dass die Abfrage Ist a = 1, dann gehe zu AUS“ ” irgendwann zur Beendigung führt. ABER: Bisher hat man keine natürliche Zahl gefunden, bei der die obige Liste von Anweisungen nicht endet. Unterschiedliche Algorithmen können entworfen werden zur Lösung ein und derselben Aufgabe. Leistungsunterschiede lassen sich herausarbeiten, wenn man ihren Aufbau und ihre Wirkungsweise analysiert. Fragestellungen dafür sind: • Entwurf von Algorithmen: Wie soll ein Algorithmus zur Lösung einer bestimmten Aufgabe aussehen? • Berechenbarkeit: Gibt es Aufgaben, für die kein Algorithmus existiert? • Komplexität: Wie lässt sich der Aufwand, der betrieben werden muss, um eine Problemklasse von Aufgaben zu lösen, bestimmen/abschätzen? • Korrektheit: Wie lässt sich nachweisen, ob ein vorliegender Algorithmus die Aufgabe korrekt löst? • Robustheit/Zuverlässigkeit: Wie groß ist die Problemklasse von Aufgaben, die der Algorithmus löst? • Genauigkeit: Was ist die Qualität der Lösung, wenn numerisches Rechnen nötig ist? 89 Hauptziel der Analyse ist die Effizienzuntersuchung und die Entwicklung effizienterer Algorithmen. Diese Analyse sollte aber rechnerunabhängig durchgeführt werden. Dazu benötigt man ein geeignetes Rechnermodell. Solche Modelle stehen zur Verfügung! Wir wollen hier nicht darauf eingehen, unsere Analyseuntersuchungen stützen wir auf die Ermittlung des Rechenaufwands, ausgedrückt durch die Anzahl von elementaren Operationen. Hierbei kann man drei Ansätze unterscheiden: – Worst-case-Komplexität: Dies ist eine obere Schranke für den Aufwand in Abhängigkeit vom Input. – Mittlere Komplexität: Dies ist eine obere Schranke für den Aufwand in Abhängigkeit vom Input bei gewissen Annahmen über das Auftreten des Inputs in der Problemklasse. – Untere Komplexität: Hierunter versteht man die Ermittlung unterer Schranken für den zu betreibenden Aufwand. Diese Ansätze können rechnerunabhängig und a-priori erfolgen, d.h. ohne den Algorithmus zu testen. Unter einer a-posteriori–Analyse versteht man das Testen des Algorithmus an Aufgaben mit (hinreichend) großem Input. 6.2 Programme und Programmiersprachen Die konkrete Ausführung eines Algorithmus nennt man einen Prozess. Die Einheit, die den Prozess ausführt, ist ein Prozessor. Beim Kuchenbacken ist der Algorithmus das Rezept, der Prozess die Abarbeitung des Rezepts, der Prozessor der Koch. Hier denken wir natürlich an den Prozessor Computer“. Um eine Analyse des Ablaufs eines Algorithmus auf diesem Pro” zessor vornehmen zu können, ist ein geeignetes Modell für den Computer (Maschinenmodell) bereitzuhalten. Die Informatik studiert u.a. die Turing-Maschine und die Random-AccessMaschine (RAM), welche in gewissem Sinne sogar äquivalent sind. Die Analyse von Algorithmen auf einem abstrakten Niveau ist eine Disziplin der Informatik und/oder mathematischen Informatik. Die Hardware-Komponenten eines Computers sind • Zentraleinheit (CPU) • Speicher (Memory) • Ein- Ausgabegeräte (Input, Output Devices) Die Merkmale, die einen Computer bezüglich Hardware kennzeichnen sind Geschwindigkeit, Speichergröße, Zuverlässigkeit, Kosten, Vernetzbarkeit. Die Ausführung eines Algorithmus auf einem Prozessor setzt voraus, dass der Computer den Algorithmus interpretieren können muss, d.h. er muss • verstehen, was jeder Abarbeitungsschritt bedeutet, • die jeweilige Operation ausüben können. Dies leisten die Programmiersprachen. Die Algorithmen werden damit in Programmen aufgeschrieben; die einzelnen Schritte heißen nun (Programm–)Anweisung, Befehl. Bei einfachen Programmiersprachen (Maschinensprachen) kann jede Anweisung direkt vom Computer interpretiert werden. Da nur elementare Operationen damit erfaßt werden, muß man sehr 90 lange Programme schreiben. Zur Vereinfachung der Programmierung wurden höhere Programmiersprachen entwickelt. Programme in solchen Programmiersprachen können nicht direkt durch den Computer interpretiert werden, sie werden durch Übersetzungsprogramme in die Maschinensprache überführt. Der Übergang von Maschinensprachen zu höheren Programmiersprachen ist fließend. Es gibt eine ganze Hierarchie von Programmiersprachen: • Basic, Fortran • Algol • Pascal, C, C++ • Java, Python, . . . Auf Computern kann man Programme auf Vorrat“ für bestimmte Aufgaben ablegen. Diese ” Sammlung von Programmen nennt man Software. Man unterscheidet • Anwendungssoftware (Textverarbeitungssoftware, Statistiksoftware, . . . ) • Systemsoftware (Betriebssystem, Editor, Compiler, . . . ) Besonderen Stellenwert nehmen Software-Pakete ein wie Maple, Mathematica, Derive, Matlab, Cinderella, die alle eine spezielle Ausrichtung haben: symbolisches Rechnen die ersten drei, numerisches Rechnen Matlab und geometrisches Rechnen das letzte. 6.3 Rekursion Ein Objekt wird als rekursiv bezeichnet, wenn es sich selbst als Teil enthält oder mit Hilfe von sich selbst definiert ist. Rekursion kommt nicht nur in der Mathematik vor, sondern auch im täglichen Leben (ein Bild im Spiegel im Spiegel . . . ). Rekursion kommt speziell in mathematischen Definitionen vor. Ein Beispiel haben wir schon kennengelernt: in der Definition der natürlichen Zahlen kommt die zur Definition anstehende Menge N selbst vor. Ein anderes Beispiel ist die Fakultät einer natürlichen Zahl. Ihre rekursive Definition sieht so aus: ( 1 falls n = 1 n! := n · (n − 1)! falls n 6= 1 Es ist nicht überraschend, dass Rekursion sehr oft im Zusammenhang mit Objekten greift, die mit natürlichen Zahlen im Zusammenhang stehen, da ja die natürlichen Zahlen selbst rekursiv ” definiert sind“. Das Wesentliche an der Rekursion ist die Möglichkeit, eine unendliche Menge von Objekten durch eine endliche Aussage zu definieren oder eine unendliche Anzahl von Berechnungsschritten durch ein endliches Programm zu beschreiben. Allerdings ist Vorsicht geboten, denn rekursive Anweisungen bergen die Gefahr nicht abbrechender Ausführung; der Terminierung ist also besonderes Augenmerk zu schenken. Hier führen wir zwei Beispiele an, die keine Hintergrundtheorie benötigen. Später kommen wir zu einem weiteren Beispiel, nämlich zur rekursiven Behandlung des Problems des größten gemeinsamen Teilers. Die Türme von Hanoi3 3 Vermutlich wurde das Spiel 1883 vom französischen Mathematiker Édouard Lucas erfunden. Er dachte sich dazu die Geschichte aus, dass indische Mönche im großen Tempel zu Benares, im Mittelpunkt der Welt, einen Turm aus 64 goldenen Scheiben versetzen müssten, und wenn ihnen das gelungen sei, wäre das Ende der Welt gekommen. 91 Wir betrachten drei Pfeiler i, j, k, auf die runde Scheiben mit unterschiedlichem Durchmesser aufgesteckt werden können. Das Problem lautet: Es sind n Scheiben, die auf dem Pfeiler i mit nach oben abnehmendem Durchmesser aufgesteckt sind, unter Zuhilfenahme des Pfeilers k durch sukzessive Bewegung jeweils einer Scheibe auf den Pfeiler j umzuschichten. Dabei dürfen die Pfeiler i, j, k verwendet werden aber immer unter der Maßgabe, dass niemals eine Scheibe mit größerem Durchmesser auf einer mit einem kleinerem Durchmesser zu liegen kommt. Man kann dieses Problem folgendermaßen lösen: Man schichtet die obersten n − 1 Scheiben vom Pfeiler i auf den Pfeiler k unter Zuhilfenahme von Pfeiler j den Regeln entsprechend; dann bringt man die auf dem Pfeiler i verbliebene einzige (anfangs unterste) Scheibe auf den Pfeiler j . Nun ist der Pfeiler i frei und man kann die n − 1 Scheiben vom Pfeiler k auf den Pfeiler j mit Hilfe des Pfeilers i umschichten. Es ist klar das rekursive Vorgehen zu erkennen: zur Lösung des Problems der Größe n bedienen wir uns der Lösung der Größe n − 1 . Wir benötigen die Bewegungsarten bewege(m,von,über,nach), bringe(von,nach) . Hierbei bedeutet bewege(l,a,b,c), dass die l obersten Scheiben vom Pfeiler a nach Pfeiler c den Regeln entsprechend unter Nutzung von b als Hilfspfeiler umzuschichten sind. Mit bringe(a,b) wird die oberste Scheibe vom Pfeiler a auf den Pfeiler b gelegt. Die rekursive Lösung für bewege(n,i,j,k) lautet damit: Solange n > 0 bewege(n-1,i,j,k), bringe(i,j), bewege(n-1,k,i,j). Beim Lösen der Aufgabe für n Scheiben, wird Z(n) := 2n − 1 mal eine Scheibe umgelegt Dies zeigt man induktiv. Der Induktionsbeginn ist trivial, der Induktionsschluss sieht so aus: Z(n) = 1 + 2Z(n − 1) = 1 + 2(2n−1 − 1) = 2n − 1 Der Aufwand ist also enorm: für n = 64 müssen 264 − 1 ∼ 1021 Scheiben umgelegt werden.4 Allerdings sind wir ja nicht sicher, ob es nicht einen schnelleren Algorithmus gibt. Dies ist aber nicht der Fall! (Man kann genauer hinsehen: Die kleinste Scheibe S1 wird bei jedem zweiten Zug bewegt, die größte Scheibe Sn wird nur einmal bewegt, die Scheibe Sm wird genau 2n−m mal bewegt.) Die Ackermann-Funktion Als Beispiel für eine rekursive Funktionsdefinition komplexerer Art betrachten wir das Beispiel der so genannten Ackermann-Funktion A(m, n) . Die Definition lautet: falls m = 0 n + 1 A(m, n) := A(m − 1, 1) falls m 6= 0, n = 0 , m, n ∈ N0 . A(m − 1, A(m, n − 1)) falls m 6= 0, n 6= 0 Die Ackermann-Funktion wächst sehr stark: 2 A(0, n) > n , A(1, n) > n + 1 , A(2, n) > 2n , A(3, n) > 2n , A(4, 3) > 22 , A(5, 4) > 1010000 4 Damit ist eine praktische Umsetzung der Lösung nur für kleine n möglich. Die Laufzeit des Algorithmus unter der Annahme, dass eine Scheibe pro Sekunde verschoben wird, ist ca. 600 Milliarden Jahre bei 64 Scheiben. Das Ende der Welt verzögert sich also noch. 92 Der Aufwand, um A(m, n) auszurechnen, wächst auch entsprechend. Beispielsweise erfordert die Berechnung von A(1, 3) bereits folgende Rechenschritte: A(1, 3) = A(0, A(1, 2)) = A(0, A(0, A(1, 1))) = A(0, A(0, A(0, A(1, 0)))) = A(0, A(0, A(0, A(0, 1)))) = A(0, A(0, A(0, 2))) = A(0, A(0, 3)) = A(0, 4) = 5 Es ist nicht sehr einfach einzusehen, dass die Rekursion terminiert; es ist so! Satz 6.3.1 Für alle m, n ∈ N terminiert der Aufruf A(m, n) nach endlich vielen Schritten. Beweis: Der Beweis erfolgt durch zwei ineinander geschachtelte Induktionen über m (äußere Induktion) und, bei festem m, über n (innere Induktion). Induktionsanfang: m = 0 Es folgt unabhängig von n nur ein Aufruf. Induktionsvoraussetzung: Die Behauptung sei richtig für alle k mit 0 ≤ k ≤ m, und für alle n ∈ N. Induktionsschluss: m + 1 Induktion über n n = 0: Hier wird für A(m + 1, 0) der Wert A(m, 1) zurückgegeben. Hierfür terminiert der Aufruf nach Induktionsvoraussetzung der äußeren Induktion. Induktionsvoraussetzung: Der Aufruf A(m + 1, l) terminiert für (festes) m und alle l ≤ n. Induktionsschluss: n + 1 Der Aufruf von A(m + 1, n) erzeugt den Aufruf von A(m, A(m + 1, n − 1)). Nach innerer Induktionsvoraussetzung terminiert der Aufruf A(m + 1, n − 1) und liefert eine Zahl k. Dies erzeugt den Aufruf A(m, k), der nach äußerer Induktionsvoraussetzung terminiert. 6.4 Ordnung, Listen und Sortieren Bei den natürlichen Zahlen 1,2,3,. . . – und nicht nur dort – verwenden wir das Ungleichungszeichen “≤“. Es hat die Eigenschaften (x, y, z ∈ N) x ≤ x; x ≤ y und y ≤ x =⇒ y = x ; x ≤ y und y ≤ z =⇒ x ≤ z ; x ≤ y oder y ≤ x . Wir nehmen dies zum Anlaß für Definition 6.4.1 Sei X eine Menge. Eine Relation O ⊂ X × X heißt Halbordnung auf X, falls die Relation reflexiv, antisymmetrisch und transitiv ist. Ist zusätzlich noch Für alle x, y ∈ X gilt (x, y) ∈ O oder (y, x) ∈ O erfüllt, dann heißt O eine Ordnung auf X. O Meist schreibt man bei Vorliegen einer Halbordnung O statt (x, y) ∈ O auch x ≤ y oder kurz x ≤ y , wenn der Zusammenhang klar ist. 93 Beispiel 6.4.2 Ist X eine Menge, dann ist in P OT (X) eine Halbordnung O definiert durch (A, B) ∈ O : ⇐⇒ A ≤ B : ⇐⇒ A ⊂ B . Beachte, dass nur in trivialen Fällen eine Ordnung vorliegt. Beispiel 6.4.3 Sei A ein (endliches) Alphabet und seien An die Wörter der Länge n über dem Alphabet A . Sei in A eine Ordnung ≤ gegeben. Wir setzen für a = a1 . . . an , b = b1 . . . bn ∈ An : a ≤ b : ⇐⇒ a = b oder ak ≤ bk für das kleinste k mit ak 6= bk . lex Dann ist ≤ eine Halbordnung in An . Man nennt sie die lexikographische Ordnung. lex Als Anwendung ordne man 0002, 0008, 0013, 0029, 0132, 1324 als Worte über dem in natürlicher Weise angeordneten Alphabet A := {0, 1, 2, . . . , 9} . Beispiel 6.4.4 Auf N wird eine Halbordnung O definiert durch (a, b) ∈ O : ⇐⇒ a|b . Dies ist leicht zu verifizieren. Beachte, dass dies keine Ordnung ist, denn es gibt unvergleichli” che“ Elemente, etwa 3, 4 . Eine Liste besteht aus einer Sammlung von wohlbestimmten und wohlunterscheidbaren Objekten und ihrer Anordnung nach einem Prinzip; die leere Liste ist zugelassen. Die Anordnung kann nach dem chronologischen Prinzip, nach einem alphabetischen Prinzip oder allgemein mit einer Ordnung erfolgen. Kennt man alle Objekte der Liste, so kennt man die Liste; Hat die Liste nur ganz wenige Elemente, so kann man sie einfach alle innerhalb einer eckigen Klammer – damit machen wir den Unterschied zu Mengen klar – hinschreiben, durch Kommata getrennt, auf die Reihenfolge kommt es hierbei offenbar an. Maple - Illustration 6.1 Maple kennt den Datentyp Liste. Eine Liste ist eine in eckige Klammern eingeschlossene Folge von Ausdrücken, Objekten. Die Zahl der Einträge in einer Liste erhält man mit dem Befehl nops. Das k-te Element einer Liste kann man sich durch Anhängen des Index k an den Listennamen besorgen. > L1:= [2,3,rot,Hahn,rot] ; L1:= [2,3,rot,Hahn,rot] > nops(L1); 5 > L1[4]; Hahn Sei M eine endliche Menge mit n Elementen und versehen mit einer Ordnung ≤ . Sortieren heißt, die Elemente von M so anzuordnen, dass sie bzgl. der Ordnung ≤ eine aufsteigende Elementfolge bilden. Sortierverfahren werden benötigt etwa bei: Einordnen von Schlüsseln im Werkzeugkasten, Ordnen der erhaltenen Karten beim Skatspiel, Sortieren von Dateien der Größe nach. Gesichtspunkte für die Leistungsfähigkeit eines Sortierverfahrens sind: 94 Schnelligkeit. Wieviele Rechenoperationen (Vergleiche, Umstellen in einer Liste) in Abhängigkeit von n sind nötig? Dieser Aufwand wird Laufzeitkomplexität des Verfahrens genannt. Speicherplatz. Im allgemeinen kann man sich die Elemente der Menge abgelegt in Fächern vorstellen. Beim Sortieren kann es sinnvoll sein, Zusatzfächer zu benutzen. Der Bedarf an Fächern in Abhängigkeit von n ist die Speicherplatzkomplexität des Verfahrens. Sei nun eine Menge M = {a1 , . . . , an } vorgegeben. Wir denken uns die Elemente a1 , . . . , an jeweils einzeln in einer Liste (Feld von Fächern) abgelegt. Wir sortieren diese Liste, indem wir die Objekte in den Fächern irgendwie solange austauschen, bis sie angeordnet in den Fächern liegen. Sortieren durch Auswählen (Selection–sort). Hier geht man folgendermaßen vor: • Finde das kleinste Element und tausche es gegen das an der ersten Stelle befindliche Element (1. Schleife). • Fahre in dieser Weise jeweils auf dem Rest des Feldes, das noch nicht sortiert ist fort (i–te Schleife). Man stellt leicht fest, dass in der i–ten Schleife n − i Vergleiche und eventuell ein Austausch anfallen: Wegen n−1 X i=1 (n − i) = n−1 X j=1 1 1 j = ((1 + · · · + n − 1) + (n − 1 + · · · + 1)) = n(n − 1) 2 2 (6.1) gilt für die Komplexität: Es fallen etwa n2 /2 Vergleiche und etwa n Austausche an. Auf den Aufwand“ −n/2 bei den Vergleichen und −1 beim Austauschen kann man für große n verzich” ten; etwa“ bedeutet diese Vernachlässigung, wir schreiben dafür meist ∼. Hierzu ein Beispiel, ” wobei hier die Elemente die Buchstaben des Alphabets in ihrer alphabetischen Ordnung sind. Anwendung von Selection–sort auf unser Beispiel EXAMPLE ergibt die Sequenz (a). EXAMPLE AXEMPLE AEXMPLE AEEMPLX AEELPMX AEELMPX (a) Selection–sort EXAMPLE EXAMPLE AEXMPLE AEMXPLE AEMPXLE AELMPXE AEELMPX (b) Insert–sort EXAMPLE EAXMPLE AEXMPLE AEMXPLE AEMPXLE AEMPLXE AEMLPXE AELMPXE AELMPEX AELMEPX AELEMPX AEELMPX EXAMPLE EEAMPLX EEALPMX EEALMPX EEAL EEA AEE A E E AEELMPX (c) Bubble–sort (d) Quick–sort M L L L M M M PX P X P X P X Sortieren durch Einfügen (Insert–sort). Betrachte die Listenelemente der Reihe nach und füge jedes an seinem richtigen Platz zwischen den bereits betrachteten ein, wobei diese sortiert bleiben. Das gerade bestimmte Element wird eingefügt, indem die größeren Elemente um eine Position nach rechts geschoben werden und das betrachtete Element auf dem frei gewordenen 95 Platz eingefügt wird. Anwendung von Insert–sort auf unser Beispiel EXAMPLE ergibt die Sequenz (b). Man stellt fest, dass für die Laufzeitkomplexität gilt: ∼ n2 /2 Vergleiche , ∼ n2 /4 Austausche . Sortieren durch Austausch (Bubble–sort). Durchlaufe immer wieder das Feld und vertausche jedesmal, wenn es notwendig ist, benachbarte Elemente; wenn beim Durchlauf kein Austausch mehr nötig ist, ist das Feld sortiert. Anwendung von Bubble–sort auf unser Beispiel EXAMPLE ergibt die Sequenz (c). Man stellt fest, dass für die Laufzeitkomplexität gilt: ∼ n2 /2 Vergleiche , ∼ n2 /2 Austausche . Sortieren nach Quick–sort. Dies ist der wohl am meisten angewendete Sortieralgorithmus. Seine Idee geht auf C.A.R. Hoare (1960) zurück. Es ist ein Vorgehen, das vom Typ Teile und Herrsche (divide et impera, divide and conquer) ist und auf einem Zerlegen des Feldes in zwei Teile und anschließendem Sortieren der Teile unabhängig voneinander beruht. Auf die Teile kann nun diese Idee wieder angewendet werden: Das Verfahren ist rekursiv, d.h. es ruft sich selbst (auf kleinerer Stufe) wieder auf. Wir kommmen im nächsten Kapitel auf das Prinzip Rekursivität“ zurück. ” Eine entscheidende Bedeutung kommt der Zerlegung eines Feldes zu. Es soll (zweckmäßigerweise) so erfolgen, dass gilt: Wird das Feld mit Hilfe des Elements ar zerlegt, so soll dies bedeuten: (1) ar befindet sich an seinem endgültigen Platz; (2) für alle j < r gilt aj ≤ ar ; (3) für alle j > r gilt aj ≥ ar . Wir nach diesen Vorgaben zerlegt, dann ist das ganze Feld sortiert, wenn die beiden Teile sortiert sind. ar nennt man das Pivot-Element. Bei jedem rekursiven Schritt wird eine solche Zerlegung benötigt. Wie findet man eine solche Zerlegung? Hier ist die Realisierung: • Wähle irgendein ar . • Durchsuche das Feld von links, bis ein Element gefunden ist, das nicht kleiner als ar ist, und durchsuche das Feld von rechts, bis ein Element gefunden ist, das nicht größer als ar ist. Tausche die so gefundenen Elemente. • Wiederhole den obigen Suchprozess solange, bis sich die Suche von links und rechts bei einem Element trifft. Nun ist das Element ar mit dem Element zu tauschen, bei dem sich die Suche von links und rechts getroffen hat. Ist das Feld nun zerlegt (Start), das Startfeld ist also nun a1 , . . . , ar , . . . , an , wird das Sortierverfahren auf die Teile a1 , . . . , ar−1 und ar+1 , . . . , an angewendet; als trennende Elemente können nun etwa die Elemente ar−1 und an verwendet werden. Anwendung von Quick–sort auf unser Beispiel EXAMPLE ergibt die Sequenz (d) (M ist beim Start das trennende Element). Das Beste, was bei Quick–sort passieren könnte, ist, dass durch jede Zerlegung das Feld genau halbiert wird. Dann würde die Anzahl Cn der von Quick–sort benötigten Vergleiche der rekurrenten Beziehung vom Typ Teile und Herrsche“ genügen (n gerade!): Cn = 2C n2 + n . ” Dabei ist 2C n2 der Aufwand für das Sortieren der zwei halbierten Felder und n der Aufwand für die Zerlegung. Man kann zeigen, dass notwendigerweise Cn = n log2 n gilt; zum Logarithmus siehe unten. Es gilt: 2n ln n ∼ 1.38n log 2 n . Die Verifikation, dass diese Darstellung von Cn der Rekursion (??) genügt ist mit der Funktionalgleichung des Logarithmus einfach: n 2C n2 + n = n log2 ( ) + n = n(log2 n − 1) + n = n log2 n = Cn . 2 Für den allgemeinen Fall zeigt eine etwas aufwendigere Analyse Cn = 2n ln n . 96 Machen wir einen kleinen Einschub zur Logarithmusfunktion. Eine allgemeine Herangehensweise, Aufwandsrekursionen der obigen Art, zu analysieren, wird unter der Überschrift Master-Theorem der Komplexität“ abgehandelt; siehe Abschnitt 6.6. ” Eine wichtige Begriffsbildung ist die Laufzeitkomplexität im Mittel eines Verfahrens. Damit ist hier gemeint, wieviele Rechenschritte ein Sortierverfahren benötigt, wenn es auf ein zufällig“ vorsortiertes Feld angewendet wird. ” 6.5 Landausymbole und Komplexität Landau-Symbole5 werden in der Mathematik und in der Informatik verwendet, um das asymptotische Verhalten von Funktionen und Folgen zu beschreiben. In der Informatik werden sie bei der Analyse von Algorithmen verwendet und geben ein Maß für die Anzahl der Elementarschritte in Abhängigkeit von der Größe der Eingangsvariablen an. Die Komplexitätstheorie verwendet sie, um verschiedene Probleme danach zu vergleichen, wie schwierig“ oder aufwändig sie zu lösen ” sind. Die folgende Definition ist ein Spezialfall der allgemeinen Landau-Symbole. Definition 6.5.1 Seien f, g : N −→ R Funktionen. (a) Wir schreiben f (n) = O(g(n)), wenn es N ∈ N und eine Konstante c ≥ 0 gibt mit |f (n)| ≤ c|g(n)| falls n ∈ N, n ≥ N . (b) Wir schreiben f (n) = o(g(n)), wenn es für alle c > 0 ein N ∈ N gibt mit |f (x)| ≤ c|g(x)| falls n ∈ N, n ≥ N . Im ersten Fall sagen wir f (n) gleich Groß-O von g(n)“, im zweiten Fall f (n) gleich Klein-o ” ” von g(n)“. Die Tatsache, dass in der Definition von Groß-O nur das Verhalten für große natürliche Zahlen eine Rolle spielt, drückt man oft so aus: f ist groß-O von g für n −→ ∞ . Beispiel 6.5.2 n−2 = O(2n−2 ) , n−2 = o(2n−1 ) . e−n = o(n−25 ) . n! = O(nn ) . n3 = O(n3 ) . Pn 2 i=1 i = O(n ) . √ n! = 2 π n (ne−1 )n (1 + O(n−1 )) . (Stirlingsche Formel) Beispiel 6.5.3 Die Fibonacci-Zahlen sind induktiv definiert durch f0 := f1 := 1 , fn+1 := fn + fn−1 , n ∈ N . Dann gilt fn = O(2n ), denn es gilt fn ≤ 2fn−1 , n ∈ N . 5 Diese Symbole wurden vom Mathematiker E.G. Landau (1877-1938) eingeführt. 97 Wenn wir sagen, dass ein Algorithmus einen Aufwand von O(g) hat, dann meinen wir damit Folgendes: Wenn der Algorithmus auf unterschiedlichen Computern mit den gleichen Datensätzen läuft, und diese die Größe n haben, dann werden die resultierenden Laufzeiten immer nicht größer sein als eine Konstante mal g(n) . Kommen wir zu einigen konkreten Beispielen. Türme von Hanoi Diese Aufgabe ist ja durchaus mit den Algorithmen für Aufgaben auf einem Rechner zu vergleichen, wenn wir etwa die Lösung einem Roboter überlassen wollen. Den Aufwand haben wir schon untersucht. Alerdings werden wir feststellen, dass die Rekursionsgleichung Z(n) = 2Z(n − 1) + 1 nicht unter das so genannte Master-Theorem, das wir noch anführen werden, passt. Matrixmultiplikation Matrix-Multiplikation ist eine Standardaufgabe beim wissenschaftlichen Rechnen. Betrachte zwei Matrizen in Rn,n , die wir miteinander multiplizieren wollen. Klar, der Mindestaufwand (für Additionen und Multiplikationen), der getrieben werden muss, ist von der Ordnung O(n2 ), da alle n2 Einträge im Produkt berechnet werden müssen. Die naive“ Umsetzung der Matrix” multiplikation ist im Aufwand von der Ordnung O(n3 ) = O(nlog2 8 ), da bei der Berechnung eines Eintrags im Produkt n Multiplikationen und n − 1 Additionen anfallen. Auf V. Strassen6 geht ein Berechnungsverfahren zurück, das nach der Methode divide and ” conquer“ arbeitet. Es ist inspiriert durch die Beobachtung von C. F. Gauss, dass die Multiplikation zweier komplexer Zahlen durch drei Multiplikationen und vier Additionen realisiert werden kann. Seien A, B ∈ Rn,n zwei Matrizen und sei n = 2k . (Sind die Matrizen nicht von der Größe n = 2k, so fülle sie mit Nullzeilen und Nullspalten so auf, dass die Größe einer Zweierpotenz erreicht wird.) Wir wollen C mit C = AB berechnen. Dazu zerlegen wir die Matrizen A, B und C in gleich große Block-Matrizen C11 C12 B11 B12 A11 A12 ,C= ,B= A= C21 C22 B21 B22 A21 A22 mit Aij , Bij , Cij ∈ Rn/2,n/2 , i, j = 1, 2 . Dann haben wir C11 = A11 B11 +A12 B21 , C12 = A11 B12 +A12 B22 , C21 = A21 B11 +A22 B21 , C22 = A21 B12 +A22 B22 . Damit haben wir den Aufwand noch nicht reduziert, denn wir benötigen 8 Multiplikationen, um die Cij -Matrizen zu berechnen, genauso, wie bei der Standard-Matrixmultiplikation. Wir können aber eine bedeutende Beobachtung machen. Wir definieren neue Matrizen M1 M2 M3 M4 6 := := := := (A11 + A22 )(B11 + B22 ) M5 := (A11 + A12 )B22 (A21 + A22 )B11 M6 := (A21 − A11 )(B11 + B12 ) A11 (B12 − B22 ) M7 := (A12 − A22 )(B21 + B22 ) A22 (B21 − B11 ) Volker Strassen, 1969 98 welche wir nutzen können, die Cij -Matrizen damit auszudrücken: C11 = M1 + M4 − M5 + M7 C12 = M3 + M5 C22 = M1 − M2 + M3 + M6 C21 = M2 + M4 Damit brauchen wir nur 7 Multiplikationen, wenngleich die Anzahl der Additionen erhöht wurde. Wir iterieren diesen Zerlegungsprozess bis die Untermatrizen zu Skalaren (reellen Zahlen) degenerieren; wir benötigen n-Zerlegungsschritte. Sei T (m) die Anzahl der Multiplikationen bei der Lösung eines Problems der Größe m. Dann haben wir T (m) = 7 T (m/2) . Da T (1) = 1 gilt, ist einfach zu verifizieren, dass T (2k ) = 7k und daher T (n) = nlog2 7 folgt. Also haben wir das Ergebnis, dass wir bei der Realisierung der iterativen Zerlegung zur Multiplikation von A, B ∈ Rn,n den Aufwand O(nlog2 7 ) ≈ O(n2.71 ) treiben müssen. In der Zwischenzeit wurde dieses Ergebnis durch weitere Überlegungen schon auf O(n2.41 ) verbessert. Berechnung des größten gemeinsamen Teilers Der euklidische Algorithmus ist ein Beispiel für einen sehr effizienten Algorithmus zur Berechnung des größten gemeinsamen Teilers zweier zahlen a, b ∈ Z, verglichen etwa mit dere Berechnung auf Grund einer Primfaktorzerlegung der beteiligten Zahlen. Zur Analyse dieses Algorithmus können wir folgendermaßen vorgehen. Wir nehmen dabei a ≥ b > 0 an und beziehen uns auf die Darstellung der Reste im Ablauf des Algorithmus: r0 := a, r1 := b, ri−1 = qi+1 ri + ri+1 , i = 2, . . . , k, rk = qk+1 rk+1 mit rk+1 = ggT(a, b) . Wegen qi+1 ≥ 1 folgt ri−1 ≥ ri + ri+1 > 2ri+1 , also ri+1 < 21 ri−1 . Es gilt also r2 < 21 a, r4 < 12 r2 , und in der Konsequenz r2l < 2−l a, l = l = 1, . . . . Also muss spätestens, wenn 2−l a ≤ 1 ist, r2l = 0 gelten. Dies bedeutet log2 a ≤ l . Wenn r2l0 der letzte Rest 6= 0 mit geradem Index ist, so muss also l0 < log2 a gelten. Für die Anzahl d der Divisionen gilt somit (wenn wir die letzte Division nicht zählen) d ≤ 2l0 ≤ ⌊2 log 2 a⌋ . Diese Schranke wird im Allgemeinen nicht erreicht. Die Größe der Eingabedaten können wir durch die Anzahl der Binärstellen von a messen. Wenn wir die maximale Anzahl der Divisionen mit d(n) bezeichnen, sehen wir d(n) ≤ 2n d.h. d(n) = O(n) . (6.2) Man kann nun so argumentieren, dass der euklidische Algorithmus zur Berechnung des größten gemeinsamen Teilers eines Bruches zweier aufeinanderfolgender Fibonacci–Zahlen besonders langsam ist; es ist der worst case für den euklidischen Algorithmus gegeben. 99 Quick-Sort Die Laufzeit des Algorithmus hängt im wesentlichen von der Wahl des Pivotelementes ab. Im worst case wird das Pivotelement stets so gewählt, dass es das größte oder das kleinste Element der Liste ist. Dies ist etwa der Fall, wenn als Pivotelement stets das Element am Ende der Liste gewählt wird und die zu sortierende Liste bereits sortiert vorliegt. Die zu untersuchende Liste wird dann in jedem Rekursionsschritt nur um eins kleiner und die Zeitkomplexität wird beschrieben durch O(n2 ) . Im best case wird das Pivotelement stets so gewählt, dass die beiden entstehenden Teillisten etwa gleich groß sind. Wir haben dies in der Analyse in Abschnitt unterstellt und dann die asymptotische Laufzeit des Algorithmus mit O(n · log2 (n)) angegeben. Diese Zeitkomplexität gilt ebenso für den average case, bei dem die Längen der Teillisten jeweils durchschnittlich n−1 1 Pn−1 i=0 i = 2 betragen. n Für die Wahl des Pivotelementes sind in der Literatur verschiedene Ansätze beschrieben worden. Die Wahrscheinlichkeit des Eintreffens des worst case ist bei diesen unterschiedlich groß. 6.6 Das Master-Theorem Das Master-Theorem stellt ein Hilfsmittel dar, aus rekursiv gegebene Aufwandsgleichungen den Aufwand für ein Problem explizit anzugeben. In konkreten Fällen haben wir dies ohne theoretischen Überbau schon getan. Als allgemeine Form einer rekursiven Aufwandsanalyse betrachten wir: n T (n) = a T ( ) + f (n) b (6.3) Hierbei steht T (n) für die zu untersuchende Laufzeitfunktion, während a und b Konstanten sind. Ferner bezeichnet f (n) eine von T (n) unabhängige und nicht negative Funktion. Interpretation der Rekurrenz für T (n): a = Anzahl der Unterprobleme in der Rekursion 1/b = Teil des Originalproblems, welches wiederum durch alle Unterprobleme repräsentiert wird f (n) = Kosten (Aufwand), die durch die Zerlegung des Problems und der Kombination der Teillösungen entstehen Wir erweitern die Groß-O– Betrachtungsweise. Definition 6.6.1 Sei g : N −→ [0, ∞) . (a) O(g) := {f : N −→ R|es gibt c ≥ 0, N ∈ N mit 0 ≤ f (n) ≤ cg(n) für alle n ≥ N } . (b) Ω(g) := {f : N −→ R|es gibt c ≥ 0, N ∈ N mit f (n) ≥ cg(n) für alle n ≥ N } . (c) Θ(g) := O(g) ∩ Ω(g) . Satz 6.6.2 (Master-Theorem) Betrachte die Rekursionsgleichung (6.3), wobei die Funktion T : N −→ R monoton nicht fallend sei, d.h. T (n) ≤ T (n + 1) für alle n ∈ N . Es gelte: (a) a ≥ 1, b > 1 . 100 (b) f : N −→ [0, ∞) . Dann gilt bei einer Betrachtung der Rekursionsgleichung (6.3) und der zugehörigen Laufzeitfunktion T für n ∈ N, welche eine Potenz von b sind: (1) Ist f (n) = O(nlogb a−ε ) für ein ε > 0, dann ist T (n) = Θ(nlogb a ) . (2) Ist f (n) = Θ(nlogb a ), dann ist T (n) = Θ(nlogb a ln n) . (3) Ist f (n) = Ω(nlogb a+ε ) für ein ε > 0 und gibt es c ∈ (0, 1) und N ∈ N mit f (n/b) ≤ ac f (n) für alle n ≥ N, dann ist T (n) = Θ(f (n)) . Beweis: Wir skizzieren nur den Beweis zum 1. Fall, und zwar für f (n) := nc mit d := logb a > c . Dies ist keine wesentlich Einschränkung auf Grund der Voraussetzung. Ohne Beschränkung der Allgemeinheit können wir auch annehmen: T (1) = 1 . Wir stellen uns nun das Rekursionsschema als Weihnachtsbaum mit l Astreihen; jede Astreihe ist mit der Anzahl ai an Kugeln bestückt, in Übereinstimmung mit der Anzahl der Aufgaben, die auf dem Level i ∈ {0, . . . , l} zu lösen sind. Offenbar ist l = logb n die Anzahl der Levels und auf jedem Level sind ai := ai Unteraufgaben zu lösen. Die Größe einer Aufgabe auf dem Level i ist (n/bi ) . Also fällt auf diesem Level der Aufwand i a i a ai (n/bi )c = nc ci = nc c b b an. Also ist der Gesamtaufwand unter Beachtung von l X i=0 n c a i bc =n c l X a i i=0 bc =n c1 a bc <1 l+1 − bac = Θ(nc ) . 1 − bac Bemerkung 6.6.3 In jedem Fall von Satz 6.6.2 haben wir polynomiales Verhalten in f . Die Voraussetzungen der drei Fälle lassen sich im wesentlichen mit f (n) = nc einteilen in die drei Fälle a a a < 1 , = 1 , > 1. bc bc bc Wie uns der Beweis zu Satz 6.6.2 lehrt, korrespondiert dies zur Tasache, dass der Aufwand auf den unterschiedlichen Levels fällt, gleich bleibt oder ansteigt. Beachte, dass die Fallunterscheidungen im Satz 6.6.2 nicht vollständig sind: es gibt Beispiele, die nicht erfasst werden; siehe (4) im folgender Beispielaufzählung. Beispiel 6.6.4 (1) Betrachte T (n) = 8T (n/2) + 1000n2 Hier ist der 1. Fall des Master-Theorems zuständig mit a = 8, b = 2, f (n) = 1000n2 und log2 8 = 3 . Klar, f (n) = O(n3−ε ) für jedes ε ∈ [0, 1) . Also erhalten wir T (n) = O(n3 ) . 101 (2) Betrachte T (n) = 4T (n/4) + 1000n Hier ist der 2. Fall des Master-Theorems zuständig mit a = 4, b = 4, f (n) = 1000n und log4 4 = 1 . Klar, f (n) = Θ(n1 ) . Also erhalten wir T (n) = Θ(n1 ) ln n . (3) Betrachte T (n) = 2T (n/2) + n2 Hier ist der 3. Fall des Master-Theorems zuständig mit a = 2, b = 2, f (n) = n2 und log2 2 = 1 . Klar, f (n) = Ω(n1+ε ) für ε = 1 . Ferner ist f (n/b) ≤ ac f (n) für alle n ∈ N mit c = 12 . Also erhalten wir T (n) = O(n2 ) . (4) Betrachte T (n) = 8T (n/2) + n3 ln n Hier könnte der 3. Fall des Master-Theorems zuständig sein mit a = 8, b = 2, f (n) = n3 ln n und log2 8 = 3 . Aber man kann zeigen, dass die Bedingung, die im 3. Fall an f (n) gestellt ist, nicht erfüllt ist. Es ist eher eine Verallgemeinerung des 2. Falls. Man kann beweisen: T (n) = Θ(n3 ln2 n) Es gibt Varianten des Master-Theorems, die die zwischen den drei Fällen des Satzes 6.6.2 liegenden Aufgaben behandeln, z.B. die Aufgabe (4) im obigen Beispiel. Bisher haben wir implizit Rekursionsgleichungen behandelt für Laufzeitwerten T (n) für natürliche Zahlen, die Potenzen von b sind. Realistischere Formen der Rekursionsgleichung sind7 oder n T (n) = a T (⌈ ⌉) + f (n) b (6.4) n T (n) = a T (⌊ ⌋) + f (n) b (6.5) n n T (n) = a T (⌈ ⌉) + (a − a′ ) T (⌊ ⌋) + f (n) b b (6.6) oder sogar (mit a′ ≥ 1) Ohne Beweis halten wir fest, dass die Aussagen des Master-Theorems hier angepasst werden können ohne wesentlichen Verlust an Schärfe. Als Ergebnis haben wir damit, dass bei der Rekursionsgleichung (6.3) nicht rücksicht genommen werden muss auf Potenzen von b, da wir ja die Version (6.4) oder (6.5) betrachten können. 7 Zur Erinnerung: ⌈z⌉ := min{u ∈ Z|u ≥ z} , ⌊z⌋ := max{u ∈ Z|u ≤ z} . 102 6.7 Anhang: g-adische Entwicklung und Computerzahlen Die Dezimalbruchschreibweise für reelle Zahlen ist wohlbekannt; wir haben sie mitunter auch schon verwendet. Etwa: 3, 20123 · · · = 3 + 0 1 2 3 2 + + + + + ··· 10 100 1000 10000 100000 Allgemein nennt man, wenn z eine natürliche Zahl und (an )n∈N eine Folge von Ziffern an ∈ {0, 1, . . . , 9} ist, die Darstellung z.a1 a2 a3 · · · := z · ∞ X aj 10j j=1 einen unendlichen Dezimalbruch oder eine Dezimalbruchentwicklung der dargestellten Zahl. Dabei ist schon berücksichtigt, dass in der Tat eine Zahl dargestellt wird, da die angeführte Reihe wegen an · 10−n ≤ 9 · 10−n die geometrische Reihe als Majorante besitzt. Es kann Mehrdeutigkeiten bei der Darstellung geben, etwa 1 = 1, 000 · · · = 0, 999 . . . . Die Mehrdeutigkeit lässt sich dadurch beseitigen, dass man entweder “an = 9 bis auf endlich viele n“ oder “an = 0 bis auf endlich viele n“ verbietet. Stellt man in einer Dezimalbruchentwicklung m P z.a1 a2 . . . auch noch z durch Potenzen von 10 dar, d.h. z = bj 10j , so resultiert schließlich j=0 ein Dezimalbruch bm . . . b0 .a1 a2 . . . . Bricht man eine Dezimalbruchentwicklung ab, erhält man eine Näherung für die reelle Zahl. Nun kann man das Spiel statt mit g = 10 allgemein mit g ∈ N\{1} betreiben und man kommt zur g–adischen Entwicklung xg einer reellen Zahl. Wir halten fest: Satz 6.7.1 Sei g ∈ N , g ≥ 2 . Jede Zahl x ∈ R hat eine g–adische Entwicklung der Form x = xg = bm . . . b0 .a1 a2 . . . mit bi , ai ∈ {0, . . . , g − 1} . Speziell für g = 2 und natürliche Zahlen x ergibt dies die sogenannte Dualdarstellung von natürlichen Zahlen; etwa 18 = 24 + 21 , d.h. (18)2 = 10010 . Eine Zahl x kann eine endliche Anzahl von Ziffern haben bezüglich einer Basis und eine unendliche Anzahl von Ziffern in einer anderen Basis. Zum Beispiel gilt für x = 1/3: (x)10 = +0.3 , (x)3 = +0.1 . Für x := 103 3/4 haben wir (x)10 = +[103.75] , (x)2 = +[1100111.11] , (x)16 = +[67.C] . Auf Rechenanlagen steht nur endlicher Speicherplatz für die Darstellung von Zahlen zur Verfügung, also auch nur eine endliche Anzahl von Zahlen und für jede Zahl hat man sich mit einem rationalen Näherungswert zu begnügen. Bei der Festkomma–Darstellung wird jede Zahl durch das Vorzeichen, s′ Ziffern vor dem Komma und t′ Ziffern nach dem Komma ersetzt. Anstelle der reellen Zahlengeraden wird ein äquidistant geteiltes endliches Punktraster verwendet. Für wissenschaftlich–technische Rechnungen ist die Festkomma–Darstellung nicht sehr geeignet, da beispielsweise physikalische Konstanten über viele 10–er Potenzen streuen, z.B.: 103 m0 = 9.1110 10 −28 g L = 6.02 10 23 Mol −1 h = 6.62 10 −34 Watt sec2 c = 3.00 10 10 cm sec−1 : : : : Ruhemasse des Elektrons Loschmidt–Zahl Planksches Wirkungsquantum Lichtgeschwindigkeit Passender für diese Zwecke ist die Gleitkomma–Darstellung. Das System der Gleitkommazahlen wird charakterisiert durch 4 Parameter (Maschinenkonstanten): • Basis g • Mantissenstellenzahl t • Exponentengrenzen b, B ∈ Z(b < 0 < B) ; Exponentenbereich [b, B] := {z ∈ Z|b ≤ z ≤ B} . Jede Gleitkommazahl hat nach Wahl dieser Parameter die Form x = sign(x) 0.a1 . . . at g e mit 0 ≤ ai < g, a1 6= 0, e ∈ [b, B] , mit dem Vorzeichen sign(x), der Mantisse 0.a1 . . . at und dem Exponenten e . (Die Bedingung a1 6= 0 erzwingt eine Normierung). Sie kann so abgespeichert werden: x= b ± a1 ··· at e (Beachte, dass die Speicherung von a1 = 1 bei g = 2 gar nicht nötig ist.) Beispiel 6.7.2 Sei t = 5, b = −5, B = 5, x = 27.5 1. g = 10 : x = +0.27500 10 2 2. g = 2 : x = +0.110111 2 5 Nicht darstellbar! 3. g = 16 : x = +0.1B800 16 2 Im Hexadezimalsystem (g = 16) schreibt man für die Ziffern 10, 11, 12, 13, 14, 15 die Buchstaben A, B, C, D, E, F . Wir stellen fest, dass die Null unter den normalisierten Gleitkommazahlen nicht vorkommt. Wir fügen sie hinzu – die Darstellung ist rechnerspezifisch – und nennen diese Zahlen dann Maschinenzahlen: x−1 , . . . x−t ∈ {0, 1, . . . , g − 1}, x−1 6= 0, e F := F(g, t, emin , emax ) := σ 0.x−1 . . . x−t g | ∪{0} . e ∈ Z, emin ≤ e ≤ emax , σ ∈ {+, −} Es ist einfach, diese Zahlen zu zählen: #F(g, t, emin , emax ) = 2(g − 1)gt−1 (emax − emin + 1) In F(g, t, emin , emax ) sind größte und kleinste Zahl gegeben durch xmin := +[10 . . . 0] gemin −t = gemin −1 , xmax := +[δ . . . δ] gemax −t = gemax (1−g−t ) wobei δ = g−1 . Offenbar gilt für alle Computerzahlen x 6= 0 g−emin −1 ≤ |x| ≤ gemax . Gilt |x| < g−emin −1 , so wird im Allgemeinen x durch Null ersetzt. Zahlen x mit |x| > gemax können nicht verarbeitet werden. Treten diese Fälle auf, so spricht man von Exponentenüberlauf. In jedem Intervall [ge−1 , ge ], emin ≤ e ≤ emax , finden wir µ = gt − gt−1 + 1 gleichförmig verteilte Zahlen: F ∩ [ge−1 , ge ] = {ge−1 , ge−1 + ge−t , . . . , ge−1 + µge−t } . Beachte, dass der Zuwachs ge−t anwächst, wenn e von emin auf emax anwächst. 104 (6.7) Beispiel 6.7.3 1. g = 2, t = 3, b = −1, B = 2. Es gibt 33 Maschinenzahlen. 2. g = 10, t = 10, B = −b = 99. Die kleinste positive Maschinenzahl verschieden von Null ist m = +0.100000000010 −99 , die größte positive Maschinenzahl ist M = +0.999999999910 99 . Nachbar“ von m : m′ = +0.1000000001 10 −99 ” Nachbar“ von M : M ′ = +0.9999999998 10 99 ” Also: m − m′ = 10 −109 , M − M ′ = 10 89 Das Beispiel zeigt uns, dass betragsmäßig kleine Gleitkommazahlen dichter liegen als betragsmäßig große. Lemma 6.7.4 Betrachte das System F(g, t, emin , emax ) . Sei x eine reelle Zahl mit xmin ≤ |x| ≤ xmax . Dann gilt: 1 |z − x| ≤ g−t+1 . (6.8) min z∈F |x| 2 Beweis: Ohne Einschränkungen können wir annehmen: x ist positiv. Wir können annehmen, dass mit einem e mit emin ≤ e ≤ emax gilt: x ∈ [ge−1 , ge ] . Aus (6.7) erhalten wir, dass eine Zahl z ∈ [ge−1 , ge ] mit |z − x| ≤ 21 ge−t existiert. Wegen ge−1 ≤ x ist die Behauptung bewiesen. Die Zahl eps := 12 g−t+1 heißt Maschinengenauigkeit. Beachte, sie hängt nur von der Mantissenlänge t ab. Auf einem Computer sind üblicherweise zwei Formate von Fließkommazahlen verfügbar: einfache und doppelte Genauigkeit. Im IEEE-Standard mit einfacher Geneauigkeit haben wir eps = 2−23 ≈ 10−7 . Nun hat man ein Verfahren anzugeben, das eine Zahl, die keine Maschinenzahl ist, durch eine Maschinenzahl approximiert. Offenbar ist Rundung ein geeignetes Verfahren, ein solches x̃ zu konstruieren. Wir wissen, dass sich jedes x ∈ R g–adisch so darstellen läßt: x = sign(x)0.a1 . . . at at+1 . . . g e mit 0 ≤ ai < g, e ∈ Z und a1 6= 0 . Dazu erklären wir die Rundung rd(x) durch g sign(x)0.a1 . . . at g e , falls at+1 < 2 rd(x) := sign(x)(0.a1 . . . at + g−t ) g e , sonst Beispiel 6.7.5 Sei g = 10, t = 4, B = −b = 99 . Wir haben: rd(0.31796 10 45) = 0.3180 10 45 , rd(0.0012345 10 −99) = 0.1235 10 −100 . 105 Die obigen Beispiele zeigen, dass Rundung nicht immer zu einer Maschinenzahl führt (Exponentenüberlauf, Exponentenunterlauf). Rechenanlagen geben bei Exponentenüberlauf eine Fehlermeldung und beenden die Rechnung. Bei Exponentenunterlauf ist die Vorgehensweise nicht einheitlich, meist wird die Rechnung mit Null fortgesetzt. Folgerung 6.7.6 Für jedes x ∈ R gilt: (a) |x − rd(x)| ≤ 12 g1−t |x| . (b) rd(x) = x(1 + ǫ) mit |ǫ| ≤ 21 g1−t . Beweis: Sei x = sign(x)0.a1 . . . at at+1 . . . g e. Zu (a) : Es gilt |x − rd(x)| ≤ 12 ge−t nach Konstruktion von rd(x). Also 1 1 1 |x − rd(x)||x|−1 ≤ ge−t ≤ g1−t . e 2 0.a1 · g 2 Zu (b) : Setze ǫ := |x − rd(x)| für x 6= 0, für x = 0 ist nichts zu beweisen. |x| Beachte: Wir finden in Folgerung 6.7.6 wieder die Maschinengenauigkeit eps := 12 g1−t . Wegen Rundung werden die arithmetischen Operationen +, −, ·, / nicht exakt ausgeführt. Dies hat zur Folge, dass die Ersatzoperationen ⊕, ⊖, ⊙, ⊘ definiert durch x ⊕ y := rd(x + y) , x ⊖ y := rd(x − y), x ⊙ y := rd(x · y) , x ⊘ y := rd(x/y) nicht den üblichen Rechenregeln genügen. Wir sehen dabei davon ab, dass Exponentenüberlauf und Exponentenunterlauf auftreten kann. Beispiel 6.7.7 Sei g = 10, t = 4 . 0.1000 10 1 ⊕ 0.400 10 −4 = rd(1.0004) = 1 = 0.1000 10 1 Aus x ⊕ y = x folgt also nicht notwendigerweise y = 0. Sei g = 10, t = 8 . Es seien a = 0.233712.58 10 −4 , b = 0.33678429 10 2 , c = −0.33677811 10 2 . Es gilt: A : = a ⊕ (b ⊕ c) = a ⊕ (0, 61800000 10 −3 B : = (a ⊕ b) ⊕ c = (0.00000023 10 2 + 0.33678429 10 2) ⊕ c = rd(0.02337126 10 3 + 0.6180000 10 −3) = rd(0.33678452 10 2 − 0.33677811 10 2) = rd(0.64137126 10 −3) = 0.64100000 10 −3 = 0, 64137126 10 −3 Exaktes Resultat: C := a + b + c = 0.641371258 10 −3 Also A = rd(C) , B 6= rd(C) und der Genauigkeitsverlust bei C beträgt 5 Dezimalen! Der Genauigkeitsverlust lässt sich gut verstehen: Es werden zwei Zahlen subtrahiert, die annähernd gleich sind. 106 Eine positive Aussage zur Wirkung der Ersatzoperationen ist Folgerung 6.7.8 Sei ⊗ eine der Operationen {⊕, ⊖, ⊙, ⊘} und × ihre entsprechende Operation in {+, −, ·, /}. Dann gilt für alle x, y ∈ R : x ⊗ y = (x × y)(1 + ǫ) mit |ǫ| ≤ eps. Beweis: Folgerung 6.7.6 mit der Definition von ⊗. Probleme der Computer-Arithmetik sind: Überlauf Siehe oben. Unterlauf Siehe oben. Auslöschung Addition etwa gleich großer Zahlen mit entgegengesetztem Vorzeichen führt zu einer starken Verringerung der Tahl der gültigen Ziffern. Rechenregeln Selbst in Fällen, wo weder Überlauf noch Unterlauf eintritt, gelten die Rechenregeln der reellen Zahlen nicht mehr. Beispiel 6.7.9 Wenn man zwei Gleitkommazahlen addiert, die ähnlichen Absolutwert haben, aber entgegengesetzes Vorzeichen, kommt es zum Auslöschen signifikanter Stellen und das Ergebnis wird ungenau. Etwa ergibt die Auswertung von ((1 + x) − 1)/x für x = 1e − 15 den Wert 1.1102, und dies ist ziemlich ungenau (11% Fehler). 6.8 1.) Übungen S Sei A ein (endliches) Alphabet, sei A∗ := {()} ∪ n∈N An die Menge der Wörter (beliebiger Länge) über dem Alphabet A . Für zwei Worte u = (u1 , . . . , uk ) ∈ Ak , v = (v1 , . . . , vl ) ∈ Al setzen wir: uv := (u1 , . . . , uk , v1 , . . . , vl ) ∈ Ak+l . Wir definieren für u, v ∈ A∗ : u ≤ v : ⇐⇒ Es gibt z ∈ A∗ mit uz = v . (a) Zeige: ≤ ist eine Halbordnung in A∗ . (b) Ist ≤ stets eine Ordnung in A∗ ? (c) Gibt es in A∗ ein Wort w, so dass gilt: w ≤ u für alle u ∈ A∗ . 2.) Ist die Halbordnung O eine Ordnung auf der Menge A, dann ist auch die zugehörige lexikographische Halbordnung auf A × A eine Ordnung. 3.) Sei O eine Halbordnung auf der Menge X und sei M eine teilmenge von X . Unter einer O oberen Schranke von M versteht man ein x ∈ X mit y ≤ x für alle y ∈ M . Wenn die Menge aller oberen Schranken von M ein kleinstes Element (bezüglich der Halbordnung O) besitzt, dann heißt dieses kleinste Element kleinste obere Schranke. Sei M := {2, 3, . . . , 10} ⊂ N versehen mit der Teilbarkeitsrelation. 107 (a) Man gebe eine mindestens zweielementige Teilmenge von M an, welche eine kleinste obere Schranke besitzt. (b) Man gebe eine mindestens zweielementige Teilmenge von M an, welche keine obere Schranke besitzt. (c) Was ist die kleinste obere Schranke von {a, b} ⊂ M ? 4.) Sei X eine Menge. Was ist die kleinste obere Schranke (siehe obige Aufgabe) einer Teilmenge von POT(X) bezüglich der Inklusion? 5.) Zeige: Für alle x, y, z > 1 gilt xlogy z = z logy x . 6.) Zeige: Für alle x, y > 1 gilt logx n = Θ(log2 n) . 7.) Betrachte mit a ≥ 1, b > 1, f : N −→ N die Rekursionsgleichung n T (1) := 1 , T (n) = a T (⌈ ⌉) + f (n), n ≥ 2 . b Zeige, dass diese Vorgabe die Laufzeitfunktion T : N −→ N eindeutig bestimmt. 8.) Betrachte die Rekursionsgleichung √ n T (1) := 1 , T (n) = 3 T ( ) + n n + 1, n ≥ 2 . 2 Zeige: T (n) = Θ(nlog2 3 ) . 9.) Betrachte die Rekursionsgleichung n T (1) := 1 , T (n) = T ( ) + 1, n ≥ 2 . 4 Welches asymptotische Verhalten zeigt T (n)? 10.) Betrachte die Rekursionsgleichung n T (1) := 1 , T (n) = 7 T ( ) + n2 , n ≥ 2 . 3 Welches asymptotische Verhalten zeigt T (n)? 108