5.2 Schnelle Matrix-Exponentiation
Ich zeige Ihnen jetzt eine trickreiche Methode, Fibonacci-Zahlen noch schneller
zu berechnen. In Ihrem itertativen Code haben Sie wahrscheinlich etwas in der Art
geschrieben:
newA = b
newB = a + b
a = newA
b = newB
Mathematisch ausgedrückt: jeder Schleifendurchlauf bildet das Paar
auf ein neues Paar ab, nämlich
Dies ist eine
lineare Abbildung und kann als Matrix dargestellt werden:
Schreiben wir also
und
,
dann ist der Vektor
nach Schleifendurchläufen
zu geworden. Anders gesagt:
Wir können also berechnen, indem wir berechnen.
Wir brauchen also einen Algorithmus für die
Matrix-Exponentiation.
Einen naiven Algorithmus sehen Sie hier:
function matrixExp (M, n):
R := die Identitätsmatrix
for i = 1 ... n:
R := M * R
return R
Übungsaufgabe 5.2.1
Implementieren Sie matrixExp(M, n)
in Python. Beschränken Sie sich hier
auf -Matrizen . Schreiben Sie als erstes
eine Funktion
matrixMult(A,B)
, die zwei -Matrizen multipliziert. Falls
Sie es
vergessen haben:
Übungsaufgabe 5.2.2
Messen Sie empirisch die Laufzeit Ihrer Funktion matrixExp(M,n)
für eine feste Matrix , die Sie selbst wählen dürfen, und
wachsende Zahlen . Wie verhält sich die Laufzeit in Abhängigkeit von ?
Schnelle Exponentiation
Mittels Rekursion können Sie Matrix-Exponentiation viel schneller implementieren.
Die Idee ist einfach: wenn gerade ist, also , dann können wir
rechnen.
Der erste Schritt ist ein rekursiver Aufruf. Der zweite Schritt multipliziert einfach
das Ergebnis mit sich selbst.
Wenn ungerade ist, also , dann rechnen wir
also wie im geraden Fall, nur, dass wir noch eine Multiplikation mit
am Ende ausführen. Sie sehen: der Wert von halbiert sich mit jedem
rekursiven Aufruf, sie haben also höchstens rekursive Aufrufe.
Übungsaufgabe 5.2.3
Schreiben Sie in Python eine Funktion
fastMatrixExp(M,n)
, die diese Idee implementiert.
Messen Sie die Laufzeit. Setzen Sie die Laufzeit zu in Bezug.
Was geschieht mit der Laufzeit, wenn Sie verdoppeln?
Übungsaufgabe 5.2.4
Wenn Sie fastMatrixExp(M,n)
richtig implementiert haben,
wird ein Aufruf nur Operationen ausführen, da sich
ja in jedem Schritt halbiert. Warum ist die gemessene Laufzeit
dennoch nicht ?
Um die Laufzeit von fastMatrixExp
auch theoretisch zu verstehen,
nehmen wir vereinfachend an, dass gilt, also eine
Zweierpotenz ist. Dies ist besonders bequem, weil in diesem Falle die
"Zwischenergebnisse" von fastMatrixExp
die folgende Form haben:
In jeden Schritt nehmen wir also die vorherige Matrix und quadrieren sie. Wie lange
benötigen wir, um eine Matrix zu quadrieren?
Wir brauchen also acht Multiplikationen und vier Additionen.
Intuitiv gesehen ist Multiplikation schwieriger als Addition, daher
beschränken wir uns darauf, die Komplexität der acht Multiplikationen
verstehen zu wollen.
Nehmen wir an, die Zahlen haben Bit-Länge höchstens
. Sei die Komplexität der Multiplikation von
-Bit-Zahlen. Dann brauchen wir also
Schritte für die Quadrierung unserer Matrix.
Wenn , dann ist
Die vier Einträge in haben also Bit-Länge
,
und somit dauert die letzte Matrix-Quadrierung,
auch viele Schritte.
Was ist mit den vorherigen Schritten, also
? Der braucht
viele Schritte. Es wird sich herausstellen,
dass diese Schritte nicht so sehr ins Gewicht fallen. Formell gesprochen:
wenn wir berechnen, dann entfällt ein
konstant großer Anteil der Laufzeit auf den letzten Schritt:
.
Übungsaufgabe 5.2.5
Messen Sie empirisch, wie lange es in Python braucht, zwei
-stellige Zahlen miteinander zu multiplizieren. Legen
Sie eine Tabelle an! Was geschieht mit der Laufzeit, wenn Sie
verdoppeln?
Übungsaufgabe 5.2.6
Schreiben Sie eine Funktion
fibFME
, die wie oben beschrieben mithilfe der
Fast Matrix Exponentation berechnet. Messen Sie die Laufzeit
und untersuchen Sie, wie sich eine Verdopplung von auf
die Laufzeit auswirkt.