AuK/Teile und Herrsche 2 SS10
Aus ProgrammingWiki
Wiki-Quelltext dieser Seite.
<language>Scheme</language>
Diese Seite setzt das in [[AuK/Teile und Herrsche 1 SS10|Teile und Herrsche 1]] begonnene Thema inhaltlich fort.
Autor:
Felix Deutschmann ([mailto:sifedeut@stud.hs-zigr.de sifedeut@stud.hs-zigr.de])
== Einführung ==
Diese Seite beschäftigt sich mit den Algorithmen ''Binäre Suche'', ''Multiplikation großer, ganzer Zahlen nach KARATSUBA und OFMAN'', sowie ''Schnelle Matrixmultiplikation'', welche hier vorgestellt und demonstriert werden.
Weiterhin findet man hier auch die Vorlesungsunterlagen, sowie die Übungsaufgaben zu diesem Thema.
== Binäre Suche ==
Wer schon einmal etwas gesucht hat, sei es ein Schlüssel an einem Schlüsselbund oder einen Namen im guten alten Telefonbuch oder auch nur eine Seite in einem dicken Buch, der hat sich dabei vielleicht schon eine wichtige Frage gestellt: "Wie finde ich das jetzt am besten?".
Beim Schlüsselbund sieht man sich jeden einzelnen Schlüssel an und prüft, ob es der Schlüssel ist, den man gesucht hat. Man kann diese Vorgehensweise mit dem Suchen in einer unsortierten Liste vergleichen. Bei dieser Art zu suchen prüft man, ob das zu suchende Element ''x'' in der Liste ''L'' vorkommt - Elementweise. Im ungünstigsten Fall findet man das Element an der letzten Stelle der Liste ''L''. Diese Suche mit <math>\mathcal{O}(n)</math> hat einen linearen Aufwand und ist für lange Liste nicht zu empfehlen. Eine Implementierung dieser Suche in Scheme ist folgende:
<code id="4bc0888750c85">(define usortSeqSuch
(lambda (x ls)
(cond
((null? ls) "Nicht gefunden!")
((= x (car ls)) 'Gefunden!)
(else
(usortSeqSuch x (cdr ls))))))</code>
<eval id="4bc088875144e">(usortSeqSuch 5 '(99 43 65 44 29 83 10 4 5 8 6))</eval>
Dieser Suchalgorithmus hat zwei große sofort erkennbare Schwächen.
* der Aufwand steigt linear mit wachsender Elementeanzahl
* die Liste wird komplett durchsucht, wenn das Element fehlt
Das zweite Problem können wir mit einem kleinen Trick sofort beheben. Wir nehmen eine aufsteigend sortierte Liste und durchsuchen diese mit dem folgenden leicht modifizierten Algorithmus. Damit können wir dann in einigen Fällen die Suche vorzeitig abbrechen, wenn das Element nicht in der Liste ist.
<code id="4bc08d12e4c7b">(define sortSeqSuch
(lambda (x ls)
(cond
((null? ls) "Nicht gefunden!")
((< x (car ls)) "Nicht gefunden!")
((= x (car ls)) 'Gefunden!)
(else
(sortSeqSuch x (cdr ls))))))</code>
<eval id="4bc08d9b95e51">(sortSeqSuch 38 '(1 4 7 9 12 17 24 29 31 33 38 41 53 74 86 93 104 137 143 163))</eval>
Da aber auch hier der Aufwand linear bleibt, erfüllt die sequentielle Suche nicht unseren Wunsch nach einem geringeren Aufwand.
Wenden wir uns also der Suche nach einem Namen im Telefonbuch zu. Wie würde man dabei vorgehen? Bei den meisten Menschen kann man folgendes erkennen:
Zuerst wird das Buch in der Mitte aufgeschlagen. Dann prüfen sie, ob der Anfangsbuchstabe vom gesuchten Namen mit dem aktuellen Index im Telefonbuch übereinstimmt. Wenn das so ist, dann ist die Suche vermutlich noch auf der selben Seite vorbei. Haben sie den Anfangsbuchstaben noch nicht gefunden, so überlegen sie, ob der gesuchte Anfangsbuchstabe vor oder nach dem aktuellen Index auftritt. Daraufhin wird entweder vor oder nach der Mitte des Buches weitergesucht. Aber nicht irgendwie, sondern wieder mit dem selben Verfahren. Auf diese Weise wird der Suchraum kontinuierlich halbiert und man kommt mit dieser Methode deutlich schneller zum Ziel als mit der sequentiellen Suche.
Jetzt wo der Name dieser Suche geklärt ist, befassen wir uns ein wenig näher mit der binären Suche. Dafür reduzieren wir das Suchproblem auf eine Liste mit <math>n</math> Elementen aus dem Zahlenbereich der ganzen Zahlen <math>\mathbb{Z}</math>:
<math>$$L=\{e_{1}, e_{2}, \dots ,e_{n}\}$$</math> mit <math>e\in\mathbb{Z}</math> und <math>n\in\mathbb{N}</math>
Unsere Liste L muss nun möglichst in der Mitte geteilt werden. Da das aber bei einer ungeraden Anzahl von Elementen ganzzahlig nicht möglich ist, teilen wir die Anzahl der Listenelemente durch 2 und runden das Ergebnis abschließend auf die nächst kleinere Zahl ab. Mathematisch schreibt man: <math>k=\lfloor\frac{n}{2}\rfloor</math> (<math>k</math> ist der Index des Elementes in der Mitte der Liste <math>L</math>)
Nach der Teilung haben wir nun zwei Teillisten, eine linke und eine rechte:
:<math>$$L_{l}=\{e_{1}, e_{2}, \dots , e_{k}\}$$</math> und
:<math>$$L_{r}=\{e_{k+1}, e_{k+2}, \dots , e_{n}\}$$</math>
Wenn nun <math>x</math> unser gesuchtes Element ist, dann wird <math>x</math> mit dem ersten Element der rechten Teilliste verglichen <math>x < e_{k+1}</math>. Abhängig vom Ergebnis dieser Prüfung wird nun mit der rechten oder linken Teillisten fortgefahren. Die verwendete Teilliste wird dann wieder in zwei Teillisten aufgeteilt und die Prüfung beginnt von vorn. Dieser Vorgang wird solange wiederholt, bis entweder <math>x</math> gefunden wurde oder bis alle Teillisten untersucht worden sind. Im besten Fall ist das erste Element <math>e_{k+1}</math> unserer Teilliste <math>L_{r}</math> schon das gesuchte <math>x</math>.
Zur übersichtlichen Darstellung der binären Suche zeigen wir hier die rekursive Bildungsvorschrift:
<math>BiSu = \{
\begin{array}{ll}
BiSu(L_{\text{l}}) &\textrm{wenn }x<e_{k+1},\\
BiSu(L_{\text{r}}) &\textrm{wenn }x>e_{k},\\
print(e_{k}) &\textrm{wenn }x = e_{k}, \\
\textrm{"nicht Enthalten"} &\textrm{wenn }k < 0 \textrm{ oder } k > n\\
\textrm{"nicht Enthalten"} &\textrm{sonst }
\end{array}
</math>
Mit der binären Suche ist es möglich, in einem Telefonbuch mit 250.000 Einträgen und 700 Seiten
mit höchstens <math>18 \approx \log_2 250000</math> Namensvergleichen jeden Namen zu finden.
===Implementierung in Scheme===
Aufgrund der oben stehenden interessanten Information schauen wir uns jetzt einmal den Algorithmus in seiner Scheme-Implementierung an.
Bevor wir aber zum eigentlichen Algorithmus kommen, benötigen wir noch die Prozedur teillisten2, welche im Kapitel [[AuK/Teile und Herrsche 1 SS10|Teile und Herrsche 1]] im Zusammenhang mit Mergesort behandelt wurde.
<code id="4bc0b3fa4f1c3" hidden>(define teillisten2
(lambda (ls)
(let ((k (floor (/ (length ls) 2))))
(letrec
((helfer
(lambda (ls i)
(if (= i 0)
(values '() ls)
(call-with-values
(lambda () (helfer (cdr ls) (- i 1)))
(lambda (links rechts)
(values (cons (car ls) links) rechts)))))))
(helfer ls k)))))</code>
Scheme-Implementierung der binären Suche:
<code id="4bc0b3fa4f91d">(define binaersuche
(lambda (x ls)
(call-with-values
(lambda () (teillisten2 ls))
(lambda (lls rls)
(cond
((null? rls) "Key not found!")
((= x (car rls)) "Key found :)")
((< x (car rls))
"Key not found!"
(binaersuche x lls))
(else
(if (null? (cdr rls))
"Key not found!"
(binaersuche x rls))))))))</code>
Um zu sehen, dass der Algorithmus arbeitet, haben wir zur Vereinfachung eine Liste explizit angegeben. Bei der späteren selbständigen Arbeit mit der binären Suchen können auch die Prozeduren zzls (Erstellt eine Liste mit Zufallszahlen) und mergesort (sortiert eine Liste) verwendet werden. Beide Prozeduren werden für die Übungen bekannt gegeben.
<code id="4bc0b3fa500e2">(define suchliste '(4 8 15 16 23 42 55 63 78 84 92 103))</code>
Wir suchen nach dem Element 42:
<eval id="4bc0b3fa50938">(binaersuche 42 suchliste)</eval>
Nachdem wir die binäre Suche nun ausprobieren konnten, stellen wir fest, dass das Ergebnis etwas dürftig ist. Denn die Aussage, dass etwas gefunden wurde ist nicht sinnvoll, wenn man nicht auch noch gesagt bekommt, an welcher Stelle das Element gefunden wurde. Aus diesem Grund fügen wir diese Funktion noch in unsere Prozedur ein. Weiterhin wird noch ein Aufrufzähler eingebaut, welcher die Rekursionsschritte zählt. Diesen können wir dann für die empirische Aufwandsanalyse benutzen.
Zuerst definieren wir einen Zähler und einen Index:
<code id="4bc0c4f54f2b7">(define calls 0)
(define index 0)</code>
Dann implementieren wir die eben definierten Variablen in der Binären Suche
<code id="4bc0c4f54fe20">(define binhelfer
(lambda (x ls)
(call-with-values
(lambda () (teillisten2 ls))
(lambda (lls rls)
(cond
((null? rls) "Nicht gefunden!")
((= x (car rls))
(and (and (set! index (+ index (length lls)))
(and (string-append "Gefunden - [Stelle: "
(number->string index) "] - [rekursive Aufrufe: "
(number->string calls) "]")))))
((< x (car rls))
(if (null? lls)
"Nicht gefunden"
(and (set! calls (+ calls 1))
(binhelfer x lls))))
(else
(if (null? (cdr rls))
(string-append "Nicht gefunden. [Rekursionen: "
(number->string calls) "]")
(and (set! calls (+ calls 1))
(and
(set! index (+ index (length lls)))
(binhelfer x rls))))))))))</code>
Und abschließend benötigen wir noch eine Prozedur zum Aufruf der binären Suche:
<code id="4bc0c4f550770">(define binaersuche2
(lambda (x ls)
(set! calls 0)
(set! index 0)
(binhelfer x ls)))</code>
Ein Aufruf unserer neuen binären Suche ergibt dann das gewünschte Ergebnis.
<eval id="4bc0c4f550f55">;so haben wir die suchliste definiert:
;verändern könnt ihr sie mit set!
;(define suchliste '(4 8 15 16 23 42 55 63 78 84 92 103))
(binaersuche2 42 suchliste)</eval>
===Vergleich Sequentielle Suche - Binäre Suche===
Da wir nun im vorangegangenen Teilabschnitt die Grundlage für eine empirische Analyse der binären Suche geschaffen haben, möchten wir das an dieser Stelle auch für die sequentielle Suche tun. Somit ist es dann in der Übung möglich die Aufwände der beiden Suchen zu vergleichen.
Dafür müssen wir lediglich die Variable für die Aufrufe implementieren, da diese dann auch für den Index verwendet werden kann:
<code id="4bc2d49daeabc">(define helper_sortSeqSuch
(lambda (x ls)
(cond
((null? ls) "Nicht gefunden")
((< x (car ls)) "Nicht gefunden")
((= x (car ls))
(string-append "Gefunden an Stelle: "
(number->string calls) ". Anzahl der Aufrufe: "
(number->string calls)))
(else
(set! calls (+ calls 1))
(helper_sortSeqSuch x (cdr ls))))))</code>
Weiterhin schaffen wir uns noch eine Prozedur, welche die Variable "calls" zurücksetzt:
<code id="4bc2d49daf279">(define sortSeqSuch2
(lambda (x ls)
(set! calls 0)
(helper_sortSeqSuch x ls)))</code>
Somit können wir mit den folgenden Aufruf machen:
<eval id="4bc2d49dafa38">(sortSeqSuch2 42 suchliste)</eval>
===Effizienzanalysen===
====Best Case====
Beide Suchalgorithmen können im ersten Schritt zum Ergebnis kommen. Bei der sequentiellen Suche ist das das erste Listenelement und bei der binären Suche ist es das erste Element der rechten Teilliste. Beide Suchen erreichen somit einen konstanten Aufwand <math>T(n)=\mathcal{O}(c)</math>, der unabhängig von n ist.
====Average- und Worst-Case====
Betrachten wir zunächst die sequentielle Suche. Im schlechtesten Fall benötigt sie <math>n</math> Durchläufe, d.h. <math>T(n)=\mathcal{O}(c)</math>. Im mittleren Fall gilt das genauso. Geht man jetzt davon aus, das alle Listenelemente mit <math>x</math> mit gleicher Wahrscheinlichkeit übereinstimmen, ist der Aufwand der sequentiellen Suche <math>T(n)=\frac{1}{n}\sum_{i=1}^{n}i=\frac{n}{2n}(n+1)=\mathcal{O}(n)</math>
Betrachten wir nun den Aufwand der binären Suche. Aufgrund der Arbeitsweise des Algorithmus kann man sofort erkennen, dass sich die Längen der Teillisten in jedem Schritt halbieren. Das gilt besonders dann, wenn die Anzahl der Listenelemente gerade ist. Ist dies der Fall, dann stimmt die Anzahl der rekursiven Aufrufe mit der Anzahl der Halbierungen überein. Das sind genau <math>\log_2 n</math> Stück. Für die Vergleichoperationen (also die in den Zwischenschritten) setzen wir einen konstanten Aufwand <math>\mathcal{O}(c)</math> an. Somit ergibt sich für die binäre Suche im mittleren, sowie schlechtesten Fall ein Gesamtaufwand von <math>T(n)=\mathcal{O}(\log_2 n)</math>
====Fazit====
Da man in der Praxis den Best Case vernachlässigen kann, muss man seine Aufmerksamkeit nur auf den mittleren und den schlechtesten Fall lenken. Der Aufwand der sequentiellen Suche ist linear. Der Aufwand der binären Suche ist logarithmisch. Aufgrund dessen sollte man die binäre Suche der sequentiellen vorziehen.
Um das ganze noch einmal deutlich zu gestalten stellen wir die Aufwände für verschieden große <math>n</math> in einer Tabelle gegenüber:
<p align="center">
<math>\begin{array}{|c|l r|r|r|r|r|}
\hline
f(n) & n= & 2 & 16 & 256 & 1024 & 1048576\\
\hline\\\hline\\
log_2 n & & 1 & 4 & 8 & 10 & 20\\
\hline\\
n & & 2 & 16 & 256 & 1024 & 1048576\\
\hline
\end{array}</math></p>
Spätestens jetzt sollte einem der Unterschied klar geworden sein.
== Multiplikation großer ganzer Zahlen ==
Ein weiterer Teile-und-Herrsche Algorithmus ist die schnelle Multiplikation nach ''KARATSUBA'' und ''OFMAN'' zum Multiplizieren zweier ganzen Zahlen <math>x</math> und <math>y</math>. Jetzt fragt man sich vielleicht, warum man sich noch eine Multiplikationsmethode ausgedacht hat und wieso man als angehender Informatiker davon gehört haben sollte. Wir kennen doch schon mindestens zwei - zum Einen die schriftliche Multiplikation aus der Schule und dann noch die Multiplikation á la Russe. Betrachten wir die Verfahren einmal etwas genauer.
===Aufwand der schriftlichen Multiplikation===
Zu Beginn wollen wir uns den Aufwand der schriftlichen Multiplikation aus unserer frühen Schulzeit ansehen. Für die Multiplikation zweier <math>n</math>-stelliger Zahlen benötigt diese Methode <math>n^{2}</math> Multiplikationen und einige Additionen. Der Aufwand ist mit <math>\mathcal{O}(n^{2})</math> quadratisch.
===Aufwand der Multiplikation á la Russe===
Die Multiplikation á la Russe wurde schon in der Einführung in das Thema ''Algorithmen und Komplexität'' vorgestellt und soll hier nicht weiter erklärt werden. Wir wollen hier nur den Aufwand dieser Methode aufgreifen, um später in diesem Kapitel darauf zurück greifen zu können: <math>\mathcal{O}(n^{2})</math>.
Mit diesen beiden Aufwänden im Hinterkopf betrachten wir nun die schnelle Multiplikation nach ''KARATSUBA'' und ''OFMAN''.
===Der Algorithmus===
Gleich zu Beginn müssen wir eine Vereinbarung treffen. Die zu multiplizierenden Zahlen <math>a</math> und <math>b</math> müssen die gleiche Stelligkeit <math>n</math> haben und <math>n</math> muss gerade sein.
Die beiten Faktoren <math>a</math> und <math>b</math> lassen sich nun in je zwei Teile aufteilen.
<math>a=\underbrace{a_{1}z^{\frac{n}{2}}}_{Teil 1}+\underbrace{a_{0}}_{Teil 2}</math> und <math>b=\underbrace{b_{1}z^{\frac{n}{2}}}_{Teil 1}+\underbrace{b_{0}}_{Teil 2}</math>, wobei <math>z</math> die Basis des Zahlensystems ist (z.B. <math>z=10</math>).Da wir eine gerade Stelligkeit vorraussetzen, sind die beiden Teile der Faktoren gleich lang. Das Produkt ergibt sich nun aus folgender Rechnung:
<math>a \cdot b=(a_{1}z^{\frac{n}{2}}+a_{0})\cdot(b_{1}z^{\frac{n}{2}}+b_{0})\\
a \cdot b=(a_{1} \cdot b_{1})z^{n}+(a_{0}\cdot b_{1}+b_{0}\cdot a_{1})z^{\frac{n}{2}}+a_{0}\cdot b_{0}</math>
Ist man nach dieser einfachen Umstellung der Gleichung nun schon am Ziel und hat den Aufwand reduziert? Die Antwort ist "Nein". Man hat hier nur die Multiplikation der beiden <math>n</math>-stelligen Zahlen <math>a</math> und <math>b</math> auf 4 gleichartige Multiplikationen zweier <math>\frac{n}{2}</math>-stelliger Zahlen zugüglich dreier Additionen von je zwei Zahlen, die nicht mehr als <math>n</math>-stellig sind, zurückgeführt. Die Addition hat eine Aufwand von <math>\Theta(n)</math>. Somit erhält man für den Gesamtaufwand
<math>T(n)=4\cdot T(\frac{n}{2})+\Theta(n)</math>.
Darauf können wir die Meistermethode anwenden. Das Matching führt in diesem Fall zu <math>x=4</math> und <math>y=2</math>. Damit ergibt sich nach <math>f(n)=n^{log_y x}</math> folgendes: <math>f(n)=n^{log_2 4} = n^{\frac{\log 4}{\log 2}} = n^{2}</math> Damit ergibt sich <math>T(n)=\mathcal{O}(n^{2})</math>.
Bis zu dieser Stelle haben wir den selben quadratischen Aufwand wie unsere Schulbuch-Methode. Um nun eine Aufwandsminderung zu erreichen, wird die Gleichung von oben noch ein wenig umgeformt.
<math>a\cdot b=(a_{1}\cdot b_{1})z^{n}+\((a_{1}\cdot b_{1})+(a_{0}\cdot b_{0})-(a_{0}-a_{1})\cdot(b_{0}-b_{1})\)z^{\frac{n}{2}}+a_{0}\cdot b_{0}</math>
Nach dieser Umformung ergibt sich eine kompliziert aussehende Gleichung mit nur noch 3 verschiedene Multiplikationen, die allerdings mehrfach auftreten. Deswegen werden die Multiplikationen substituiert und man erhält eine einfachere Gleichung.
<math>
r=a_{1}\cdot b_{1}\\
s=(a_{0}-a_{1})\cdot(b_{0}-b_{1})\\
t=a_{0}\cdot b_{0}\\
\\
\underline{\underline{a\cdot b = r\cdot z^{n}+(r+t-s)z^{\frac{n}{2}}+t}}
</math>
Jetzt kann man sehr gut erkennen, dass Multiplikationen, die mehrfach vorkommen, nur einmal berechnet werden müssen und dass eine aufwändige Multiplikation durch einfachere Addition und Subtraktion ersetzt worden ist. Diese Tatsachen geben diesem Algorithmus den Aufwandsvorteil, was wir im Folgenden noch zeigen werden.
Wir verzichten an dieser Stelle auf ein ausgiebiges Beispiel, da dieses für die Übung vorgesehen ist.
===Der Aufwand der Multiplikation nach KARATSUBA und OFMAN===
[[Bild:Fdeutschmann_Karatsuba.png|framed|Aufwände für Karatsuba-Multiplikation und Schulbuch-Multiplikation]]
<p>Nachdem wir uns nun mit der Funktionsweise des Algorithmus beschäftigt haben, wollen wir nun noch den Aufwand betrachten. Wir haben gerade festgestellt, dass die beiden Multiplikationen <math>r</math> und <math>t</math> je zweimal vorkommen, aber nur einmal mit einem einem Aufwand von jeweils <math>T(\frac{n}{2})</math> berechnet und danach eingesetzt werden. Dazu kommt nun der Aufwand der noch verbleibenden Multiplikation in <math>s</math>, so wie Additionen (<math>\Theta(n)</math>).</p>
<p>Daraus ergibt sich ein Gesamtaufwand von <math>T(n)=3\cdot T(\frac{n}{2})+\Theta(n)</math>.</p>
<p>Die Lösung dieser Gleichung ergibt <math>T(n)=\Theta(n^{\log_2 3}) \approx \Theta(n^{1.585})</math>.</p>
<p> </p>
<p>Hier noch einmal alle drei Aufwände untereinander:
* Schulbuch Multiplikationsmethode: <math>\mathcal{O}(n^2)</math>
* Multiplikation á la Russe: <math>\mathcal{O}(n^2)</math>
* Multiplikation nach Karatsuba und Ofman: <math>\Theta(n^{1.585})</math></p>
<p> </p>
<p>Die nebenstehende Grafik lässt diesen Unterschied noch deutlicher werden.</p>
<p> </p>
<p> </p>
<p> </p>
<p> </p>
<p> </p>
<p> </p>
<!--
Um den Algorithmus besser verstehen zu können, folgt an dieser Stelle eine Beispielrechnung mit folgenden Faktoren:
<math>a=12\\b=35</math>
Bsp:
====
a = 12
b = 35
a = 14 = 1*10 + 4
b = 73 = 7*10 + 3
a*b = (a1*10 + a0)*(b1*10 + b0)
= (a1*b1)*10² + (a1*b0 + a0*b1)*10^1 + a0*b0
= (1*7)*10² + (1*3 + 4*7)*10^1 + 4*3
= 1022
-> das ist NOCH KEIN BESSERES Verfahren
immernoch 4 Multiplikationen und die Addition der Ergebnisse
Karatsuba stellt die Formel um, damit man nur noch 3 Multiplikationen hat:
(a1*b1)*10² + ((a1*b1)+(a0*b0)-(a0-a1)*(b0-b1))*10^1 + a0*b0
noch ist die Formel kompliziert -> einige Multiplikationen kommen doppelt vor
deswegen:
r = a1*b1
s = (a0-a1)*(b0-b1)
t = a0*b0
und schreibt:
r*10² + (r + t - s)*10^1 + t
dabei fällt auf, dass man r und t nur einmal berechnet, aber je zweimal verwendet
Für das Beispiel von oben heißt das:
r = a1*b1 = 1*7 = 7
s = (a0-a1)*(b0-b1) = (4-1)*(3-7) = -12
t = a0*b0 = 4*3 = 12
7*10² + (7+12+12)*10 + 12
= 700 + 310 + 12 = 1022
Das Verfahren ist für kleine 2-stellige Zahlen unnötig kompliziert.
Nimmt man aber größere Zahlen, ist der Nutzen besser
Bsp:
====
Zahl a: 7245 = 72*10 + 45
a1 = 72
a0 = 45
Zahl b: 5273 = 52*10 + 73
b1 = 52
b0 = 73
r = a1*b1 = 3744
s = (a0-a1)*(b0-b1) = (45-72)*(73-52) = -567
t = a0*b0 = 45*73 = 3285
3744*10^4 + (3744 + 3285 + 567)*10² + 3285
= 453645
Allgemein bedeutet das für längere Zahlen:
==========================================
a = a1*10^(n/2) + a0
b = b1*10^(n/2) + b0
Das Produkt mithilfe der 3 Multiplikationen von Zahlen der Länge (n/2) = 2^(k-1):
a*b = a1*b1*10^n + (a1*b1 + a0*b0 - (a0-a1)*(b0-b1))*10^(n/2) + a0*b0
-->
== Schnelle Matrixmultiplikation==
<p>Die Multiplikation von zwei Matrizen mit <math>n</math> Zeilen und <math>n</math> Spalten ist ja schon aus der Schule bekannt.</p>
<p> </p>
<p><math>A=\begin{pmatrix} a_{1,1} & a_{1,2} & a_{1,3} & a_{1,4}\\ a_{2,1} & a_{2,2} & a_{2,3} & a_{2,4}\\ a_{3,1} & a_{3,2} & a_{3,3} & a_{3,4}\\a_{4,1} & a_{4,2} & a_{4,3} & a_{4,4}\end{pmatrix}</math>, <math>B=\begin{pmatrix} b_{1,1} & b_{1,2} & b_{1,3} & b_{1,4}\\ b_{2,1} & b_{2,2} & b_{2,3} & b_{2,4}\\ b_{3,1} & b_{3,2} & b_{3,3} & b_{3,4}\\b_{4,1} & b_{4,2} & b_{4,3} & b_{4,4}\end{pmatrix}</math></p>
<p> </p>
<p>Um die Ergebnismatrix <math>C=A\cdot B</math> zu erhalten, folgt man im Allgemeinen folgender Regel:</p>
<p> </p>
<p><math>C=
\begin{pmatrix} a_{1,1} & a_{1,2} & a_{1,3} & a_{1,4}\\ a_{2,1} & a_{2,2} & a_{2,3} & a_{2,4}\\ a_{3,1} & a_{3,2} & a_{3,3} & a_{3,4}\\a_{4,1} & a_{4,2} & a_{4,3} & a_{4,4}\end{pmatrix}\cdot\begin{pmatrix} b_{1,1} & b_{1,2} & b_{1,3} & b_{1,4}\\ b_{2,1} & b_{2,2} & b_{2,3} & b_{2,4}\\ b_{3,1} & b_{3,2} & b_{3,3} & b_{3,4}\\b_{4,1} & b_{4,2} & b_{4,3} & b_{4,4}\end{pmatrix}\\
\\C=
\begin{pmatrix}
(a_{1,1}\cdot b_{1,1} + a_{1,2}\cdot b_{2,1} + a_{1,3}\cdot b_{3,1} + a_{1,4}\cdot b_{4,1}) & \cdots & \cdots & (a_{1,1}\cdot b_{1,4} + a_{1,2}\cdot b_{2,4} + a_{1,3}\cdot b_{3,4} + a_{1,4}\cdot b_{4,4})\\
\vdots & \ddots & & \vdots\\
\vdots & & \ddots & \vdots\\
(a_{4,1}\cdot b_{1,1} + a_{4,2}\cdot b_{2,1} + a_{4,3}\cdot b_{3,1} + a_{4,4}\cdot b_{4,1}) & \cdots & \cdots & (a_{4,1}\cdot b_{1,4} + a_{4,2}\cdot b_{2,4} + a_{4,3}\cdot b_{3,4} + a_{4,4}\cdot b_{4,4})
\end{pmatrix}</math></p>
<p> </p>
<p>Ausgehend von <math>n</math> (Zeilen oder Spalten der beiden Matrizen <math>A</math> und <math>B</math>), erkennt man schon an der allgemeinen Form, dass daraus <math>n^3</math> Multiplikationen entstehen und dieses Verfahren somit einen Aufwand von <math>\mathcal{O}(n^3)</math> benötigt.</p>
<p>Ende der 60er-Jahre hatte ''STRASSEN'' einer Idee, welche die typische Teile-und-Herrsche Strategie verfolgte. Matrizen teilte er in Teilmatrizen auf und multiplizierte diese untereinander und ermittelte das Produkt, mithilfe aufwandsmäßig günstigen Operationen, mit einem geringeren Gesamtaufwand.</p>
== Quellen ==