Dynamische Programmierung Das Prinzip „Teile und Herrsche“ diente als Grundlage für die Entwicklung vieler der untersuchten Algorithmen. Um ein umfangreiches Problem zu lösen, zerlege man es in kleinere Probleme, die unabhängig voneinander gelöst werden können. In der dynamischen Programmierung wird dieses Prinzip bis zum Extrem weiterentwickelt: Wenn nicht bekannt ist, welche kleineren Probleme zu lösen sind, löst man einfach alle und speichert dann die Ergebnisse zum Zwecke der späteren Verwendung bei der Lösung größerer Probleme. Dieser Ansatz ist in „Operations Research“ weit verbreitet. Hierbei bezieht sich der Begriff „Programmierung“ auf den Prozess der Formulierung der Bedingungen eines Problems, um das Verfahren anwendbar zu machen. G.Heyer 1 Algorithmen und Datenstrukturen 2 Prinzip und Voraussetzung 1. Charakterisiere den Lösungsraum und die Struktur der intendierten optimalen Lösung 2. Definiere rekursiv, wie sich eine optimale Lösung (und der ihr zugeordnete Wert) aus kleineren optimalen Lösungen (und deren Werte) zusammensetzt. 3. Konzipiere den Algorithmus in einer bottom-up Weise so, dass für n=1,2,3, ... tabellarisch optimale Teillösungen (und deren zugeordnete Werte) gefunden werden. Beim Finden einer bestimmten optimalen Teillösung der Größe k>1 ist auf alle optimalen Teillösungen der Größe < k zurückzugreifen. Voraussetzung: Die optimale Lösung für ein Problem der Größe n setzt sich aus optimalen Teillösungen kleinerer Größe zusammen. (Bellmannsches Optimalitätsprinzip) G.Heyer 2 Algorithmen und Datenstrukturen 2 Bei der Anwendung der dynamischer Programmierung können zwei Schwierigkeiten auftreten: - Erstens muss es nicht immer möglich sein, die Lösungen kleinerer Probleme so zu kombinieren, dass sich die Lösung eines größeren Problems ergibt. - Zweitens kann die Anzahl der zu lösenden Probleme unvertretbar groß sein. Es ist noch nicht gelungen, genau anzugeben, welche Probleme mit Hilfe der dynamischen Programmierung in effizienter Weise gelöst werden können; es gibt viele „schwierige“ Probleme, für die sie nicht anwendbar zu sein scheint, aber auch viele „leichte“ Probleme, für die sie weniger effizient ist als Standardalgorithmen. G.Heyer 3 Algorithmen und Datenstrukturen 2 Das Produkt mehrerer Matrizen Ein klassischer Anwendungsfall der dynamischen Programmierung ist das Problem der Minimierung des Rechenaufwands, der für die Multiplikation einer Reihe von Matrizen unterschiedlicher Dimension erforderlich ist. a11 a12 c11 b11 b12 b13 a21 a22 e11 e12 f11 f12 f13 c21 d11 d12 a31 a32 b21 b22 b23 e21 e22 f21 f22 f23 c31 a41 a42 4x2 2x3 3x1 1x2 2x2 2x3 Ziel: Multiplikation der 6 Matrizen Natürlich muss die Anzahl der Spalten in einer Matrix stets mit der Anzahl der Zeilen in der folgenden Matrix übereinstimmen, damit die Multiplikation ausführbar ist. G.Heyer 4 Algorithmen und Datenstrukturen 2 Die Gesamtanzahl der erforderlichen Skalar-Multiplikationen hängt von der Reihenfolge ab, in der die Matrizen multipliziert werden. Im Beispiel könnte man von links nach rechts vorgehen: AxB 24 Skalar-Multipl. neue Matrix M1 4 x 3 M1 x C 12 Skalar-Multipl. neue Matrix M2 4 x 1 M2 x D 8 Skalar-Multipl. neue Matrix M3 4 x 2 ... nach 84 Skalar-Multipl. Ergebnismatrix 4x3 Geht man statt dessen von rechts nach links vor, erhält man als Ergebnis die gleiche Matrix der Dimension 4 x 3 nach nur 69 Skalarmultiplikationen . Es sind auch noch viele andere Reihenfolgen möglich. G.Heyer 5 Algorithmen und Datenstrukturen 2 Die Reihenfolge der Multiplikation kann durch das Setzen von Klammern ausgedrückt werden, z. B. entspricht die Reihenfolge von links nach rechts dem Ausdruck ( ( ( ( ( AB ) C ) D ) E ) F ) und die Reihenfolge von rechts nach links dem Ausdruck ( A ( B ( C ( D ( EF ) ) ) ) ). Jedes zulässige Setzen von Klammern führt zum richtigen Ergebnis, doch wann ist die Anzahl der Skalar-Multiplikation am kleinsten? Wenn große Matrizen auftreten, können beträchtliche Einsparungen erzielt werden: Wenn z. B. die Matrizen B, C und F im obigen Beispiel eine Dimension von 300 statt von 3 besitzen, sind bei der Reihenfolge von links nach rechts 6024 Skalar-Multipl. erforderlich, bei der Reihenfolge von rechts nach links dagegen die astronomische Zahl von 274 200 Multiplikationen auszuführen. G.Heyer 6 Algorithmen und Datenstrukturen 2 Demnach ergibt die Multiplikation einer Matrix der Dimension p x q mit einer Matrix der Dimension q x r eine Matrix der Dimension p x r, wobei jedes Element mit Hilfe von q Multiplikationen berechnet wird, so dass insgesamt p q r Multiplikationen ausgeführt werden müssen. Allgemeiner Fall: N Matrizen sind miteinander zu multiplizieren: M1 M2 M3 ... Mn wobei die Matrizen der Bedingung genügen, dass Mi für 1 i < N ri Zeilen und ri+1 Spalten ausweist. Ziel: Diejenigen Reihenfolge der Multiplikation der Matrizen zu finden, für die die Gesamtzahl der auszuführenden Skalar-Multiplikationen minimal wird. G.Heyer 7 Algorithmen und Datenstrukturen 2 Die Anzahl der Reihenfolgen ist eine kombinatorische Funktion, die Katalanische Zahl genannt wird; die Anzahl der Möglichkeiten, N Variablen zu klammern, beträgt ungefähr 4 N-1 / N N Die Lösung des Problems mit Hilfe der dynamischen Programmierung besteht darin, „von unten nach oben“ vorzugehen und berechnete Lösungen kleiner Teilprobleme zu speichern, um eine wiederholte Rechnung zu vermeiden. Um die beste Möglichkeit zu ermitteln, M1M2M3 zu multiplizieren, entnehmen wir zuerst der gespeicherten Tabelle die Kosten der Berechnung von M1M2 und addieren dann die Kosten der Multiplikation dieses Ergebnisses mit M3 dazu. Diese Summe wird mit den Kosten verglichen, die enstehen, wenn zuerst die Multiplikation M2M3 ausgeführt und dann mit M1 multipliziert wird, was auf die gleiche Weise berechnet werden kann. G.Heyer 8 Algorithmen und Datenstrukturen 2 Die kleinere der beiden Summen wird gespeichert und ebenso wird mit allen anderen Tripeln verfahren. Anschließend berechnet man unter Berücksichtigung aller Informationen die beste Möglichkeit Quadrupel Matrizen zu multiplizieren. Indem man in dieser Weise fortfährt, findet man schließlich die beste Möglichkeit, alle Matrizen miteinander zu multiplizieren. Für 1 j N - 1 ermittelt man die minimalen Kosten der Berechnung von Mi M i+1 . . . M i + j‘ indem man für 1 i N-j und für jedes k zwischen i und i + j die Kosten der Berechnung von M i M i + 1 . . . Mk-1 ermittelt und dann die Kosten addiert, die bei der Multiplikation dieser Ergebnisse entstehen. Da man stets eine Gruppe in zwei kleinere Gruppen zerlegt, braucht man die minimalen Kosten für die beiden Gruppen nur aus einer Tabelle zu entnehmen und nicht neu zu berechnen. G.Heyer 9 Algorithmen und Datenstrukturen 2 Dies führt zu folgendem Programm: for ( i = 1 ; i <= N ; i++ ) for ( j = i + 1 ; j < = N ; j++ ) cost [i] [j] = INT_MAX ; for ( i = 1 ; i <= N ; i++ ) cost [i] [i] = 0 ; for ( j = 1 ; j < N ; j++ ) for ( i = 1 ; i <= N - j ; i++ ) for ( k = i + 1 ; k <= i + j ; k++ ) { t = cost [i][k - 1] + cost [k][i+j] + r[i] * r[k] * r[i+j+1]; if ( t < cost [i][i+j] ) { cost [i][i+j] = t ; best [i][i+j] = k ; } } G.Heyer 10 Algorithmen und Datenstrukturen 2 cost [l][r] gibt die minimalen Kosten der Berechnung von Ml M l+1 . . . Mr an; die Kosten für die erste oben angegebene Gruppe betragen cost [i][k-1], die Kosten für die zweite Gruppe cost [k][i + j] . Die Kosten für die abschließenden Multiplikationen lassen sich leicht bestimmen: Mi Mi+1 . . M k-1 ist eine Matrix der Dimension r i x r k‘ und M k M k+1 ... M i+j ist eine Matrix der Dimension rk x r i+j+1, so dass die Kosten der Multiplikation dieser beiden Matrizen ri rk r i+j+1 betragen. Auf diese Weise berechnet das Programm cost [i][i+j] für 1 i N - j, wobei j von 1 bis N-1 wächst. Wenn man j = N - 1 erreicht (und i = 1), hat man die gesuchten minimalen Kosten der Berechnung M1 M2 . . . MN gefunden. G.Heyer 11 Algorithmen und Datenstrukturen 2 Die getroffenen Entscheidungen werden in einem getrennten Feld best registriert, um sie später, wenn die tatsächliche Folge der Multiplikationen erzeugt werden soll, wieder bestimmen zu können. Das folgende Programm stellt die Implementation dieses Prozesses der Ermittlung der optimalen Anordnung der Klammern anhand der mit Hilfe des obigen Programms berechneten Felder cost und best dar. order (int i, int j ) { if (i == j ) printf ( „%c“ , name ( i ) ) ; else { printf ( „(„ ) ; order ( i, best [i][j] -1) ; order (best[i][j], j ); printf ( „)“ ) ; } } G.Heyer 12 Algorithmen und Datenstrukturen 2 Lösung des Problems der Multiplikation mehrerer Matrizen A B C D E F 24 14 22 26 36 [A][B] B [A][BC] [ABC][D] [ABC][DE] [ABC][DEF] 6 [B][C] C 10 [BC][D] 6 [C][D] D E 14 [BC][DE] 22 [BC][DEF] 10 19 [C][DE] [C][DEF] 4 10 [D][E] [DE][F] 12 [E][F] G.Heyer 13 Algorithmen und Datenstrukturen 2 Beschreibung zur Tabelle Sie zeigt den Ablauf der oben gezeigten Programme für das angegebene Beispiel. Sie gibt die Gesamtkosten und die optimale „letzte“ Multiplikation für jede Teilfolge in der Liste der Matrizen an. Z. B. besagt die Eintragung in der Zeile A und der Spalte F, dass 36 Skalar-Multiplikationen erforderlich sind, um die Matrizen A bis F zu multiplizieren, und dass dies erreicht werden kann, indem A bis C auf optimale Weise multipliziert werden, dann D bis F auf optimale Weise multipliziert werden und danach die erhaltenen Matrizen miteinander multipliziert werden. Nur D ist in dem Feld best enthalten. Für obiges Beispiel ist die ermittelte Anordnung der Klammern ( (A ( BC ) ) ( ( DE ) F ) , wofür nur 36 SkalarMultiplikationen benötigt werden. Für das Beispiel, wo die Dimension von 3 auf 300 geändert wurde, ist die gleiche Anordnung der Klammern optimal, wobei hier 2412 Skalar-Multiplikationen erforderlich sind. G.Heyer 14 Algorithmen und Datenstrukturen 2 Eigenschaft: Mit Hilfe der dynamischen Programmierung kann das Problem der Multiplikation mehrerer Matrizen in einer zu N3 proportionalen Zeit und mit einem zu N2 proportionalen Speicheraufwand gelöst werden. G.Heyer 15 Algorithmen und Datenstrukturen 2 Beispiel: Traveling Salesman Problem (TSP) Gegeben: n Städte, Entfernungsmatrix M = (mij), mij Entfernung von Stadt i nach Stadt j. Gesucht: Rundreise über alle Städte mit minimaler Länge, also Permutation : {1,...,n} -> {1,...,n} so daß n-1 c(p) = S m(i),(i+1) + m(n),(1) i=1 minimal. NP-vollständig (Rechenzeit mit großer Sicherheit exponentiell) Naiver Algorithmus: alle (n-1)! viele Reihenfolgen betrachten. G.Heyer 16 Algorithmen und Datenstrukturen 2 Dynamische Programmierung: Sei g (i,S) Länge des kürzesten Weges von i über jede Stadt in S (jeweils genau 1 Besuch) nach Stadt 1. Lösung des TSP also g(1, {2,...,n}). (Stadt 1 kann beliebig gewählt werden, da Rundreise gesucht wird) Es gilt: g(i,S) = G.Heyer mi1 minjS (mij + g ( j, S -{ j } ) 17 falls S = {} sonst Algorithmen und Datenstrukturen 2 Algorithmus konstruiert Tabelle : for ( i = 2 ; i = n; i++) g[i,{}] = mi1 for ( k = 1; k = n-2 ; k++ ) for (S, |S| = k, 1S ) for (i {2,...,n} - S ) Berechne g[i,S] gemäß Formel Berechne g[1,{ 2,...,n } gemäß Formel Komplexität: Tabellengröße * Aufwand Tabelleneintrag Größe: < n 2n (Anzahl der i's mal Anzahl betrachtete Teilmengen) Tabelleneintrag: Suche nach Minimum unter j aus S: O(n) Insgesamt: O(n22n), deutlich besser als (n-1)! . G.Heyer 18 Algorithmen und Datenstrukturen 2 Beispiel: 4 Städte, symmetrische Entfernungen, M: 1 2 3 4 1 0 4 9 8 2 4 0 12 2 3 9 12 0 10 4 8 2 10 0 Tabelleneinträge: g[2,{}] = 4 g[3,{}] = 9 g[4,{}] = 8 g[2,{3}] = 12 + 9 g[2,{4}] = 2 + 8 g[3,{2}] = 12 + 4 g[3,{4}] = 10 + 9 g[4,{2}] = 2 + 4 g[4,{3}] = 10 + 9 G.Heyer = 21 = 10 = 16 = 19 =6 = 19 19 Algorithmen und Datenstrukturen 2 g[2,{3,4}] = min(m23 + g[3,{4}] , m24 + g[4,{3}] ) = 21 g[3,{2,4}] = min(m32 + g[2,{4}] , m34 + g[4,{2}] ) = 16 g[4,{2,3}] = min(m42 + g[2,{3}] , m43 + g[3,{2}] ) = 23 g[1,{2,3,4}] = min(m12 + g[2,{3,4}] , m13 + g[3,{2,4}], m14 + g[4,{2,3}]) = 25 Lösung: 1, 2, 4, 3, 1 G.Heyer 20 Algorithmen und Datenstrukturen 2 Das Rucksack-Problem Ein Dieb, der einen Safe ausraubt, findet in ihm N Typen von Gegenständen unterschiedlicher Größe und unterschiedlichen Werts, hat aber nur einen kleinen Rucksack der Größe M zur Verfügung, um die Gegenstände zu tragen. Das Rucksack-Problem besteht darin, diejenige Kombination von Gegenständen zu finden, die der Dieb für seinen Rucksack auswählen sollte, so dass der Gesamtwert der von ihm geraubten Gegenstände maximal wird. Beispiel: Der Rucksack besitzt ein Fassungsvermögen von 17, der Safe enthält viele Gegenstände mit unterschiedlichen Größen und den angegebenen Werten. (Die Bezeichnungen der Gegenstände werden im Programm in Indizes umgewandelt.) G.Heyer 21 Algorithmen und Datenstrukturen 2 Abbildung zum Rucksack-Problem 3 4 7 8 9 Wert 4 Bezeichnung A 5 B 10 C 11 D 13 E Größe Der Dieb kann dann 5 Gegenstände A ( jedoch nicht 6) mitnehmen, so dass die gesamte Beute den Wert 20 hat, oder er kann seinen Rucksack mit einem D und einem E füllen, was einen Gesamtwert von 24 ergibt, oder er kann andere Kombinationen ausprobieren. Doch für welche Kombination wird der Gesamtwert maximal? G.Heyer 22 Algorithmen und Datenstrukturen 2 Bedeutung des Rucksack-Problems auch im kommerziellen Bereich: z. B. ist es für eine Reederei von Interesse, die beste Möglichkeit zu kennen, wie ein Lastkraftwagen oder ein Transportflugzeug mit Gütern für die Verschiffung beladen werden kann. Bei solchen Anwendungsfällen können auch andere Varianten des Problems auftreten: Es könnte z. B. sein, dass von jedem Gegenstand nur eine begrenzte Anzahl vorhanden ist. Für viele solche Varianten ist der gleiche Ansatz geeignet. Zur Lösung des Rucksack-Problems mit Hilfe der dynamischen Programmierung berechnet man die beste Kombination für alle Größen eines Rucksacks bis M. G.Heyer 23 Algorithmen und Datenstrukturen 2 Diese Berechnung kann in sehr effizienter Weise realisiert werden, indem die Operationen in einer zweckmäßigen Reihenfolge ausgeführt werden: for ( j = 1 ; j <= N ; j ++ ) { for ( i = 1 ; i <= M ; i++ ) if ( i >= size [ j ] ) if ( cost [i] < cost [i - size [j] ] + val [j] ) { cost [i] = cost [i - size [j]] + val [j]; best [i] = j ; } } G.Heyer 24 Algorithmen und Datenstrukturen 2 In diesem Beispiel ist: cost [i] der größte Wert, der mit einem Rucksack mit dem Fassungsvermögen i erzielt werden kann, best [i] ist das letzte Element, das hinzugefügt wurde, um das Maximum zu realisieren. Zuerst berechnet man für alle Größen des Rucksacks den maximalen Wert, wenn nur Elemente vom Typ A verwendet werden, danach berechnet man den maximalen Wert, wenn nur Elemente A und B verwendet werden usw. Die Lösung reduziert sich auf eine einfache Berechnung von cost [i]. Annahme: Auswahl des Elements j für den Rucksack, dann wäre der beste Gesamtwert, der erzielt werden könnte val [j] (für das Element) + cost [i - size [j]] (um den Rest des Rucksacks aufzufüllen) . Wenn dieser Wert den besten Wert übersteigt, der ohne ein Element j erreicht werden kann, aktualisiert man cost [i] und best [i]; andernfalls lässt man diese Größen unverändert. G.Heyer 25 Algorithmen und Datenstrukturen 2 Lösung des Rucksack-Problems k 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 j=1 cost [k] 4 4 4 8 8 8 12 12 12 16 16 16 20 20 20 best [k] AAAAAAAA A A A A A A A j=2 cost [k] 4 5 5 8 9 10 12 13 14 16 17 18 20 21 22 best [k] AB B A B B A B B A B B A B B j=3 cost [k] 4 5 5 8 10 10 12 14 15 16 18 20 20 22 24 best [k] AB B A C D A C C A C C D C C j=4 cost [k] 4 5 5 8 10 11 12 14 15 16 18 20 21 22 24 best [k] A B B A C D A C C A C C D C C j=5 cost [k] 4 5 5 8 10 11 13 14 15 17 18 20 21 23 24 best [k] A B B A C D E C C E C C D E C G.Heyer 26 Algorithmen und Datenstrukturen 2 Erklärung zur Lösung des Beispiels Das erste Zeilenpaar zeigt den maximalen Wert (den Inhalt der Felder cost und best ), wenn nur Elemente A benutzt werden; das zweite Zeilenpaar zeigt den maximalen Wert, wenn nur Elemente A und B verwendet werden, usw. Der höchste Wert, der mit einem Rucksack der Größe 17 erreicht werden kann, ist 24. Im Verlaufe der Berechnung dieses Ergebnisses hat man auch viele Teilprobleme gelöst, z. B. ist der größte Wert, der mit einem Rucksack der Größe 16 erreicht werden kann, 22, wenn nur Elemente A, B und C verwendet werden. Der tatsächliche Inhalt des optimalen Rucksacks kann mit Hilfe des Feldes best berechnet werden. Per Definition ist best[M] in ihm enthalten, und der restliche Inhalt ist der gleiche wie im optimalen Rucksack der Größe M - size [best [M]] usw. G.Heyer 27 Algorithmen und Datenstrukturen 2 Eigenschaft: Für die Lösung des Rucksack-Problems mit Hilfe der dynamischen Programmierung wird eine zu N M proportionale Zeit benötigt. Somit kann das Rucksack-Problem leicht gelöst werden, wenn M nicht groß ist; für große Fassungsvermögen kann die Laufzeit jedoch unvertretbar groß werden. Eine grundlegende Schwierigkeit ist es, das das Verfahren nicht anwendbar ist, wenn M und die Größen oder Werte z. B. reelle Zahlen anstatt ganzer Zahlen sind. Wenn jedoch die Fassungsvermögen sowie die Größen und Werte der Gegenstände ganze Zahlen sind, so gilt das grundlegende Prinzip, dass optimale Entscheidungen nicht geändert werden müssen, nachdem sie einmal getroffen wurden. Jedesmal, wenn dieses allgemeine Prinzip zur Anwendung gebracht werden kann, ist die dynamische Programmierung anwendbar. G.Heyer 28 Algorithmen und Datenstrukturen 2 Optimale binäre Suchbäume Bei vielen Suchanwendungen ist bekannt, dass die Suchschlüssel mit stark variierenden Häufigkeiten auftreten können, z. B. wird ein Programm, das die Schreibweise von Wörtern in einem deutschen Text prüft, wahrscheinlich viel öfter nach Wörtern wie „und“ und „der“ suchen, als nach Wörtern wie „dynamische“ und „Programmierung“. In ähnlicher Weise wird ein C-Compiler sicher weit häufiger Schlüsselwörter wie „if“ und „for“ aufsuchen, als „goto“ oder „ main“. Wenn Suche in einem Binärbaum angewandt wird, ist es vorteilhaft, die am häufigsten gesuchten Schlüssel in der Nähe der Spitze des Baumes anzuordnen. Um zu bestimmen, wie die Schlüssel im Baum anzuordnen sind, so dass die Gesamtkosten der Suche minimiert werden, kann ein Algorithmus der dynamischen Programmierung benutzt werden. G.Heyer 29 Algorithmen und Datenstrukturen 2 Jeder Knoten im binären Suchbaum ist mit einer ganzen Zahl gekennzeichnet, von der angenommen wird, dass sie der Häufigkeit des Zugriffs auf diesen Knoten entspricht. Das besagt, dass zu erwarten ist, dass bei jeweils 18 Suchvorgängen (im Beispielbaum) in diesem Baum viermal nach A gesucht wird, zweimal nach B, einmal nach C usw. Bei jedem der vier Suchvorgänge, die A betreffen, sind zwei Zugriffe auf Knoten erforderlich, bei jedem der zwei Suchvorgänge, die B betreffen, drei Zugriffe auf Knoten usw. Man kann ein Maß für die „Kosten“ des Baumes berechnen, indem man die jedem Knoten zugeordnete Häufigkeit mit seinem Abstand von der Wurzel multipliziert und dann die Summe dieser Produkte bildet. Dies ist die gewichtete innere Pfadlänge des Baumes. G.Heyer 30 Algorithmen und Datenstrukturen 2 Ein binärer Suchbaum mit Häufigkeiten C 1 F A 2 4 D B 3 2 E 5 G 1 Für diesen Baum beträgt die gewichtete innere Pfadlänge 4 * 2 + 2 * 3 + 1 * 1 + 3 * 3 + 5 * 4 + 2 * 2 + 1 * 3 = 51 . Ziel: Für die gegebenen Schlüssel mit den gegebenen Häufigkeiten den binären Suchbaum bestimmen, der unter allen solchen Bäumen die kleinste innere Pfadlänge besitzt. G.Heyer 31 Algorithmen und Datenstrukturen 2 Dieses Problem weist Ähnlichkeiten zu dem Problem der Minimierung der gewichteten äußeren Pfadlänge auf, das bei der Betrachtung der Huffman-Codierung untersucht wurde. Bei der Huffman-Codierung war es jedoch nicht erforderlich, die Reihenfolge der Schlüssel beizubehalten; bei dem binären Suchbaum muss die Eigenschaft erhalten bleiben, dass alle links von der Wurzel befindlichen Knoten Schlüssel besitzen, die kleiner sind usw. Diese Forderung bewirkt, dass das Problem dem oben betrachteten Problem der Multiplikation mehrerer Matrizen sehr ähnlich ist; es kann praktisch das gleiche Programm verwendet werden. G.Heyer 32 Algorithmen und Datenstrukturen 2 Annahme: Gegeben: Menge von Suchschlüsseln K1 < K2 < ... < KN und Menge von zugehörigen Häufigkeiten r0, r1, . . . ,rN wobei ri die vermutete Häufigkeit des Zugriffs auf den Schlüssel Ki ist. Ziel: Bestimmung des binären Suchbaums, für den die über alle Schlüssel gebildete Summe der Produkte dieser Häufigkeiten mit den Abständen des Schlüssels von der Wurzel ( den Kosten des Zugriffs auf den entsprechenden Knoten) minimal wird. Der Zugang zu diesem Problem mittels dynamischer Programmierung besteht darin, der Reihe nach für jedes 1 j N - 1 die beste Möglichkeit zu berechnen, einen Unterbaum zu erzeugen, der Ki, Ki+1, ... , Ki + j für 1 i N - j enthält. G.Heyer 33 Algorithmen und Datenstrukturen 2 Implementierung for ( i = 1 ; i <= N ; i++ ) for ( j = i +1; j <= N+1 ; j++ ) cost[i] [j] = INT_MAX ; for ( i = 1 ; i <= N ; i++ ) cost[i] [i] = f [i]; for ( i = 1 ; i <= N+1; i++ ) cost[i] [i-1] = 0 ; for ( j = 1 ; j <= N - 1 ; j++ ) for ( i = 1 ; i <= N - j ; i++ ) { for ( k = i ; k <= i + j ; k++ ) { t = cost[i] [k - 1] + cost[k+1] [i + j] ; if ( t < cost[i] [i+j] ) { cost[i] [i + j] = t ; best[i] [i+j] = k ; } } for ( k = i ; k <= i + j ; cost[i] [i+j] += f[k++] ) ; } G.Heyer 34 Algorithmen und Datenstrukturen 2 Für jedes j wird die Berechnung ausgeführt, indem jeder Knoten als Wurzel ausprobiert wird und im voraus berechnete Werte verwendet werden, um die beste Möglichkeit zur Erzeugung der Unterbäume zu ermitteln. Für jedes i k i + j möchte man den optimalen Baum mit Kk als Wurzel finden, der Ki , Ki+1 , . . . , Ki+j enthält. Dieser Baum wird gebildet, indem der optimale Baum für Ki , Ki+1 , . . . , Kk-1 als der linke Unterbaum und der optimale Baum für Kk+1 , K k+2 , . . . , K i+j als der rechte Unterbaum verwendet wird. Die innere Pfadlänge dieses Baumes ist gleich der Summe der inneren Pfadlänge der beiden Unterbäume und der Summe der Häufigkeiten für alle Knoten (da jeder Knoten in dem neuen Baum einen Schritt weiter von der Wurzel entfernt ist). G.Heyer 35 Algorithmen und Datenstrukturen 2 Optimaler binärer Suchbaum D 3 A E 4 5 B F 2 2 C G 1 1 Die gewichtete innere Pfadlänge dieses Baumes beträgt 41. Man muss beachten, dass die Summe aller Häufigkeiten jedesmal zu den Kosten addiert wird, weshalb sie für die Bestimmung des Minimums nicht benötigt wird. Weiterhin muss cost[i][i - 1] = 0 gelten, um die Möglichkeit zu berücksichtigen, dass ein Knoten nur einen Nachfolger hat (beim Problem der Multiplikation mehrerer Matrizen gab es keine analoge Möglichkeit). Wie zuvor ist ein kurzes rekursives Programm erforderlich, um anhand des mit Hilfe des Programms berechneten Felds best den eigentlichen Baum zu rekonstruieren. G.Heyer 36 Algorithmen und Datenstrukturen 2 Eigenschaft: Das Verfahren der dynamischen Programmierung zur Bestimmung eines optimalen binären Suchbaums erfordert einen zu N3 proportionale Zeit und einen zu N2 proportionalen Speicherplatz. Der Algorithmus arbeitet wieder mit einer Matrix der Größe N2 und benötigt für jedes Element eine zu N proportionale Zeit. In Wirklichkeit ist es in diesem Falle möglich, die erforderliche Zeit auf N2 zu reduzieren, indem man die Tatsache ausnutzt, dass die optimale Position der Wurzel eines Baumes nicht zu weit von der optimalen Position für die Wurzel eines etwas kleineren Baumes entfernt sein kann, so dass k in dem obigen Programm nicht alle Werte von i bis i+j durchlaufen muss. G.Heyer 37 Algorithmen und Datenstrukturen 2