Praktische Einführung und Programmierung
Stefan Bosse
Universität Koblenz - FB Informatik
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen ::
Unter einem Paradigma versteht man unter anderem in der Wissenschaftstheorie ein »Denkmuster, welches das wissenschaftliche Weltbild einer Zeit prägt« – ein Algorithmenparadigma sollte daher ein Denkmuster darstellen, das die Formulierung und den Entwurf von Algorithmen und damit letztendlich von Programmiersprachen grundlegend prägt.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Algorithmen
Es gibt zwei grundlegende Arten, Schritte von Algorithmen zu notieren (schießloich die Programmierklassen):
Applikative Algorithmen sind eine Verallgemeinerung der Funktionsauswertung mathematisch notierter Funktionen.
Imperative Algorithmen basieren auf einem einfachen Maschinenmodell mit gespeicherten und änderbaren Werten.
In der Informatik werden weitere Paradigmen diskutiert: logisch, objektorientiert, agentenorientiert, parallel.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Applikative Algorithmen
Die Idee applikativer Algorithmen besteht darin, eine Definition zusammengesetzter Funktionen durch Terme mit Unbestimmten vorzunehmen. Eine einfache Funktionsdefinition in diesem Sinne kann wie folgt mathematisch notiert werden:
f(x)=5x+1
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Applikative Algorithmen
Applikative Algortihmen entsprechen den funktionalen Programmiersprachen und bestehen aus:
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Applikative Algorithmen
Gegeben sind im Folgenden zwei (unendliche, abzählbare) Mengen von Symbolen (als »Unbestimmte« bezeichnet):
x, y, z, … vom Typ int q, p, r, … vom Typ bool
if b then t else u fi
ein int-Term.Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Applikative Algorithmen
x, x − 2, 2x + 1, (x + 1)(y − 1)
Terme vom Typ int mit Unbestimmten
p, p ∧ true, (p ∨ true) = ⇒ (q ∨ false)
Terme vom Typ bool
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Applikative Algorithmen
7+(9+4)⋅8−1413−sign(−17)⋅15
Bei der Termbildung dienen Klammern und Prioritäten zur Festlegung der Auswertungsreihenfolge
Für Algorithmensprachen gibt es eine weitere Art von Termen , die in normaler Arithmetik nicht eingesetzt werden (oder doch?).
if b then t else u fi
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Applikative Algorithmen
Sind v1, …, vn Unbestimmte vom Typ τ1, …, τn (bool oder int) und ist t(v1, …, vn) ein Term, so heißt
f(v1,…,vn)=t(v1,…,vn)
eine Funktionsdefinition der Funktion f vom Typ τ. τ ist dabei der Typ des Terms t(v1, …, vn).
foo(a,b) = a+b
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Applikative Algorithmen
Jede Funktion ist also bestimmt durch:
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Applikative Algorithmen
Definierte Funktionen können mit konkreten Werten aufgerufen und ausgewertet werden.
Eine Funktionsdefinition definiert eine Funktion mit der folgenden Signatur:
f:τ1×…×τn→τ
Sind nun a1, .., an Werte vom Typ τ1, .., τn, so ersetzt man bei der Auswertung von f(a1, .., an) im definierenden Term jedes Vorkommen der Unbestimmten vi durch den Wert ai und wertet dann den entstehenden Term t(a1, .., an) aus.
Die konkreten Werte a1, …, an heißen aktuelle Parameter.
Den Ausdruck f(a1, …, an) bezeichnen wir als Funktionsaufruf.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Applikative Algorithmen
f(x, y) = if g(x, y) then h(x + y) else h(x − y) fig(x, y) = (x = y) ∨ odd(y)h(x) = j(x + 1) · j(x − 1)j(x) = 2x − 3
Das grundlegende Programmierparadigma von funktionalen Sprachen (Haskell, OCaML)
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Applikative Algorithmen
addp Funktionsauswertung; obwohl scheinbar mehrschrittig wird die Berechnung nur durch Datenabhängigkeiten "gesteuert". Es findet eine schrittweise Termreduktion durch Einsetzen und Zusammenfassen sowie Berechnung mit grundlegenden Operationen statt.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Applikative Algorithmen
Schließlich kann zusammengefasst werden:
Ein applikativer Algorithmus ist eine Liste von Funktionsdefinitionen:
f1(v1,1,…,v1,n1)=t1(v1,1,…,v1,n1) ⋮ fm(v1,m,…,vm,n1)=tm(vm,1,…,vm,nm)
Die erste Funktion f1 wird wie beschrieben ausgewertet und ist die Bedeutung (Semantik) des Algorithmus (quasi die main Funktion).
Applikative Algorithmen sind die Grundlage einer Reihe von universellen Programmiersprachen, wie APL, Lisp, Scheme, Haskell oder Scala etc. Diese Programmiersprachen werden als funktionale Programmiersprachen bezeichnet.
Ein applikativer Algorithmus muss nicht für alle Eingabewerte zu einem definierten Ergebnis führen. Das folgende Beispiel verdeutlicht dies.
f(x) = if x = 0 then 0 else f(x − 1) fi
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Applikative Algorithmen
f(−1) → f(−2) → … Auswertung terminiert nicht!
Eine nicht terminierende Berechnung führt als Vereinbarung zum Ergebnis ⊥ für undefiniert. Also gilt:
f(x)={0x≥0⊥
x! = x(x − 1)(x − 2) … 2 · 1 für x > 0fac(x) = if x = 0 then 1 else x · fac(x − 1) fi
Beispiel für einen applikativen Algorithmus: Fakultät mit Rekursion
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Applikative Algorithmen
f0 = f1 = 1, fi = fi−1 + fi−2 für i > 0fib(x) = if (x = 0) ∨ (x = 1) then 1 else fib(x − 2) + fib (x − 1) fi
Beispiel für einen applikativen Algorithmus: Fibonacci Zahlen mit Rekursion
f(x) = if x > 100 then x − 10 else f(f(x + 11)) fi⇔f(x) = if x > 100 then x − 10 else 91 fi
Noch ein kurioses Beispiel: Die McCarthys 91 Funktion und zwei Funktionen mit gleicher Ein- und Ausgabemenge und Relation
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Imperative Algorithmen
Die imperativen Algorithmen bilden die verbreitetste Art, Algorithmen für Computer zu formulieren, da sie auf einem abstrakten Modell eines üblichen Rechners basieren.
Wir hatten trotzdem zuerst applikative Algorithmen betrachtet, da diese mathematisch einfacher zu formalisieren und zu analysieren sind.
Imperative Algorithmen basieren auf den Konzepten Anweisung und Variable.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Imperative Algorithmen
Variablen bilden die Speicherplätze für Werte:
Nach Ausführung von X:=t gilt: X = w(t).
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Imperative Algorithmen
Die möglichen Zustände eines Rechners werden wie folgt festgelegt:
Z : 𝕏 → W (Zuordnung des momentanen Wertes).
Ist Z : 𝕏 → W ein Zustand und wählt man eine Variable X ∊ 𝕏 und einen Wert w vom passenden Typ, so ist der transformierte Zustand durch eine Änderung der Variablen X gegeben, d.h. es findet ein Zustandsübergang Zi → Zi+1 statt!
Mit einer Anweisung α wird ein Zustand Z in einen (anderen) Zustand ⟦α⟧(Z) überführt.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Imperative Algorithmen
In applikativen (funktionalen) Algorithmen können Unbestimmte wie in einem Gleichungssystem "parallel" im Sinne von bewachten Ausdrücken verwendet werden, d.h. erst ein Term der eine Unbestimmte x verwendet, und dann folgend ein Term der die Unbestimmte berechnet, oder mehrere Terme die die Unbestimmten bestimmen. In imperativen Sprachen gibt es eine strikte Sequenz: Erst berechnen, dann verwenden.
Applikativ Imperativ=============== =========y=x-t fac n | n===1 = 1 a:=1; fac(n) {t=a+b | n > 1 = n *fac(n-1) b:=2; if (n<0) then return null;a=1 | n<1 = ⊥ t:=a+b; else if (n==1) return 1:b=2 y:=x-t; else return n*fac(n-1) }
Terme mit Unbestimmten (funktional) und Ausdrücke mit Variablen
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Imperative Algorithmen
Die Auswertung von Termen mit Variablen ist zustandsabhängig. An der Stelle der Variablen wird ihr Wert im gegebenen Zustand gesetzt. Imperative Algorithmen könne Seiteneffekte haben, d.h. dann dass ein Berechnugn mit den gleichen Eingabe- zu verschiedenen Ausgabewerten führt!
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Imperative Algorithmen
Rückwirkung Seiteneffekt ═══════════ ════════════ x=0 ┌――▶x=0 ――――――――――――┐for(i=0;i<10;i++) { │ function foo(u) { │ if (x>5) { ◀――┐ └―――――if (u>2) x=x+1; ◀―┘ i=i-1 │ return u-x } │ } x=foo(i) ――――┘ }
Beispiele für Rückwirkung und Seiteneffekte
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Imperative Algorithmen
Die komplexen Anweisungen imperativer Algorithmen bilden eine Untermenge der intuitiven Algorithmenbausteine.
⟦α1;α2⟧ = ⟦α2⟧(⟦α1⟧(Z))
if B then α1 else α2 fi
eine Anweisung.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Imperative Algorithmen
while B do α od
eine Anweisung. Die Iteration wird wie bei den intuitiven Algorithmen oft als Schleife bezeichnet.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Imperative Algorithmen
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Imperative Algorithmen
Kurz gefasst lassen sich imperative Algorithmen wie folgt charakterisieren:
Die Algorithmenausführung besteht aus einer Folge von Basisschritten, genauer von Wertzuweisungen. Diese Folge wird mittels Selektion und Iteration basierend auf booleschen Tests über dem Zustand konstruiert. Jeder Basisschritt definiert eine elementare Transformation des Zustands. Die Semantik des Algorithmus ist durch die Kombination all dieser Zustandstransformationen zu einer Gesamttransformation festgelegt.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Deduktive Algorithmen
Das sogenannte logische Paradigma führt uns zu den deduktiven Algorithmen.
Deduktive Algorithmen basieren auf logischen Aussagen und sind die Grundlage von Programmiersprachen wie PROLOG (für PROgramming in LOGic).
Logische Algorithmen bestehen aus einer Reihe von logischen Aussagen und einem Auswertungsalgorithmus für Anfragen.
Daher sind sie nicht in dem Sinne Algorithmen, wie wir sie bisher betrachtet haben – erst die Kombination der drei Bestandteile, logisches Programm, Auswertungsalgorithmus und konkrete Anfrage, legen eine Berechnungsfolge fest.
Während die meisten sonstigen Programmiersprachen applikative und imperative Elemente mischen, werden aufgrund dieser Unterschiede deduktive Elemente nur in speziellen logischen Programmiersprachen eingesetzt.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Deduktive Algorithmen
%% sudoku.pl:- use_module(library(clpfd)).sudoku(Puzzle) :- flatten(Puzzle, Tmp), Tmp ins 1..9, Rows = Puzzle, transpose(Rows, Columns), blocks(Rows, Blocks), maplist(all_distinct, Rows), maplist(all_distinct, Columns), maplist(all_distinct, Blocks), maplist(label, Rows).blocks([A,B,C,D,E,F,G,H,I], Blocks) :- blocks(A,B,C,Block1), blocks(D,E,F,Block2), blocks(G,H,I,Block3), append([Block1, Block2, Block3], Blocks).blocks([], [], [], []).blocks([A,B,C|Bs1],[D,E,F|Bs2],[G,H,I|Bs3], [Block|Blocks]) :- Block = [A,B,C,D,E,F,G,H,I], blocks(Bs1, Bs2, Bs3, Blocks).
Sudoko Löser in Prolog mit Randbedingungslöser. That's all folks.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Genetische Algorithmen
Genetische Algorithmen sind durch Prinzipien der Informationsverarbeitung in der Natur inspiriert.
Die DNS kann als Analogon zu einem Programm aufgefasst werden, indem Gene als ein in einem 4-Buchstaben-Alphabet kodiertes Programm interpretiert werden, das den Bauplan etwa eines Tieres (inklusive einiger Aspekte des Verhaltens!) festgelegt.
In der Natur gibt es keine Programmierer, die Fortpflanzung mit der Kombination aus männlichem und weiblichem Erbgut zusammen mit Mutationen verändert das Erbgut; die natürliche Auslese führt zur Ausprägung von Eigenschaften und letztendlich zu neuen Arten.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Genetische Algorithmen
Umgesetzt in die Denkweise der Informatik, arbeitet ein genetischer Algorithmus wie folgt:
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Algorithmenparadigmen :: Genetische Algorithmen
Initialisiere erste Generation G;do BesteFitness = max { Fitness(x) |x in G }; if BesteFitness = 0 then break; Bestimme G′ aus G als Eltern der neuen Generation G; // etwa durch n Turniere aus je k zufällig aus G gewählten x G = {}; for each x in G′ do with probability 0.7 do x = mutate(x) od; with probability 0.2 do wähle y aus G′ aus; x = crossover(x, y); od; G = G union { x }; od;od;return Kandidaten x aus G mit Fitness(x) = BesteFitness;
Grundprinzip genetischer Algorithmen
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Entwurfsprinzipien :: Genetische Algorithmen
Der Entwurf von Algorithmen und damit von Programmen ist eine konstruktive und kreative Tätigkeit, die den Entwerfer immer wieder vor neue Herausforderungen stellt – neben der reinen Funktionalität sind ja auch Fragen der Laufzeitkomplexität und andere, nichtfunktionale Anforderungen zu berücksichtigen.
Die automatische Ableitung eines optimalen Algorithmus aus einer Beschreibung der Anforderungen ist prinzipiell nicht automatisierbar!
ad-dpunkt 8.1
TODO: ad-dpunkt: Greedy (S. 226), DaC (S. 231), Backtracking (S. 234), Dyn. Programmierung (S. 242)
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Entwurfsprinzipien :: Schrittweise Verfeinerung
Die Vorgehensweise der schrittweisen Verfeinerung basiert auf folgendem Ablauf:
In der Terminologie der Programmierung entspricht der Verfeinerungsschritt der Formulierung von Algorithmen auf einer abstrakten Stufe, bei der statt ausführbarer Einzelschritte Prozeduraufrufe eingesetzt werden, deren Bedeutung dann durch die Ausprogrammierung der Prozedur konkretisiert wird.
Fakorisierung, d.h. Aufteilung eines Programms oder einer großen Funktion in viele kleine Funktionen ist ebenfalls schrittweise Verfeinering von Algorithmen.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Entwurfsprinzipien :: Schrittweise Verfeinerung
Aber schrittweise Verfeinerung ist Übgergang von abstrakter in konkrete Notation mit einer Programmiersparche.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Entwurfsprinzipien :: Schrittweise Verfeinerung
Auch Datenstrukturen sowie Klassen können verfeinert (also refaktorisiert) werden.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Entwurfsprinzipien :: Einsatz von Algorithmenmustern
Die Idee des Einsatzes von Algorithmenmustern besteht darin, generische algorithmische Muster für bestimmte Problemklassen zu entwickeln und diese dann jeweils an eine konkrete Aufgabe anzupassen.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Entwurfsprinzipien :: Einsatz von Algorithmenmustern
Diese Grundidee kann man auf verschiedene Weise konkretisieren:
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Entwurfsprinzipien :: Problemreduzierung durch Rekursion
Rekursive Algorithmen sind ein für die Informatik spezifischer Lösungsansatz, der in den klassischen Ingenieurwissenschaften nicht entwickelt werden konnte – ein mechanisches Bauteil kann physikalisch nicht sich selbst als Bauteil enthalten, während ein Programm sich durchaus selbst aufrufen kann.
Formale Algorithmenmodelle sind auch ohne rekursive Aufrufe bereits berechnungsuniversell.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Entwurfsprinzipien :: Problemreduzierung durch Rekursion
Allerdings ist das rekursive Anwenden einer Problemlösungsstrategie auf Teilprobleme ein Algorithmenmuster, mit dem man bestimmte Problemklassen einfach realisieren kann.
==== Linear ==== ==== Rekursiv ==== function fac(n) { function fac(n) { y=1 if (n>1) return n*fac(n-1) for(i=2;i≤n;i++) { else return 1 y=y*i } } return y }
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Entwurfsprinzipien :: Algorithmenmuster: Greedy
Greedy steht hier für gierig. Das Prinzip gieriger Algorithmen ist es, in jedem Teilschritt so viel wie möglich zu erreichen. Wir werden das Prinzip an einem kleinen Beispiel verdeutlichen und danach ein realistisches Problem und dessen »gierige« Lösung behandeln.
Problemklasse, für die sie angewendet werden können:
Greedy-Algorithmen berechnen nicht für alle derartigen Probleme tatsächlich die optimale Lösung.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Entwurfsprinzipien :: Rekursion: Divide-and-conquer
Das bereits mehrfach behandelte Prinzip der Rekursion wendet bei der Lösung eines Problems die Gesamtlösungsstrategie innerhalb der algorithmischen Lösung rekursiv auf ein geändertes Problem an.
Typischerweise erfolgt diese erneute Anwendung des Gesamtverfahrens auf ein vereinfachtes Problem, um eine Terminierung zu garantieren.
Das Prinzip »Divide-and-conquer«, deutsch »Teile und herrsche«, basiert nun darauf, in einem Schritt eine große Aufgabe in mehrere kleinere Aufgaben zu teilen und diese rekursiv zu bearbeiten – also ein klassischer Einsatz des Rekursionsprinzips.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Entwurfsprinzipien :: Rekursion: Divide-and-conquer
Vorgehensweise:
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Entwurfsprinzipien :: Rekursion: Divide-and-conquer
function DIVANDCONQ (P: problem) { if (P is klein) then return f(P) // direkte Berechnung else { {P1,P2,...,Pk} = split(P) R1=DIVANDCONQ ( P1 ) R2=DIVANDCONQ ( P2 ) .. Rk=DIVANDCONQ ( Pk ) return merge(R1,R2,...,Rk) }}
Grundprinzip DaC
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Entwurfsprinzipien :: Rekursion: Backtracking
Das Backtracking ist ein weiteres wichtiges Algorithmenmuster für Such- und Optimierungsprobleme. Das Backtracking realisiert eine allgemeine systematische Suchtechnik, die einen vorgegebenen Lösungsraum komplett bearbeitet.
Backtracking basiert auf dem vollständigen Durchlauf eines Lösungsraums, so dass die gesuchte Lösung tatsächlich erreicht wird.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Entwurfsprinzipien :: Rekursion: Backtracking
Formal bedeutet es, dass für unsere Problemstellung die folgenden Voraussetzungen gelten müssen:
Backtracking ist ein rekursives Verfahren, das mit dem Aufruf BACKTRACK(K0), also mit der Anfangskonfiguration, gestartet wird.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Entwurfsprinzipien :: Rekursion: Backtracking
function FindeLösung (Stufe, Vektor) { 1. wiederhole, solange es noch neue Teil-Lösungsschritte gibt: a) wähle einen neuen Teil-Lösungsschritt; b) falls Wahl gültig ist: I) erweitere Lösungsvektor um Wahl; II) falls Lösungsvektor vollständig ist, return true; // Lösung gefunden! sonst: falls (FindeLösung(Stufe+1, Vektor)) return true; // Lösung! sonst mache Wahl rückgängig; // Sackgasse (Backtracking)! 2. Da es keinen neuen Teil-Lösungsschritt gibt: return false // Keine Lösung!}
Backtracking-Muster
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Entwurfsprinzipien :: Rekursion: Backtracking
Die Tiefensuche und somit auch Backtracking haben im schlechtesten Fall mit O(zN) und einem Verzweigungsgrad z > 1 eine exponentielle Laufzeit.
Je größer die Suchtiefe N, desto länger dauert die Suche nach einer Lösung. Daher ist das Backtracking primär für Probleme mit einem kleinen Lösungsbaum geeignet.
Es gibt jedoch Methoden, mit welchen die Zeitkomplexität eines Backtracking-Algorithmus verringert werden kann. Diese sind unter anderem:
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Entwurfsprinzipien :: Dynamische Programmierung
Dynamische Programmierung vereint Aspekte der drei bisher vorgestellten Algorithmenmuster.
Während Divide-and-conquer-Verfahren unabhängige Teilprobleme durch rekursive Aufrufe lösen, werden bei der dynamischen Programmierung abhängige Teilprobleme optimiert gelöst, indem mehrfach auftretende Teilprobleme nur einmal gelöst werden.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Entwurfsprinzipien :: Dynamische Programmierung
Der Anwendungsbereich der dynamischen Programmierung sind Optimierungsprobleme analog zu Greedy-Algorithmen – mit dynamischer Programmierung werden aber insbesondere Probleme bearbeitet, bei denen die Greedy-Vorgehensweise nicht zu optimalen Lösungen führt.
Stefan Bosse - Algorithmen und Datenstrukturen - Modul E Entwurfsprinzipien :: Dynamische Programmierung