Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • p.anaguano/informatik2022
  • a.engelke/informatik2022
  • p.schleinzer/informatik2022
  • n.rosenkranz/informatik2022
  • w.weber/informatik2022
  • xuhao.zhang/informatik2022
  • h.el-menuawy/informatik2022
  • saifalla.ibrahim/informatik2022
  • d.krause/informatik2022
  • p.schilling/informatik2022
  • j.tolke/informatik2022
  • f.luckau/informatik2022
  • danish.ahmad/informatik2022
  • emir.sagdani/informatik2022
  • d.griedel/informatik2022
  • j.mahnke/informatik2022
  • l.poehler/informatik2022
  • christoph.wrede/informatik2022
  • y.kummert/informatik2022
  • alexander.reisch/informatik2022
  • t.dickel/informatik2022
  • ni.petersen/informatik2022
  • markus.werner/informatik2022
  • s.mouammar/informatik2022
  • j.jahns/informatik2022
  • m.figueroa-castillo/informatik2022
  • b.hannan/informatik2022
  • v.lapschiess/informatik2022
  • j.hegner/informatik2022
  • g.paraschiv/informatik2022
  • e.abkaimov/informatik2022
  • l.krogmann/informatik2022
  • d.mizyed/informatik2022
  • h.almasri/informatik2022
  • a.mickan/informatik2022
  • f.shikh-alshabab/informatik2022
  • j.feldbausch/informatik2022
  • l.abdel-kader/informatik2022
  • jan.seibt/informatik2022
  • e.goekmen/informatik2022
  • nathanael.schenk/informatik2022
  • r.reksius/informatik2022
  • edmont.schulze/informatik2022
  • a.singh/informatik2022
  • p.christensen/informatik2022
  • m.woidt/informatik2022
46 results
Show changes
Showing
with 2661 additions and 0 deletions
%% Cell type:markdown id:ca8d90cf tags:
# <font color='blue'>**Übung 2 - Anwendung von Datenstrukturen (Fortsetzung)**</font>
## <font color='blue'>**Problemstellung: Auftragsverwaltung einer Fertigungsmaschine**</font>
### <font color='blue'>**Problembeschreibung**</font>
Es soll eine Datenstruktur zur Verwaltung von Aufträgen für CNC-Fertigungsmaschinen implementiert werden. Hierbei soll jedoch nicht ein First-in-first-out (FIFO) Prinzip verwendet werden, sondern es soll möglich sein, Aufträge zu priorisieren. Die Aufträge sollen folgende Elemente enthalten:
* Name
* Stückzahl
* Materialart
* Materialmenge pro Stück
* Dauer pro Stück<br>
* Priorität<br>
Die ````__init()__````- Methode soll prüfen, dass bei den Zahlenwerten nur positive Werte eingegeben werden.
Die Maschine soll Aufträge annehmen und bearbeiten können. Bei der Bearbeitung eines soll zur Kontrolle eine Textausgabe ausgeben, welcher Auftrag ausgeführt wurde, und wie viel Zeit und Material dafür benötigt wurde. Es soll eine weitere Methode geben, mit der alle verbleibenden Aufträge abgearbeitet werden.
### <font color='blue'>**Modellbildung**</font>
Das Modell besteht aus Aufträgen mit den oben genannten Informationen und der Maschine, die die Aufträge speichert, sortiert, und bei Bearbeitung eine entsprechende Statusmeldung ausgibt. Dazu werden die Material- und Zeitbedarfe mit der Stückzahl multipliziert. Die Speicherung und Sortierung wird mithilfe einer geeigneten Datenstruktur gelöst, was im Bereich "Algorithmierung" näher besprochen wird.
### <font color='blue'>**Algorithmierung**</font>
Für die Anwendung einer Vorrangwarteschlange eignet sich die Datenstruktur (Minimum-) Heap hervorragend, da wir neue Elemente hinzufügen und immer das Element mit dem niedrigsten Schlüssel entnehmen können. Wir verwenden eine Standardimplementierung mit den Methoden `insert()` und `extractMin()` zur Nutzung, sowie den internen Methoden `__init__()`,`remove()`, `exchange()` und `heapify` aus dem Grundlagennotebook. Die Algorithmen für das Hinzufügen, Entnehmen und Sortieren sind im Grundlagenteil bereits besprochen. Wir fügen der Implementierung die Methode `__len__()`hinzu, um die Anzahl der gespeicherten Elemente erhalten zu können. In der verwendeten Implementierung wird der Prioritätsschlüssel ausschließlich im Zusammenhang mit den Vergleichsoperationen `<=` und `<` ausgewertet. Es muss also dafür gesorgt werden, dass die von uns in diesem Heap gespeicherten Aufträge (Elemente) eine sinnvolle und auf die Priorität bezogene Implementierung von `<=` und `<` haben. Dann kann diese Implementierung des Heaps direkt verwendet werden.
Für die Aufträge erstellen wir zwei Klassen, um die Funktionalität für die Priorätatswarteschlange vom eigentlichen Auftrag zu trennen. Die Klasse `Auftrag` erhält somit nur einen Konstruktor `__init__()` und die Attribute, die den Auftrag beschreiben.
Die abgeleitete Klasse `PrioAuftrag` erweitert `Auftrag` um den Schlüssel `prio`, sowie die Implementierung der `<=` und `<` Operatoren mithilfe der Magic-Methoden `__le__()` und `__lt__()`. Diese Methoden erhalten das Objekt hinter dem Operator als Argument und geben als Ergebnis den Vergleich der Attribute `prio` der beiden Objekte zurück. So kann es direkt vom verwendeten Heap verarbeitet werden.
Die Klasse `Maschine` verwaltet den Heap und stellt die geforderten Methoden zur Verfügung. Die Methoden rufen die passenden Methoden des Heaps auf.
Die Struktur ist im folgenden UML-Diagramm gezeigt:
%% Cell type:markdown id:5e2c7b57 tags:
![MinHeap_Maschine.svg](Pics/MinHeap_Maschine.svg)
%% Cell type:markdown id:ea55317c tags:
### <font color='blue'>**Umsetzung**</font>
Auftrag:
%% Cell type:code id:f847f8e0 tags:
``` python
class Auftrag:
def __init__(self, name, material, materialverbrauch_pro_stueck, stueckzahl, stueckdauer):
"?"
```
%% Cell type:markdown id:e0cf1a69 tags:
PrioAuftrag ableiten:
%% Cell type:code id:d425e591 tags:
``` python
class PrioAuftrag(Auftrag):
def __init__(self, name, material, materialverbrauch_pro_stueck, stueckzahl, stueckdauer, prio):
super().__init__(name, material, materialverbrauch_pro_stueck, stueckzahl, stueckdauer) # Aufruf Basisklassenkonstruktor
"?"
def __le__(self, other):
"?"
def __lt__(self,other):
"?"
```
%% Cell type:markdown id:4b88c81b tags:
Heap (übernommen aus Grundlagen-Notebook, `__len__()` hinzugefügt):
%% Cell type:code id:3c319063 tags:
``` python
class Heap:
def __init__(self):
self.A = [] # Array/Liste
def __len__(self):
"?"
def insert(self, newNode):
self.A.append(newNode)
self.heapify(len(self.A)-1)
def extract_min(self):
x = self.A[0]
self.remove(0)
return x
def remove(self, kE):
self.exchange(kE,len(self.A)-1)
del self.A[-1]
self.heapify(kE)
def exchange(self, k1, k2):
x = self.A[k1]
self.A[k1] = self.A[k2]
self.A[k2] = x
def heapify(self, kE):
# Austausch aufwärts
while kE > 0:
kV = (kE-1)//2
if self.A[kE] <= self.A[kV]:
self.exchange(kE,kV)
kE = kV
else:
break
# Austausch abwärts,
# falls kE ist innerer Knoten
while kE < len(self.A)//2:
kN = 2*kE+1
if kN+1 < len(self.A):
if self.A[kN+1] < self.A[kN]:
kN += 1
if self.A[kE] <= self.A[kN]:
return
self.exchange(kE, kN)
kE = kN
```
%% Cell type:markdown id:9a0aa0a2 tags:
Maschine:
%% Cell type:code id:d745f1fd tags:
``` python
class Maschine:
def __init__(self):
"?"
def neuer_auftrag(self, auftrag):
"?"
def bearbeite_auftrag(self):
info = "?"
print("?")
def bearbeite_alle_auftraege(self):
for "?":
"?"
```
%% Cell type:markdown id:b9174f58 tags:
### <font color='blue'>**Anwendung**</font>
Zum Testen werden eine Maschine und verschiedene Aufträge angelegt. Über Variation der Prioritätswerte kann getestet werden, ob die Implementierung korrekt funktioniert. Ist das der Fall, wird unabhängig von der Reihenfolge des Erstellens der Aufträge immer als nächstes der Auftrag mit der höchsten Priorität (bzw. dem niedrigsten Prioritätsschlüssel) abgearbeitet.
%% Cell type:code id:bf9970e6 tags:
``` python
fraese = "?"
fraese.neuer_auftrag("?"("Grundplatte", "Stahl", 0.4, 20, 8, 5))
fraese.neuer_auftrag("?"("Formwerkzeug", "Necuron 690", 0.3, 4, 120, 1))
fraese.bearbeite_auftrag()
fraese.neuer_auftrag("?"("Stütze", "Aluminium", 0.3, 40, 6, 3))
fraese.bearbeite_alle_auftraege()
```
%% Cell type:markdown id:f71e5ad3 tags:
### <font color='blue'>**Anregungen zum selbst Programmieren**</font>
Nimm zwei Erweiterungen an dem Programm vor:
* Damit Aufträge mit anfangs niedriger Priorität auch irgendwann abgearbeitet werden und nicht immer von neueren Aufträgen mit höherer Priorität blockiert werden, soll die Maschine immer nach der Abarbeitung von z.B. 2 Aufträgen die Priorität der verbleibenden Aufträge erhöhen. Damit dies funktioniert, benötigt die Maschine ein weiteres Attribut, das die abgearbeiteten Aufträge seit der letzten Prioritätserhöhung zählt. Sie benötigt eine Methode (z.B. "Prio_erhoehen"), die immer beim Bearbeiten von Aufträgen aufgerufen wird, sobald genug Aufträge abgearbeitet werden. In dieser Methode wird ein neuer Heap angelegt. Die Aufträge des auftraege-Heaps werden ausgelesen, der Prioritätsschlüssel verringert und anschließend der Auftrag dem neuen Heap hinzugefügt. Sind alle Aufträge aus dem auftraege-Heap in den neuen Heap eingefuegt, wird der neue Heap als auftraege-Heap abgespeichert. So können die Aufträge verändert werden, ohne manipulativ in die Datenstruktur des Heaps einzugreifen (und damit potentiell die korrekte Funktion des Heaps zu zerstören, die darauf basiert, dass außer für ein gerade angehängtes Element die Heap-Bedingung immer erfüllt ist).
* Die Aufträge sollen zusätzlich ein Attribut für "Gewinn pro Stück" erhalten. Der Prioritätswert soll nun automatisch so gewählt werden, dass Aufträge, die in kurzer Zeit viel Gewinn bringen, priorisiert werden.
%% Cell type:markdown id:ca8d90cf tags:
# <font color='blue'>**Übung 2 - Anwendung von Datenstrukturen (Fortsetzung)**</font>
## <font color='blue'>**Problemstellung: Auftragsverwaltung einer Fertigungsmaschine**</font>
### <font color='blue'>**Problembeschreibung**</font>
Es soll eine Datenstruktur zur Verwaltung von Aufträgen für CNC-Fertigungsmaschinen implementiert werden. Hierbei soll jedoch nicht ein First-in-first-out (FIFO) Prinzip verwendet werden, sondern es soll möglich sein, Aufträge zu priorisieren. Die Aufträge sollen folgende Elemente enthalten:
* Name
* Stückzahl
* Materialart
* Materialmenge pro Stück
* Dauer pro Stück<br>
* Priorität<br>
Die ````__init()__````- Methode soll prüfen, dass bei den Zahlenwerten nur positive Werte eingegeben werden.
Die Maschine soll Aufträge annehmen und bearbeiten können. Bei der Bearbeitung eines soll zur Kontrolle eine Textausgabe ausgeben, welcher Auftrag ausgeführt wurde, und wie viel Zeit und Material dafür benötigt wurde. Es soll eine weitere Methode geben, mit der alle verbleibenden Aufträge abgearbeitet werden.
### <font color='blue'>**Modellbildung**</font>
Das Modell besteht aus Aufträgen mit den oben genannten Informationen und der Maschine, die die Aufträge speichert, sortiert, und bei Bearbeitung eine entsprechende Statusmeldung ausgibt. Dazu werden die Material- und Zeitbedarfe mit der Stückzahl multipliziert. Die Speicherung und Sortierung wird mithilfe einer geeigneten Datenstruktur gelöst, was im Bereich "Algorithmierung" näher besprochen wird.
### <font color='blue'>**Algorithmierung**</font>
Für die Anwendung einer Vorrangwarteschlange eignet sich die Datenstruktur (Minimum-) Heap hervorragend, da wir neue Elemente hinzufügen und immer das Element mit dem niedrigsten Schlüssel entnehmen können. Wir verwenden eine Standardimplementierung mit den Methoden `insert()` und `extractMin()` zur Nutzung, sowie den internen Methoden `__init__()`,`remove()`, `exchange()` und `heapify` aus dem Grundlagennotebook. Die Algorithmen für das Hinzufügen, Entnehmen und Sortieren sind im Grundlagenteil bereits besprochen. Wir fügen der Implementierung die Methode `__len__()`hinzu, um die Anzahl der gespeicherten Elemente erhalten zu können. In der verwendeten Implementierung wird der Prioritätsschlüssel ausschließlich im Zusammenhang mit den Vergleichsoperationen `<=` und `<` ausgewertet. Es muss also dafür gesorgt werden, dass die von uns in diesem Heap gespeicherten Aufträge (Elemente) eine sinnvolle und auf die Priorität bezogene Implementierung von `<=` und `<` haben. Dann kann diese Implementierung des Heaps direkt verwendet werden.
Für die Aufträge erstellen wir zwei Klassen, um die Funktionalität für die Priorätatswarteschlange vom eigentlichen Auftrag zu trennen. Die Klasse `Auftrag` erhält somit nur einen Konstruktor `__init__()` und die Attribute, die den Auftrag beschreiben.
Die abgeleitete Klasse `PrioAuftrag` erweitert `Auftrag` um den Schlüssel `prio`, sowie die Implementierung der `<=` und `<` Operatoren mithilfe der Magic-Methoden `__le__()` und `__lt__()`. Diese Methoden erhalten das Objekt hinter dem Operator als Argument und geben als Ergebnis den Vergleich der Attribute `prio` der beiden Objekte zurück. So kann es direkt vom verwendeten Heap verarbeitet werden.
Die Klasse `Maschine` verwaltet den Heap und stellt die geforderten Methoden zur Verfügung. Die Methoden rufen die passenden Methoden des Heaps auf.
Die Struktur ist im folgenden UML-Diagramm gezeigt:
%% Cell type:markdown id:728ee1f2 tags:
![MinHeap_Maschine.svg](Pics/MinHeap_Maschine.svg)
%% Cell type:markdown id:ea55317c tags:
### <font color='blue'>**Umsetzung**</font>
Auftrag:
%% Cell type:code id:f847f8e0 tags:
``` python
class Auftrag:
def __init__(self, name, material, materialverbrauch_pro_stueck, stueckzahl, stueckdauer):
self.name = name
self.material = material
if materialverbrauch_pro_stueck > 0 and stueckzahl > 0 and stueckdauer > 0:
self.materialverbrauch_pro_stueck = materialverbrauch_pro_stueck
self.stueckzahl = stueckzahl
self.stueckdauer = stueckdauer
else:
self.materialverbrauch_pro_stueck = 0
self.stueckzahl = 0
self.stueckdauer = 0
```
%% Cell type:markdown id:e0cf1a69 tags:
PrioAuftrag ableiten:
%% Cell type:code id:d425e591 tags:
``` python
class PrioAuftrag(Auftrag):
def __init__(self, name, material, materialverbrauch_pro_stueck, stueckzahl, stueckdauer, prio):
super().__init__(name, material, materialverbrauch_pro_stueck, stueckzahl, stueckdauer)
self.prio = prio
def __le__(self, other):
return self.prio <= other.prio
def __lt__(self, other):
return self.prio < other.prio
```
%% Cell type:markdown id:4b88c81b tags:
Heap (übernommen aus Grundlagen-Notebook, `__len__()` hinzugefügt):
%% Cell type:code id:3c319063 tags:
``` python
class Heap:
def __init__(self):
self.A = [] # Array/Liste
def __len__(self):
return len(self.A)
def insert(self, newNode):
self.A.append(newNode)
self.heapify(len(self.A)-1)
def extractMin(self):
x = self.A[0]
self.remove(0)
return x
def remove(self, kE):
self.exchange(kE,len(self.A)-1)
del self.A[-1]
self.heapify(kE)
def exchange(self, k1, k2):
x = self.A[k1]
self.A[k1] = self.A[k2]
self.A[k2] = x
def heapify(self, kE):
# Austausch aufwärts
while kE > 0:
kV = (kE-1)//2
if self.A[kE] <= self.A[kV]:
self.exchange(kE,kV)
kE = kV
else:
break
# Austausch abwärts,
# falls kE ist innerer Knoten
while kE < len(self.A)//2:
kN = 2*kE+1
if kN+1 < len(self.A):
if self.A[kN+1] < self.A[kN]:
kN += 1
if self.A[kE] <= self.A[kN]:
return
self.exchange(kE, kN)
kE = kN
```
%% Cell type:markdown id:9a0aa0a2 tags:
Maschine:
%% Cell type:code id:d745f1fd tags:
``` python
class Maschine:
def __init__(self):
self.auftraege = Heap()
def neuer_auftrag(self, auftrag):
self.auftraege.insert(auftrag)
def bearbeite_auftrag(self):
info = self.auftraege.extractMin()
print(f"{info.stueckzahl} Stück {info.name} wurden in {info.stueckdauer * info.stueckzahl} s hergestellt" +
f" und dabei {info.materialverbrauch_pro_stueck * info.stueckzahl} kg {info.material} verwendet.")
def bearbeite_alle_auftraege(self):
for i in range(len(self.auftraege)):
self.bearbeite_auftrag()
```
%% Cell type:markdown id:b9174f58 tags:
### <font color='blue'>**Anwendung**</font>
Zum Testen werden eine Maschine und verschiedene Aufträge angelegt. Über Variation der Prioritätswerte kann getestet werden, ob die Implementierung korrekt funktioniert. Ist das der Fall, wird unabhängig von der Reihenfolge des Erstellens der Aufträge immer als nächstes der Auftrag mit der höchsten Priorität (bzw. dem niedrigsten Prioritätsschlüssel) abgearbeitet.
%% Cell type:code id:bf9970e6 tags:
``` python
fraese = Maschine()
fraese.neuer_auftrag(PrioAuftrag("Grundplatte", "Stahl", 0.4, 20, 8, 5))
fraese.neuer_auftrag(PrioAuftrag("Formwerkzeug", "Necuron 690", 0.3, 4, 120, 1))
fraese.bearbeite_auftrag()
fraese.neuer_auftrag(PrioAuftrag("Stütze", "Aluminium", 0.3, 40, 6, 3))
fraese.bearbeite_alle_auftraege()
```
%% Output
4 Stück Formwerkzeug wurden in 480 s hergestellt und dabei 1.2 kg Necuron 690 verwendet.
40 Stück Stütze wurden in 240 s hergestellt und dabei 12.0 kg Aluminium verwendet.
20 Stück Grundplatte wurden in 160 s hergestellt und dabei 8.0 kg Stahl verwendet.
%% Cell type:markdown id:f71e5ad3 tags:
### <font color='blue'>**Anregungen zum selbst Programmieren**</font>
Nimm zwei Erweiterungen an dem Programm vor:
* Damit Aufträge mit anfangs niedriger Priorität auch irgendwann abgearbeitet werden und nicht immer von neueren Aufträgen mit höherer Priorität blockiert werden, soll die Maschine immer nach der Abarbeitung von z.B. 2 Aufträgen die Priorität der verbleibenden Aufträge erhöhen. Damit dies funktioniert, benötigt die Maschine ein weiteres Attribut, das die abgearbeiteten Aufträge seit der letzten Prioritätserhöhung zählt. Sie benötigt eine Methode (z.B. "Prio_erhoehen"), die immer beim Bearbeiten von Aufträgen aufgerufen wird, sobald genug Aufträge abgearbeitet werden. In dieser Methode wird ein neuer Heap angelegt. Die Aufträge des auftraege-Heaps werden ausgelesen, der Prioritätsschlüssel verringert und anschließend der Auftrag dem neuen Heap hinzugefügt. Sind alle Aufträge aus dem auftraege-Heap in den neuen Heap eingefuegt, wird der neue Heap als auftraege-Heap abgespeichert. So können die Aufträge verändert werden, ohne manipulativ in die Datenstruktur des Heaps einzugreifen (und damit potentiell die korrekte Funktion des Heaps zu zerstören, die darauf basiert, dass außer für ein gerade angehängtes Element die Heap-Bedingung immer erfüllt ist).
* Die Aufträge sollen zusätzlich ein Attribut für "Gewinn pro Stück" erhalten. Der Prioritätswert soll nun automatisch so gewählt werden, dass Aufträge, die in kurzer Zeit viel Gewinn bringen, priorisiert werden.
%% Cell type:markdown id:e7ac2738-e0bb-4c7f-a03f-10fa7ec5b71e tags:
# <font color='blue'>**Algorithmen**</font>
## **<font color='blue'>Komplexität</font>**
Ein wichtiges Maß, um Algorithmen zu bewerten und zu vergleichen, ist die **Komplexität**.
**Komplexität** beschreibt den maximalen Ressourcenbedarf in Abhängigkeit der Problemgröße
dargestellt durch die Zahl $n$.
In der Regel sind dies die Speicherkomplexität $S(n)$, d.h. der maximal benötigte Speicherplatz
bei einem Problem der Größe $n$, oder die Zeitkomplexität $T(n)$, d.h. die Zahl an Operationen bei einem Problem
der Größe $n$, die je nach Taktrate proportional zu CPU-Zeit ist.
Z.B. ist die Zeitkomplexität bei einem Skalarprodukt $s$ zweier Vektoren $x$ und $y$
mit $n$ Komponenten bezüglich der Multiplikations- und Additionsoperationen:
\begin{equation}
s \;=\; x_1 y_1 + \; ... \;+ x_n y_n
\quad
\rightarrow
\quad
T_{mult}(n) = n \; , \quad
T_{add}(n) = n-1\; , \quad
T_{arith}(n) = 2n-1\; ,
\end{equation}
was durch einfaches Abzählen der Operationen nachzuvollziehen ist.
Eine vollständige Komplexitätsbeschreibung erfolgt über die drei Fälle:
* **best case** (der günstigste Fall) <br>
Der günstigste Fall ereignet sich bei bestimmten Eingabegrößen,
für die der Algorithmus minimalen Speicherplatz bzw. minimale Laufzeit benötigt.
* **average case** (der Normalfall bzw. Durchschnittsfall) <br>
Die average-case-Komplexität ist das gemäß einer Wahrscheinlichkeitsverteilung
für alle Eingaben gewichtete Mittel der Laufzeit bzw. des Speicherplatzbedarfs.
* **worst case** (der schlechteste Fall) <br>
Der schlechteste Fall ereignet sich bei bestimmten Eingabegrößen,
für die der Algorithmus maximalen Speicherplatz bzw. maximale Laufzeit benötigt.
### **<font color='blue'>Landau-Notation</font>**
Entscheidende Aussage der Komplexität ist, wie sich der zu leistende Aufwand für große Probleme darstellt.
Daher wird zur Bestimmung eine Abschätzung für **große** $n$ vorgenommen.
Als Nomenklatur wird das Landau-Symbol $O(f(n))$ herangezogen, dessen mathematische Definition
\begin{equation}
O(f(n)) \;=\; \{\; g: \mathbb{N} \rightarrow \mathbb{N}
\mid \; \exists n_0 > 0 \wedge \exists \; c > 0
\text{, so dass} \; \forall \; n > n_0 : \; g(n) \leq c f(n) \;\},
\end{equation}
ist: $O(f(n))$ ist die Menge aller der Funktionen $g(n)$, für die es zwei Konstanten $n_0$ und $c$ gibt, so dass für alle $n > n_0$ gilt: $g(n) \leq c \cdot f(n)$.
Wäre nun $g(n)$ die exakte Komplexität, besagt die Definition, dass die $O$-Notation eine **obere Schranke** angibt, welche selbst im ungünstigsten Fall nicht überschritten wird.
$g(n)=O(f(n))$ bedeutet also, dass $g(n)$ ab einer gewissen Anzahl von Eingabewerten ($n>n_0$) stets kleiner als $c f(n)$ ist. <br>
Kurz gesagt bedeutet $g(n)=O(f(n))$, dass für große $n$ die Funktion $f(n)$ eine obere Schranke darstellt. <br>
$O(f(n))$ kann man also auch aus einer Grenzwertbetrachtung für große Zahlen $n$ herleiten oder z.B. über vollständige Induktion.
### **<font color='blue'>Komplexitätsklassen</font>**
Übliche Schranken $f(n)$ für die Komplexität sind Potenzen von $n^k$,
($n, n^2, n^3, ... $), $\log(n)$ oder $a^n$, wobei $a$ eine Konstante $a>1$
oder sogar selbst $n$ sein kann. Die folgende Tabelle
md"
$$
\begin{aligned}
& \text {Tabelle: Zeitkomplexitäten}\\
&\begin{array}{rcccccc}
n & \log(n) & n & n \log(n) & n^2 & \; 2^n & \; n^n \\
\hline
10 & 1 & 10^1 & \; 10 & 10^2\; & 10^3\; & 10^{10}\; \\
100 & 2 & 10^2 & 2\; 10^2 & 10^4\; & 10^{30} & 10^{200} \\
1.000 & 3 & 10^3 & 3\; 10^3 & 10^6\; & (\infty) & (\infty)\\
10.000 & 4 & 10^4 & 4\; 10^4 & 10^8\; & (\infty) & (\infty) \\
100.000 & 5 & 10^5 & 5\; 10^5 & 10^{10} & (\infty) & (\infty) \\
\hline
\end{array}
\end{aligned}
$$
"
zeigt, das die Komplexität $\log(n)$ günstiger ist als die mit $n^k$ ist und die Formen $a^n$ bereits für kleine Werte $n$ sehr große Werte annehmen.
Diese großen Werte sind \textit{fast unendlich} $(\infty)$, wenn man sich vor Augen führt, dass eine Stunde $3.6\; 10^9 \mu s$, ein Tag $8.64\; 10^{10} \mu s$ und ein Jahr $3.15\; 10^{13} \mu s$ sind und man bei derzeitiger Hardware grob von 1 Operation in einer $\mu s$ (Mikro-Sekunde) ausgehen kann.
<img src="Pics/Komplexitaetsklassen.png" width="45%" height="45%">
* Die **logarithmische Komplexität** $O(log(n))$ ist außerordentlich günstig,
ebenso wie lineare $O(n)$ und überlineare $O(n\;log(n))$.
* Algorithmen mit einer Komplexität der Form $O(n^k)$ mit $ k>1$
werden als polynomiale Algorithmen bezeichnet.
Sie gelten als **ausführbar** und **effizient**.
* Demgegenüber stehen die Algorithmen mit einer
exponentiellen Komplexität der Form $O(a^n)$.
Sie gelten als **nicht ausführbar**.
* **Nicht-deterministisch polynomiale** Probleme lassen
sich unter Umständen mit polynomialer Komplexität lösen
und haben die Eigenschaft, dass für Fragestellungen,
zu denen kein durchführbarer Algorithmus bekannt ist,
zumindest eine gefundenen Lösung in polynomialer Laufzeit
verifiziert werden kann.
%% Cell type:markdown id:d024bdde-545c-4c2a-916f-ab80e3717c1e tags:
## **<font color='blue'>Sortieren</font>**
Sinn: Zugriff auf sortierte Daten wesentlich effizienter
Ein grundlegendes Beispiel für Algorithmen ist das Sortieren von Elementen
\begin{equation}
a[0], a[1], ... , a[n-1]
\end{equation}
entsprechend einer **Ordnungsfunktion** $f()$, die auf $a[i]$ angewendet wird.
Gesucht ist die **Permutation** $i_0, i_1, ... , i_{n-1}$, so dass
\begin{equation}
f(a[i_0]) \leq f(a[i_1]) \leq ... \leq f(a[i_{n-1}])
\end{equation}
gilt, in anderen Worten: die Reihenfolge, bei der die Ordnungsrelation für aufeinanderfolgende Elemente zutrifft.
Bei den folgenden Verfahren wird davon ausgegangen, dass die zu sortierenden Elemente - einfache Zahlen - in einem Array $a$ vorliegen, das einen direkten, wahlfreien Zugriff ermöglicht.
Unterschiede der Sortieralgorithmen ergeben sich z.B. aus der **Struktur der sortierenden Daten**, des **sequentiellen oder wahlfreien Zugriffs** oder auch bei den **Komplexitäten**.
Grob lassen sich die Verfahren nach **direkten Verfahren**, bei denen $T_{Cm} = O(n^2)$ und/oder $T_{Mv} = O(n^2)$ sind, und **höheren Verfahren**, die oft günstiger bei der Komplexität aber in der Implementierung aufwändiger sind, klassifizieren.
### **<font color='blue'>Direkte Sortierverfahren</font>**
Direkte Verfahren zeichnen sich dabei dadurch aus, dass sie in der Regel das gesamte Array behandeln, d.h. ein Problem der Größe $n$.
#### **<font color='blue'>Direktes Einfügen</font>**
Das direkte Einfügen entnimmt im $i$-ten Schritt aus der nicht sortierten Teilfolge $a[i], a[i+1], ... , a[n-1]$ das erste Element $a[i]$ und sortiert dies in die bereits sortiert vorliegende Sequenz $a[0], a[1], ... , a[i-1]$ ein. Hierzu wird zunächst die Einfügeposition $j+1$ linear von hinten beginnend gesucht, und die Einfügelücke geöffnet, indem die Elemente $a[j+1], a[j+2], ... , a[i-1]$ um eine Stelle nach hinten geschoben werden. Das ursprüngliche Element $a[i]$ wird dann auf die Position $a[j+1]$ gesetzt.
Im Folgenden werden die Belegungen des Arrays $a$ nach den einzelnen Einfügeschritten
dargestellt. Die bereits sortierten Folgen sind unterstrichen, das einzusortierende
Element ist fett gesetzt.
\begin{equation}
\begin{matrix}
[\; \underline{13},\; \mathbf{57},\; 18,\; 8,\; 6,\; 61,\; 97,\; 12 \;] \\
[\; \underline{13,\; 57},\; \mathbf{18},\; 8,\; 6,\; 61,\; 97,\; 12 \;] \\
[\; \underline{13,\; 18,\; 57},\; \mathbf{8},\; 6,\; 61,\; 97,\; 12 \;] \\
[\; \underline{8,\; 13,\; 18,\; 57},\; \mathbf{6},\; 61,\; 97,\; 12 \;] \\
[\; \underline{6,\; 8,\; 13,\; 18,\; 57},\; \mathbf{61},\; 97,\; 12 \;] \\
[\; \underline{6,\; 8,\; 13,\; 18,\; 57,\; 61},\; \mathbf{97},\; 12 \;] \\
[\; \underline{6,\; 8,\; 13,\; 18,\; 57,\; 61,\; 97},\; \mathbf{12} \;] \\
[\; \underline{6,\; 8,\; 12,\; 13,\; 18,\; 57,\; 61,\; 97} \;]
\end{matrix}
\end{equation}
%% Cell type:code id:042b203d-e2e1-484f-92de-a77f8b9ac8e2 tags:
``` python
def sort_insertion( a ):
for i in range( 1, len( a ) ):
print( i, a[:i], a[i:i+1], a[i+1:] )
a_i = a[i] # Aktuelles Element
j = i-1
while ( j >= 0 and a_i < a[j] ): # Vergleich und
a[j+1] = a[j] # Verschieben nach rechts bzw. öffnen der Lücke
j -= 1
a[j+1] = a_i # Aktuelles Element einfügen
print( '*', a )
sort_insertion( [ 13, 57, 18, 8, 6, 61, 97, 12 ] )
```
%% Output
1 [13] [57] [18, 8, 6, 61, 97, 12]
2 [13, 57] [18] [8, 6, 61, 97, 12]
3 [13, 18, 57] [8] [6, 61, 97, 12]
4 [8, 13, 18, 57] [6] [61, 97, 12]
5 [6, 8, 13, 18, 57] [61] [97, 12]
6 [6, 8, 13, 18, 57, 61] [97] [12]
7 [6, 8, 13, 18, 57, 61, 97] [12] []
* [6, 8, 12, 13, 18, 57, 61, 97]
%% Cell type:markdown id:46d46a7a-da33-47e2-8e82-346ad9ac87f7 tags:
#### **<font color='blue'>Binäres Einfügen</font>**
Das Verfahren läßt sich beschleunigen, indem
die bereits erfolgte Sortierung der Teilsequenz
$a[0], a[1], ... , a[i-1]$ ausgenutzt wird.
Im Folgenden ist die binäre Suche der Einfügeposition
der Zahl 12 unter Angabe der Indizes \texttt{j},
\texttt{ug} und \texttt{og} illustriert:
\begin{equation}
% use packages: array
\begin{array}[c]{lcl}
\texttt{j} & \texttt{ug:og} & \\
& \texttt{0:6} & [\;\underline{6,\; 8,\; 13,\; 18,\; 57,\; 61,\; 97}, 12 \;]\\
\texttt{3} & \texttt{0:2} & [\;\underline{6,\; 8,\; 13},\; \mathbf{18},\; 57,\; 61,\; 97, 12 \;] \\
\texttt{1} & \texttt{2:2} & [\; 6,\; \mathbf{8},\; \underline{13},\; 18,\; 57,\; 61,\; 97, 12 \;] \\
\texttt{2} & \texttt{2:1} & [\; 6,\; 8,\; \underline{\mathbf{13}},\; 18,\; 57,\; 61,\; 97, 12 \;]
\end{array}
\end{equation}
Die folgende Implementierung ersetzt die Rekursion der binären Suche durch
eine Iteration.
%% Cell type:code id:60d9adf9-fac5-4631-8974-11783e334469 tags:
``` python
def sort_binary_insertion( a ):
for i in range( 1, len( a ) ):
print( i, a[:i], a[i:i+1], a[i+1:] )
a_i = a[i] # Aktuelles Element
# binäre Suche der Einfügestelle über eine Iteration
ug = 0; og = i-1
while( ug <= og ):
j = ( ug + og ) // 2
if ( a_i < a[j] ): og = j-1
else: ug = j+1
# Verschieben nach rechts bzw. öffnen der Lücke
a[ug+1:i+1] = a[ug:i]
#for j in range( i, ug, -1 ):
# a[j] = a[j-1];
a[ug] = a_i # Aktuelles Element einfügen
print( '*', a )
sort_binary_insertion( [ 13, 57, 18, 8, 6, 61, 97, 12 ] )
```
%% Output
1 [13] [57] [18, 8, 6, 61, 97, 12]
2 [13, 57] [18] [8, 6, 61, 97, 12]
3 [13, 18, 57] [8] [6, 61, 97, 12]
4 [8, 13, 18, 57] [6] [61, 97, 12]
5 [6, 8, 13, 18, 57] [61] [97, 12]
6 [6, 8, 13, 18, 57, 61] [97] [12]
7 [6, 8, 13, 18, 57, 61, 97] [12] []
* [6, 8, 12, 13, 18, 57, 61, 97]
%% Cell type:markdown id:95324903-ea1d-4767-9b2d-d5af5e601773 tags:
Komplexitäten für die Vergleichoperationen $T_{Cm}$:
\begin{equation}
T _{Cm}^{Mn} \;=\; T _{Cm}^{Mx} \;=\; T _{Cm}^{Av} \;=\;
\sum_{i=2}^{n} \log_2 i \;\approx \;\int_2^n \log_2 i \; \text{d} i
\;\approx\; O(n \log(n))
\end{equation}
Die Komplexität $ T_{Mv}$ wird gegenüber dem direkten Einfügen nicht beeinflußt.
%% Cell type:markdown id:c5b61d12-04bd-440e-ae21-04fe7b1b519b tags:
#### **<font color='blue'>Direktes Auswählen</font>**
Die Zahl der Zuweisungen läßt sich beim Sortierverfahren **Direktes Auswählen** reduzieren,
in dem man das kleinste Element der unsortierten Sequenz $a[i], a[i+1], ... , a[n-1]$ sucht,
und dieses durch Austausch an die erste Stelle der unsortierten Sequenz setzt, indem man es mit $a[i]$ austauscht.
Dadurch ist die sortierte Sequenz $a[0], a[1], ... , a[i-1]$ um ein Element verlängert worden.
Im nächsten Schritt wird somit eine verkürzte Sequenz durchsucht.
Im Beispiel ist das kleinste Element in der Sequenz $a[i], a[i+1], ... , a[n-1]$
fett gesetzt, die sortierte Sequenz wieder unterstrichen:
\begin{equation}
\begin{matrix}
[\; 13,\; 57,\; 18,\; 8,\; \mathbf{6},\; 61,\; 97,\; 12 \;] \\
[\; \underline{6},\; 57,\; 18,\; \mathbf{8},\; 13,\; 61,\; 97,\; 12 \;] \\
[\; \underline{6,\; 8},\; 18,\; 57,\; 13,\; 61,\; 97,\; \mathbf{12} \;] \\
[\; \underline{6,\; 8,\; 12},\; 57,\; \mathbf{13},\; 61,\; 97,\; 18 \;] \\
[\; \underline{6,\; 8,\; 12,\; 13},\; 57,\; 61,\; 97,\; \mathbf{18} \;] \\
[\; \underline{6,\; 8,\; 12,\; 13,\; 18},\; 61,\; 97,\; \mathbf{57} \;] \\
[\; \underline{6,\; 8,\; 12,\; 13,\; 18,\; 57},\; 97,\; \mathbf{61} \;] \\
[\; \underline{6,\; 8,\; 12,\; 13,\; 18,\; 57,\; 61},\; 97 \;] \\
\end{matrix}
\end{equation}
%% Cell type:code id:c8a0f512-6413-4650-acbf-4e7848d4b92d tags:
``` python
def sort_selection( a ):
for i in range( 0, len( a ) ):
print( i, a[:i], a[i:] )
i_min = i
for j in range( i+1, len( a ) ): # Kleinsten Wert suchen
if( a[j] < a[i_min] ): i_min = j;
a[i], a[i_min] = a[i_min], a[i] # Austauschen
print( '*', a )
sort_selection( [ 13, 57, 18, 8, 6, 61, 97, 12 ] )
```
%% Output
0 [] [13, 57, 18, 8, 6, 61, 97, 12]
1 [6] [57, 18, 8, 13, 61, 97, 12]
2 [6, 8] [18, 57, 13, 61, 97, 12]
3 [6, 8, 12] [57, 13, 61, 97, 18]
4 [6, 8, 12, 13] [57, 61, 97, 18]
5 [6, 8, 12, 13, 18] [61, 97, 57]
6 [6, 8, 12, 13, 18, 57] [97, 61]
7 [6, 8, 12, 13, 18, 57, 61] [97]
* [6, 8, 12, 13, 18, 57, 61, 97]
%% Cell type:markdown id:6a94e6bf-576a-4677-8f63-e830339b42fe tags:
Für die Komplexitäten der Zuweisungen (Index $Mv$)
\begin{equation}
T _{Mv}^{Mn} \,=\, 3n -3 \,=\, O(n) \, ,
\quad
T _{Mv}^{Mx} \,=\, \frac 1 2 ( n^2 + 3n - 4 ) \,=\, O(n^2) \; ,
\quad
T _{Mv}^{Av} \,=\, O(n \log(n))
\end{equation}
Die Komplexität der Vergleiche ist immer die selbe und entspricht dem Maximalfall des direkten Einfügens:
\begin{equation}
T _{Cm}^{Mn} \;=\; T _{Cm}^{Mx} \;=\; T _{Cm}^{Av} \;=\;
\frac 1 2 (n^2-n)\;=\; O(n^2)
\end{equation}
%% Cell type:markdown id:fca70245-7782-4989-969a-2a337c25cee7 tags:
#### **<font color='blue'>Direktes Austauschen / Bubble-Sort</font>**
Beginnend mit dem letzten aktuellen Elementpaar $a[n-2]$ und $a[n-1]$ werden Vergleiche entsprechend der Ordnungsrelation durchgeführt
(Anmerkung: die Indices bei 'C++' laufen von $0$ bis $n-1$).
Falls $a[n-1] < a[n-2]$ wird mit den Elementen ein Austausch (swap) durchgeführt.
Im nächsten Teilschritt werden nun die aktuellen benachbarten Elemente $a[n-3]$ und $a[n-2]$ behandelt.
Im weiteren Verlauf wandert die kleinste Zahl nach vorn und darüberhinaus werden die anderen Zahlen in der Sequenz implizit
**vorsortiert**. Mit diesem Vorgehen *von hinten nach vorne* ergibt sich die Sequenz:
\begin{equation}
\begin{matrix}
[\; 13,\; 57,\; 18,\; 8,\; \mathbf{6},\; 61,\; 97,\; 12 \;] \\
[\; \underline{6},\; 13,\; 57,\; 18\;, \mathbf{8},\; 12,\; 61,\; 97 \;] \\
[\; \underline{6,\; 8},\; 13,\; 57,\; 18,\; \mathbf{12},\; 61,\; 97 \;] \\
[\; \underline{6,\; 8,\; 12},\; \mathbf{13},\; 57,\; 18,\; 61,\; 97 \;] \\
[\; \underline{6,\; 8,\; 12,\; 13},\; \mathbf{18},\; 57,\; 61,\; 97 \;] \\
[\; \underline{6,\; 8,\; 12,\; 13,\; 18},\; \mathbf{57},\; 61,\; 97 \;] \\
[\; \underline{6,\; 8,\; 12,\; 13,\; 18,\; 57},\; \mathbf{61},\; 97 \;] \\
[\; \underline{6,\; 8,\; 12,\; 13,\; 18,\; 57,\; 61},\; \mathbf{97} \;] \\
\end{matrix}
\end{equation}
Im drittletzten Schritt erfolgt hier kein swappen mehr.
Dies liegt an der *Vorsortierung*, so dass der Algorithmus abgebrochen werden kann, wenn kein Austausch mehr erfolgt ist.
Der zugehörie Code ist dann z.B.:
%% Cell type:code id:5009c3e7-aaef-45d5-886d-1e73f118cb52 tags:
``` python
import numpy as np
def sort_bubble( a ):
for i in range( 0, len( a ) ):
print( i, a[:i], a[i:] )
swapped = False
for j in range( len( a )-1, i, -1 ):
if a[j-1] > a[j]:
a[j-1], a[j] = a[j], a[j-1] # Austauschen
swapped = True
if not swapped:
break
print( '*', a )
sort_bubble( [ 13, 57, 18, 8, 6, 61, 97, 12 ] )
```
%% Output
0 [] [13, 57, 18, 8, 6, 61, 97, 12]
1 [6] [13, 57, 18, 8, 12, 61, 97]
2 [6, 8] [13, 57, 18, 12, 61, 97]
3 [6, 8, 12] [13, 57, 18, 61, 97]
4 [6, 8, 12, 13] [18, 57, 61, 97]
* [6, 8, 12, 13, 18, 57, 61, 97]
%% Cell type:markdown id:6067d7f5-632e-4751-82cb-4e222c1817b7 tags:
Die Komplexitäten sind
\begin{equation}
T _{Cm}^{Mn} \,=\, O(n)\, ,
\quad
T _{Cm}^{Mx} \,=\, T _{Cm}^{Av} \,=\, O(n^2) \; ,
\end{equation}
und
\begin{equation}
T _{Mv}^{Mn} \,=\, 0 \, ,
\quad
T _{Mv}^{Mx} \,=\, \frac 3 2 ( n^2 - n ) \,=\, O(n^2) \; ,
\quad
T _{Mv}^{Av} \,=\, \frac 3 4 ( n^2 - n ) \,=\, O(n^2) \; ,
\end{equation}
trotzdem nicht besser als beim direkten Einfügen.
Ein Vorteil des Verfahrens liegt sicherlich in der
einfachen Implementierung.
#### **<font color='blue'>Laufzeitvergleich der Direkten Verfahren </font>**
%% Cell type:code id:bcab2496-baa7-44f5-8069-559828bb20d9 tags:
``` python
import numpy as np
import time
def sort_insertion( a ):
for i in range( 1, len( a ) ):
a_i = a[i] # Aktuelles Element
j = i-1
while ( j >= 0 and a_i < a[j] ): # Vergleich und
a[j+1] = a[j] # Verschieben nach rechts bzw. öffnen der Lücke
j -= 1
a[j+1] = a_i # Aktuelles Element einfügen
def sort_binary_insertion( a ):
for i in range( 1, len( a ) ):
a_i = a[i]
ug = 0; og = i-1
while( ug <= og ):
j = ( ug + og ) // 2
if ( a_i < a[j] ): og = j-1
else: ug = j+1
#for j in range( i, ug, -1 ):
# a[j] = a[j-1];
a[ug+1:i+1] = a[ug:i]
a[ug] = a_i
def sort_selection( a ):
for i in range( 0, len( a ) ):
i_min = i
for j in range( i+1, len( a ) ): # Kleinsten Wert suchen
if( a[j] < a[i_min] ): i_min = j;
a[i], a[i_min] = a[i_min], a[i] # Austauschen
def sort_bubble( a ):
for i in range( 0, len( a ) ):
swapped = False
for j in range( len( a )-1, i, -1 ):
if a[j-1] > a[j]:
a[j-1], a[j] = a[j], a[j-1] # Austauschen
swapped = True
if not swapped:
break
# Vektor der Werte
values = np.random.rand(1000)
repeat = 100
start_time = time.time()
for i in range(repeat):
vals = values.copy()
sort_insertion( vals )
time_insertion = time.time()-start_time
start_time = time.time()
for i in range(repeat):
vals = values.copy()
sort_binary_insertion( vals )
time_binary_insertion = time.time()-start_time
start_time = time.time()
for i in range(repeat):
vals = values.copy()
sort_selection( vals )
time_selection = time.time()-start_time
start_time = time.time()
for i in range(repeat):
vals = values.copy()
sort_bubble( vals )
time_bubble = time.time()-start_time
print( "Einfügen ", time_insertion )
print( "Binäres Einfügen ", time_binary_insertion )
print( "Auswählen ", time_selection )
print( "Bubble ", time_bubble )
```
%% Output
Einfügen 3.863898992538452
Binäres Einfügen 0.14241480827331543
Auswählen 4.684155702590942
Bubble 9.374122381210327
%% Cell type:markdown id:628beafc-34ee-4681-aac9-a6c8ab417244 tags:
### **<font color='blue'>Höhere Sortierverfahren</font>**
Direkte Verfahren zeichnen sich dabei dadurch aus, dass sie in der Regel das gesamte Array behandeln, d.h. ein Problem der Größe $n$.
#### **<font color='blue'>Divide-and Conquer Methodik</font>**
Divide-and-Conquer bzw. teile-und-herrsche Verfahren stellen eine allgemeine Lösungsstrategie dar.
Das Vorgehen erfolgt in den drei Schritten:
* **Zerlege** das Problem in **Teilprobleme**
* Finde die **Lösungen** der **Teilprobleme**
* Setze die Gesamtlösung als **Kombination der Lösungen** zusammen
Das Vorgehen erfolgt **rekursiv**, d.h. die Lösung der Teilprobleme
wird wiederum mit diesen drei Schritten ermittelt, es sei denn
das resultierende Teilproblem ist trivial und seine Lösung
unmittelbar bekannt, so dass die Rekursion stoppt.
Die resultierende Komplexität läßt sich allgemein angeben. <br>
Teilt man das Problem der Größe $n$
in $a$ Teilprobleme der Größe $n/b$, so ist
die Komplexität berechenbar aus:
\begin{equation}
T(n) = a \cdot T(n/b) + t(n) \; .
\end{equation}
Darin ist $t(n)$ der Aufwand für die Zerlegungs- und
Zusammensetzungsschritte. Geht man von einer
Gesamtproblemgröße $n = b^k$ aus, und ist $T(1) = c$
der Aufwand für das triviale Problem, so ergeben sich die
Lösungen für die Gesamtkomplexität:
\begin{equation}
\begin{split}
a<b : &\quad T(n) \leq c \;\frac{b}{b-a} n = O(n) \\
a=b : &\quad T(n) = c \; n \;\log_b n + 1 = O(n \log n) \\
a>b : &\quad T(n) \leq c \; \frac{a}{a-b} \; n^{\log_b a} = O(n^{\log_b a})
\end{split}
\end{equation}
Oftmals ist $a=b=2$, so dass man auf die überlineare Komplexität
$O(n \log n)$ stößt. <br>
Beispiele hierfür sind das **binäre Suchen**,
**Merge-Sort** und **Quick-Sort**.
%% Cell type:markdown id:2a29019c-547c-47c5-b313-98f1787ee694 tags:
#### **<font color='blue'>Merge-Sort</font>**
Beim Merge-Sort wird die zu sortierende Sequenz ohne echten Aufwand rekursiv solange halbiert,
bis nur noch Sequenzen (Teilprobleme) der Größe 1 vorliegen.
Diese sind für sich alleine betrachtet **sortiert**, so dass zu deren Problemlösung nichts gemacht werden muss.
Für das Beispiel sieht die Aufspaltung so aus:
\begin{equation}
\begin{matrix}
[\; 13,\; 57,\; 18,\; 8,\; 6,\; 61,\; 97,\; 12 \;] \\
[\; 13,\; 57,\; 18,\; 8\;], \;[\; 6,\; 61,\; 97,\; 12 \;] \\
[\; 13,\; 57\;], \;[\; 18,\; 8\;], \;[\; 6,\; 61\;], \;[\; 97,\; 12 \;] \\
[\; 13\;], \;[\; 57\;], \;[\; 18\;], \;[\; 8\;], \;[\; 6\;], \;[\; 61\;], \;[\; 97\;], \;[\; 12 \;] \;. \\
\end{matrix}
\end{equation}
Die rekursive Funktion besitzt hier als Parameter
das Array `a` sowie den untersten Index `i_left`, ab dem sortiert werden soll,
und die Länge des Teilarrays `len_a`:
%% Cell type:code id:6d719409-8826-43fe-b197-e465d8695efb tags:
``` python
def merge_sort( a, i_left, len_a ):
if len_a == 1: return
len_a1 = len_a // 2
len_a2 = len_a - len_a1
merge_sort( a, i_left, len_a1 )
merge_sort( a, i_left+len_a1, len_a2 )
merge_solutions( a, i_left, len_a1, len_a2 )
```
%% Cell type:markdown id:f4f450bf-7b92-435d-af2d-3d7babfdf4a8 tags:
Es muss aus den beiden sortiert vorliegenden Sequenzen
$a_1$ und $a_2$
eine gemeinsame, sortierte Sequenz $b$ entstehen.
Dies erfolgt nach dem Reisverschlussverfahren.
Hierbei wird der kleinste noch in den Teilsequenzen
$a_1$ und $a_2$ enthaltene Wert entnommen (und gestrichen)
und an die ursprünglich leere Sequenz $b$ angehängt.
Das Beispiel des letzten Merge-Schrittes ist:
\begin{equation}
\begin{matrix}
a_1 = [\; 8,\; 13,\; 18,\; 57 \;] \quad\searrow&\\
& b = [\; 6,\; 8,\; 12,\; 13,\; 18,\; 57,\; 61,\; 97 \;] \; . \\
a_2 = [\; 6,\; 12,\; 61,\; 97 \;] \quad\nearrow&\\
\end{matrix}
\end{equation}
Die passende Funktion lautet:
%% Cell type:code id:8fb821c6-f0b6-4b89-a5a7-ae58cdd5f7cf tags:
``` python
def merge_solutions( a, i_left, len_a1, len_a2 ):
b = a[i_left:i_left+len_a1+len_a2].copy() # Temporärer Array
i_left1 = i_left
i_left2 = i_left+len_a1
for k in range( len_a1+len_a2 ):
# Nehme aus a1, wenn a2 leer oder Element von a1 <= a2
if ( i_left2 >= i_left+len_a1+len_a2 ) or \
( i_left1 < i_left+len_a1 and a[i_left1] <= a[i_left2]):
b[k] = a[i_left1]
i_left1 += 1
else: # sonst nehme aus a2
b[k] = a[i_left2]
i_left2 += 1
# Zurückkopieren des gemischten Vektors
#for k in range( len_a1+len_a2 ): a[i_left+k] = b[k]
a[i_left:i_left+len_a1+len_a2] = b
```
%% Cell type:markdown id:22e687bf-94dc-43d9-aab6-ab853f01baf3 tags:
In der Implementierung wird für `b` ein temporärer Array erzeugt und dessen Inhalt abschließend wieder in den Teilbereich
des Arrays `a` zurückkopiert wird. Insgesamt liefern die Merge-Schritte ausgehend von den trivialen Problemen:
\begin{equation}
\begin{matrix}
[\; 13\;], \;[\; 57\;], \;[\; 18\;], \;[\; 8\;], \;[\; 6\;], \;[\; 61\;], \;[\; 97\;], \;[\; 12 \;] \\
[\; 13,\; 57\;], \;[\; 8,\; 18\;], \;[\; 6,\; 61\;], \;[\; 12,\; 97 \;] \\
[\; 8,\; 13,\; 18,\; 57\;], \;[\; 6,\; 12,\; 61,\; 97 \;] \\
[\; 6,\; 8,\; 12,\; 13,\; 18,\; 57,\; 61,\; 97 \;] \\
\end{matrix}
\end{equation}
Die Komplexitäten sind für den besten und schlechsten Fall gleich:
\begin{equation}
T _{Cm}^{Mn} \,=\, T _{Cm}^{Mx} \,=\, T _{Cm}^{Av} \,=\, O(n \log n) \; ,
\quad
T _{Mv}^{Mn} \,=\, T _{Mv}^{Mx} \,=\, T _{Mv}^{Av} \,=\,O(n \log n) \; .
\end{equation}
Somit ist dieses Verfahren im Durchschnitt genauso gut oder
besser als die bisherigen. Nachteil ist jedoch die Notwendigkeit
eines temporären Arrays beim merge, der im letzen Schritt genauso
groß ist wie der zu sortierende.
Unter Nutzung der **Python-Liste** mit ihren Methoden kann man auch folgenden Implementierung entwickeln:
%% Cell type:code id:958ed917-4e5d-4d17-8f71-afc7fa78e1ac tags:
``` python
def merge_sort_list( unsorted_list ):
if len(unsorted_list) <= 1: return unsorted_list
i_middle = len(unsorted_list) // 2
left_list = unsorted_list[:i_middle]
right_list = unsorted_list[i_middle:]
left_list = merge_sort_list(left_list)
right_list = merge_sort_list(right_list)
return list( merge_lists(left_list, right_list) )
def merge_lists( left_half, right_half ):
res = []
while len(left_half) != 0 and len(right_half) != 0:
if left_half[0] < right_half[0]:
res.append(left_half[0])
left_half.remove(left_half[0])
else:
res.append(right_half[0])
right_half.remove(right_half[0])
if len(left_half) == 0:
res = res + right_half
else:
res = res + left_half
return res
```
%% Cell type:markdown id:f367ed27-8a06-4268-90cc-6616e7ee2015 tags:
Der Laufzeitvergleich mit dem gleichen Vektor von oben liefert:
%% Cell type:code id:6424593e-628f-498d-9fcb-f3e362ee91b4 tags:
``` python
start_time = time.time()
for i in range(repeat):
vals = values.copy()
merge_sort( vals, 0, len(vals) )
time_merge = time.time()-start_time
start_time = time.time()
for i in range(repeat):
vals = values.tolist()
r_vals = merge_sort_list( vals )
time_merge_list = time.time()-start_time
print( "Merge ", time_merge )
print( "Merge with lists ", time_merge_list )
```
%% Output
Merge 0.3301358222961426
Merge with lists 0.15523695945739746
%% Cell type:markdown id:9151915b-41d1-4903-927d-a2b8adaec1f8 tags:
Folgend werden die Sortierverfahren des Numpy-Pakets genutzt, die natürlich schneller sind, das sie komplett in einer Compilersprache implementiert sind:
%% Cell type:code id:434b07c4-c846-472a-b077-c96d2f78958d tags:
``` python
start_time = time.time()
for i in range(repeat):
np.sort(values, kind='mergesort')
time_merge_numpy = time.time()-start_time
start_time = time.time()
for i in range(repeat):
np.sort(values, kind='quicksort')
time_quick_numpy = time.time()-start_time
start_time = time.time()
for i in range(repeat):
np.sort(values, kind='heapsort')
time_heap_numpy = time.time()-start_time
print( "Merge numpy ", time_merge_numpy )
print( "Quick numpy ", time_quick_numpy )
print( "Heap numpy ", time_heap_numpy )
```
%% Output
Merge numpy 0.008358955383300781
Quick numpy 0.0012059211730957031
Heap numpy 0.0019860267639160156
%% Cell type:markdown id:4aee6dcf-d797-45cf-98f5-94f3ef414132 tags:
#### **<font color='blue'>Quick-Sort</font>**
Während beim Merge-Sort der Aufwand innerhalb der Verschmelzung der Teillösungen
versteckt ist, ist er es beim Quick-Sort innerhalb der Partitionierungsphase.
Die Aufteilung wird so vorgenommen, dass die Elemente der
ersten Teilsequenz alle kleiner als die Elemente der zweiten sind.
Hierzu wird ein Zerlegungselement $a[k]$ (Pivotelement),
wobei $k$ beliebig sein kann, gewählt.
Elemente des vorderen und hinteren Teil des Array, die größer bzw.
kleiner als das Pivotelement sind werden paarweise ausgetauscht,
bis eine Folge entstanden ist, für die gilt:
\begin{equation}
\begin{split}
\text{linker Teil:} &\quad \; i = 0, 1, ..., j, : \quad a[i] \leq a[k] \\
\text{rechter Teil:} &\quad \; i = n-1, n-2, ..., j+1 : \quad a[i] \geq a[k]
\end{split}
\end{equation}
Welche Zerlegungstelle $j$ gefunden wird und wie lang die Teilsequenzen
sind, ist so zufällig wie die Wahl des Pivotelementes.
Für das Beispiel ergeben sich die Folgen,
in denen die zur Zerlegung vorgesehenen Sequenzen unterstrichen sind,
und das Zerlegungselement fett gesetzt ist.
\begin{equation}
\begin{matrix}
[\; \underline{13,\; 57,\; 18,\; \mathbf 8,\; 6,\; 61,\; 97,\; 12} \;] \\
[\; \underline{\mathbf 6,\; 8},\; 18,\; 57,\; 13,\; 61,\; 97,\; 12 \;] \\
[\; 6,\; 8,\; \underline{18,\; 57,\; \mathbf {13},\; 61,\; 97,\; 12} \;] \\
[\; 6,\; 8,\; \underline{\mathbf {12},\; 13},\; 57,\; 61,\; 97,\; 18 \;] \\
[\; 6,\; 8,\; 12,\; 13,\; \underline{ 57,\; \mathbf {61},\; 97,\; 18} \;] \\
[\; 6,\; 8,\; 12,\; 13,\; \underline{ \mathbf {57},\; 18},\; 97,\; 61 \;] \\
[\; 6,\; 8,\; 12,\; 13,\; 18,\; 57,\; \underline{\mathbf {97},\; 61} \;] \\
[\; 6,\; 8,\; 12,\; 13,\; 18,\; 57,\; 61,\; 97 \;] \\
\end{matrix}
\end{equation}
Die zugehörige Funktion lautet:
%% Cell type:code id:f2579fba-259d-4ed5-b29d-a83aa9b26ce9 tags:
``` python
def quick_sort( a, ug, og ):
i, j = ug, og
pivot = a[(ug+og)//2] # Pivotelement
while ( i <= j ):
while ( a[i] < pivot ): i += 1
while ( a[j] > pivot ): j -= 1
if ( i <= j ):
a[i], a[j] = a[j], a[i]
i += 1 ; j -= 1
#print( pivot, a )
# Teilen und Sortieren
if ( ug < j ): quick_sort( a, ug, j )
if ( i < og ): quick_sort( a, i, og )
vals = [ 13, 57, 18, 8, 6, 61, 97, 12 ]
quick_sort( vals, 0, len(vals)-1 )
print( vals )
```
%% Output
[6, 8, 12, 13, 18, 57, 61, 97]
%% Cell type:code id:c8bf3504-a04c-4578-9309-ca34fd28dc80 tags:
``` python
start_time = time.time()
for i in range(repeat):
vals = values.copy()
r_vals = quick_sort( vals, 0, len(vals)-1 )
time_quick_array = time.time()-start_time
print( repeat, len(values) )
start_time = time.time()
for i in range(repeat):
vals = values.tolist()
r_vals = quick_sort( vals, 0, len(vals)-1 )
time_quick_list = time.time()-start_time
print( "Quick with array ", time_quick_array )
print( "Quick with lists ", time_quick_list )
```
%% Output
100 1000
Quick with array 0.14501237869262695
Quick with lists 0.07118535041809082
%% Cell type:markdown id:066c52c5-8d2f-4f54-92b1-ec24c110ea27 tags:
Ein expliziter Zusammensetzungsschritt ist hier nicht notwendig, da implizit die Gesamtsortierung erreicht wird.
Die Komplexitäten sind je nachdem wie gut die Folgen vorsortiert sind:
\begin{equation}
T _{Cm}^{Mn} \,=\, T _{Mv}^{Mn} \,=\, O(n) \; ,
\quad
T _{Cm}^{Av} \,=\, T _{Mv}^{Av} \,=\, O(n \log n) \; .
\quad
T _{Cm}^{Mx} \,=\, T _{Mv}^{Mx} \,=\, O(n^2) \; .
\end{equation}
Der Vorteil gegenüber Merge-Sort liegt darin, dass hier kein
zusätzlicher Speicherplatz benötigt wird, das Sortieren
erfolgt In-Place, auf dem Platz des gegebenen Arrays.
%% Cell type:markdown id:84964082-54b0-4684-ac7f-f55fc4dc5dfe tags:
## **<font color='blue'>Entscheidungsprobleme</font>**
Bei Entscheidungsproblemen gilt es, eine optimale Belegung eines Lösungsvektors im Hinblick auf eine zu minimierende oder zu maximierende Gütefunktion zu bestimmen.
Klassische Vertreter hierfür sind das Rucksack-Problem und das Wechselgeld-Problem.
### **<font color='blue'>Rucksack-Problem</font>**
Bei diesem Problem soll eine optimale Bepackung eines Rucksacks bestimmt werden.
Es stehen $n$ verschiedene Objekte zur Verfügung, jedes mit einem Gewicht $g_i$ und einem Nutzwert $w_i$.
Die Aufgabe besteht nun darin, die Objekte auszuwählen, die mit einem maximalen Gesamtgewicht $G$ in den Rucksack passen und den gesamten Nutzwert $W$ maximieren.
Welche Objekte ausgewählt werden, wird über eine Variable des Objekts $x_i$ gekennzeichnet, die den Wert 1 annimmt, falls das Objekt im Rucksack enthalten ist, sonst den Wert 0.
Alle $ x_i $ zusammengefasst ergeben den Lösungsvektor $X$.
Knapp formuliert lautet das Problem also:
\begin{equation}
\begin{split}
&\text{Gegeben: } \;n, w_i, g_i, G \in \mathbb{N} \\
&\text{Bestimme: } \; X = ( x_1, x_2, ... , x_n )
\quad \text{mit} \quad x_i \in [0,1] \\
&\quad\quad\text{so dass} \quad
W = \sum_{i=1}^n x_i \; w_i \rightarrow \max \; ,
\qquad \text{wobei} \quad
G \geq \sum_{i=1}^n x_i \; g_i
\end{split}
\end{equation}
### **<font color='blue'>Wechselgeld-Problem</font>**
Die Aufgabe beim Wechselgeld-Problem ist die Bestimmung der Münzen aus einem Vorrat von $n$ verschiedenen mit einem individuellen Wert $w_i$,
so dass die Wechselsumme $W$ genau getroffen wird und die Anzahl der gewählten Münzen minimal ist.
Der Lösungsvektor wird analog zum Rucksack-Problem aufgebaut, wobei die Münze $i$ dem Objekt $i$ gleichkommt.
\begin{equation}
\begin{split}
&\text{Gegeben: } \;n, w_i, W \in \mathbb{N} \\
&\text{Bestimme: } \; X = ( x_1, x_2, ... , x_n )
\quad \text{mit} \quad x_i \in [0,1] \\
&\quad\quad\text{so dass} \quad
N = \sum_{i=1}^n x_i \rightarrow \min \; ,
\quad \text{wobei} \quad
W = \sum_{i=1}^n x_i \; w_i
\end{split}
\end{equation}
### **<font color='blue'>Problem des Handlungsreisenden</font>**
Das Problem des Handlungsreisenden - Traveling Salesman Problem (TSP) - versucht die kürzeste bzw. günstigste Rundreise durch einen Satz von $n$ Städten zu bestimmen.
Dabei darf in der Rundreise jede Stadt nur einmal besucht werden.
Die möglichen Verbindungen zwischen Städten sind in Form von Kanten gegeben, wobei die Städte den Knoten entsprechen.
Die Distanz zwischen zwei verbundenen Städten $i$ und $j$ ist durch die Kantenwichtung $B_{ij}$ gegeben
Eine Lösung wird durch die Städtefolge bzw. Permutation $X = [x_1, x_2, ... , x_n]$ beschrieben.
Der Startpunkt kann zufällig gewählt werden, da es eine Rundreise ist.
Die Gesamtdistanz bzw. Kosten $C(X)$ für eine Lösung $X$ errechnen sich aus der Summe der Wichtungen der durchlaufenden Kanten:
\begin{eqnarray}
C(X) \;=\; \sum_{i=1}^{n-1} B_{x_i x_{i+1}} + B_{x_n x_{1}}
\quad \rightarrow \quad \min
\end{eqnarray}
Der letzte Summand berücksichtigt die Rückreise von der letzten zur ersten Stadt.
### **<font color='blue'>Brute-Force</font>**
Brute-Force-Algorithmen zeichnen sich dadurch aus, dass sie keine besondere Strategie oder algorithmischen Feinheiten beinhalten.
Sie behandeln die Problemstellung sehr einfach, so dass auch eine Implementierung schnell und einfach erfolgen kann.
Ein Brute-Force-Algorithmus ist gleichbedeutend mit der Bewertung aller denkbaren Möglichkeiten.
Dies wird z.B. im Vergleich der linearen Suche mit der binären Suche bereits deutlich.
Zwar wird immer die korrekte Lösung gefunden, jedoch ist der Aufwand in der Ausführung relativ hoch.
Der Brute-Force-Algorithmus testet bei den beiden Problemstellungen alle Möglichkeiten
der Auswahl, prüft ob die Restriktionen eingehalten wird, und merkt sich für die zulässigen Möglichkeiten den Lösungsvektor, für den die Zielvorgabe optimiert.
#### **<font color='blue'>Rucksackproblem</font>**
Beim Rucksackproblem werden zur Auswertung der Gewichtrestriktion und des Nutzwerts je $n-1$ Additionen und $n$ Multiplikationen durchgeführt,
d.h. der Aufwand je Vektor $X$ ist in der Größenordnung $O(n)$.
Da es $2^n$ Kombinationsmöglichkeiten für die Belegung des Vektors $X$ gibt ist der notwendige Gesamtaufwand
\begin{equation}
T_{Mult+Add}(n) \,=\, O(n \cdot 2^n) \; .
\end{equation}
%% Cell type:code id:5e7335ad-62ba-49fc-b1a4-d1ba8b728ba9 tags:
``` python
import itertools
def rucksack_brute_force( weights, values, capacity ):
max_value = max_x = 0
all_combinations = [[0,1] for i in range(len(weights))]
for x in itertools.product( *all_combinations ):
value = weight = 0
for i in range( len(x) ):
value += x[i] * values[i]
weight += x[i] * weights[i]
if weight <= capacity and value > max_value:
max_value = value
max_x = x[:]
return max_value, max_x
print( rucksack_brute_force([100, 50, 45, 20, 10, 5], [40, 35, 18, 4, 10, 2], 100 ) )
```
%% Output
(55, (0, 1, 1, 0, 0, 1))
%% Cell type:markdown id:792550d7-682e-4a6f-85e5-1febc0bffc8c tags:
#### **<font color='blue'>Wechselgeldproblem</font>**
Hier gibt es eine analoges Vorgehen:
%% Cell type:code id:5f477150-5801-4cd8-84c6-304e87217cc3 tags:
``` python
import math
def wechselgeld_brute_force( values, target ):
min_number = math.inf
min_x = ()
all_combinations = [[0,1] for i in range(len(values))]
for x in itertools.product( *all_combinations ):
value = number = 0
for i in range( len(x) ):
value += x[i] * values[i]
number += x[i]
if value == target and number < min_number:
min_number = number
min_x = x[:]
return min_number, min_x
print( wechselgeld_brute_force( [1, 1, 1, 1, 1, 5, 5, 10, 10, 25], 32 ) )
```
%% Output
(4, (0, 0, 0, 1, 1, 0, 1, 0, 0, 1))
%% Cell type:markdown id:389aca60-7c69-4418-9d4b-223815f66bd4 tags:
#### **<font color='blue'>Problem des Handlungsreisenden</font>**
Die Brute-Force-Komplexität umfaßt hier die Anzahl der möglichen Permutationen, die sich über die Fakulätsfunktion ergibt.
Diese liegt jenseits der $2^n$ Komplexität, so dass Brute-Force für größere Probleme nicht ausführbar ist.
%% Cell type:markdown id:b28ad86c-df91-4f8c-924b-99cdc7679ee2 tags:
### **<font color='blue'>Greedy-Algorithmen</font>**
* Greedy-Algorithmen bauen den Lösungsvektor **stückweise** auf, indem z.B. zunächst eine Variable $x_i$ bestimmt und festgelegt wird, danach eine zweite $x_j$ usw.,
um die Ziel- oder Gütefunktion zu maximieren bzw. zu minimieren.
* Bei jedem der Schritte wird die Entscheidung aber nur aufgrund der lokal bzw. **derzeit verfügbaren Information** getätigt.
* Stehen im Einzelschritt mehrere zulässige Teillösungen $x_k$ zur Verfügung, so wird die Teillösung gewählt, die die Gütefunktion am stärksten verbessert.
In anderen Worten: die aktuelle Entscheidung wird **gierig** (greedy) gewählt, so dass der Nutzen der Teillösung maximiert wird.
* Bei dieser Vorgehenweise werden also **keine Änderung** bereits getroffener Entscheidungen in Erwägung gezogen und es werden auch nicht die zukünftigen Entscheidungsmöglichkeiten in die Lösungsfindung einbezogen.
Dieses Greedy-Prinzip kann in vielen Fällen funktionieren und tatsächlich auf eine optimale Lösung führen.
Oftmals ist die Lösung nicht optimal, aber meist auch nicht nicht ganz schlecht.
Die Lösungsqualität hängt sicher stark von der Entscheidungsstrategie ab, die im Einzelschritt angewendet wird.
Der Vorteil der Greedy-Algorithmen ist in der einfachen Implementierung und in der extrem kurzen Laufzeit zu sehen.
Letztere hängt natürlich vom Aufwand der gewählten Entscheidungsstrategie ab.
%% Cell type:markdown id:a1340d58-d4f2-45f7-94d5-3dc94295f6d1 tags:
#### **<font color='blue'>Wechselgeld-Problem</font>**
Als Entscheidungsstrategie wird im Beispiel folgende gewählt:
Wähle die Münze, die der Differenz zwischen $W$ und dem
aktuellen Wert der bisher gewählten Münzen $\sum_{i=1}^n x_i \; w_i$ am nächsten
kommt, aber kleiner ist.
Hat man beispielsweise fünf 1 Cent, zwei 5 Cent, zwei 10 Cent und eine 25 Cent Münze im Geldbeutel und muß in Summe 32 Cent herausgeben, so würde die Entscheidung auf die Wahl der 25 Cent Münze fallen.
25 Cent ist die größte Münze, die kleiner-gleich der 32 Cent ist.
Es fehlen noch 7 Cent.
Die zweite Wahl fällt somit auf die 5 Cent Münze.
Für die restlichen 2 Cent werden in den letzten beiden
Entscheidungen je 1 Cent Münzen gewählt:
\begin{equation}
\begin{split}
w_i : & \quad [\;1,\;1,\;1,\;1,\;1,\;5,\;5,10,10,25\;] \\
\text{1. Entscheidung} \; &\quad [\;0,\;0,\;0,\;0,\;0,\;0,\;0, \;0, \;\;0,\;\; 1\;] \; \rightarrow \;\; W_X = 25 \\
\text{2. Entscheidung} \; &\quad [\;0,\;0,\;0,\;0,\;0,\;0,\;1, \;0, \;\;0,\;\; 1\;] \; \rightarrow \;\; W_X = 30 \\
\text{3. Entscheidung} \; &\quad [\;0,\;0,\;0,\;0,\;1,\;0,\;1, \;0, \;\;0,\;\; 1\;] \; \rightarrow \;\; W_X = 31 \\
\text{4. Entscheidung} \; &\quad [\;0,\;0,\;0,\;1,\;1,\;0,\;1, \;0, \;\;0,\;\; 1\;] \; \rightarrow \;\; W_X = 32
\end{split}
\end{equation}
#### **<font color='blue'>Rucksack-Problem</font>**
Hier wird wiederholt ein noch nicht eingepackter Gegenstand nach einem Kriterium ausgewählt und in den Rucksack gepackt, sofern die Gewichtsrestriktion nicht überschritten wird.
Verschiedene Kriterien sind denkbar:
* **Wert**: der Gegenstand mit höchstem Nutzwert wird ewählt.
* **Gewicht**: der Gegenstand mit geringstem Gewicht wird ewählt.
* **Dichte**: der Gegenstand mit bestem Nutzwert-Gewichts-Verhältnis (Dichte) wird gewählt.
Ein Beispiel soll nun zeigen, dass für die verschiedenen Kriterien auch verschiedene Lösungen entstehen.
In der folgenden Tabelle sind die Objektgewichte $g_i$ und -werte $w_i$ aufgelistet. Als Gewichtsrestriktion
wird $G=100$ gewählt.
\begin{equation}
\begin{matrix}
i \;& g_i & w_i & w_i/g_i & \textrm{Kriterium:} & \textrm{Wert} & \textrm{Gewicht} & \textrm{Dichte} & \textrm{Optimum} \\
1 \;& 100 & 40 & 0,4 && 1 & 0 & 0 & 0 \\
2 \;& 50 & 35 & 0,7 && 0 & 0 & 1 & 1 \\
3 \;& 45 & 18 & 0,4 && 0 & 1 & 0 & 1 \\
4 \;& 20 & 4 & 0,2 && 0 & 1 & 1 & 0 \\
5 \;& 10 & 10 & 1,0 && 0 & 1 & 1 & 0 \\
6 \;& 5 & 2 & 0,4 && 0 & 1 & 1 & 1 \\
& & && \sum_{i=1}^n x_i g_i : & 100 & 80 & 85 & 100 \\
& & && \sum_{i=1}^n x_i w_i : & 40 & 34 & 51 & 55 \\
\end{matrix}
\end{equation}
Beim Wert-Kriterium wird als erstes das teuerste Objekt gewählt, also $i=1$, das mit $w_1=40$ am wertvollsten ist.
Danach ist der Rucksack voll und es kann nichts mehr eingepackt werden.
Das resultierende Gewicht von 100 liegt am geforderten Limit von 100, als Gesamtwert wird 40 erreicht.
Das Gewichts-Kriterium berücksichtigt an keiner Stelle die Objektwerte.
Nacheinander werden die Objekte 6, 5, 4 und 3 gewählt.
Gewicht 2 passt danach nicht mehr Rucksack.
Der zulässige Gesamtmasse wird nicht erreicht, der Gesamtwert ist mit 34 schlechter als beim Wert-Kriterium.
Das beste Ergebnis der drei Kriterien liefert das Dichte-Kriterium mit einem Gesamtwert von 51 und einem Gesamtgewicht von 85.
Keines der Kriterien liefert jedoch die optimale Lösung.
Diese ist in der letzten Spalte angegeben und führt auf einen Gesamtwert von 55 und ein Gesamtgewicht von 100.
Um diese Lösung zu finden, kann man neben der Brute-Force-Methode auch die der Dynamischen Programmierung verwenden.
%% Cell type:code id:d573b1a1-cce0-4406-9430-ef99e57762aa tags:
``` python
```
<?xml version="1.0" encoding="windows-1252" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" height="430px" preserveAspectRatio="none" style="width:332px;height:430px;background:#FFFFFF;" version="1.1" viewBox="0 0 332 430" width="332px" zoomAndPan="magnify"><defs/><g><!--class Auftrag--><g id="elem_Auftrag"><rect codeLine="1" fill="#F1F1F1" height="129.4844" id="Auftrag" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="136" x="98" y="294"/><ellipse cx="135.5" cy="310" fill="#ADD1B2" rx="11" ry="11" style="stroke:#181818;stroke-width:1.0;"/><path d="M137.8438,305.6719 C136.9063,305.2344 136.3125,305.0938 135.4375,305.0938 C132.8125,305.0938 130.8125,307.1719 130.8125,309.8906 L130.8125,311.0156 C130.8125,313.5938 132.9219,315.4844 135.8125,315.4844 C137.0313,315.4844 138.1875,315.1875 138.9375,314.6406 C139.5156,314.2344 139.8438,313.7813 139.8438,313.3906 C139.8438,312.9375 139.4531,312.5469 138.9844,312.5469 C138.7656,312.5469 138.5625,312.625 138.375,312.8125 C137.9219,313.2969 137.9219,313.2969 137.7344,313.3906 C137.3125,313.6563 136.625,313.7813 135.8594,313.7813 C133.8125,313.7813 132.5156,312.6875 132.5156,310.9844 L132.5156,309.8906 C132.5156,308.1094 133.7656,306.7969 135.5,306.7969 C136.0781,306.7969 136.6875,306.9531 137.1563,307.2031 C137.6406,307.4844 137.8125,307.7031 137.9063,308.1094 C137.9688,308.5156 138,308.6406 138.1406,308.7656 C138.2813,308.9063 138.5156,309.0156 138.7344,309.0156 C139,309.0156 139.2656,308.875 139.4375,308.6563 C139.5469,308.5 139.5781,308.3125 139.5781,307.8906 L139.5781,306.4688 C139.5781,306.0313 139.5625,305.9063 139.4688,305.75 C139.3125,305.4844 139.0313,305.3438 138.7344,305.3438 C138.4375,305.3438 138.2344,305.4375 138.0156,305.75 L137.8438,305.6719 Z " fill="#000000"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="54" x="154.5" y="314.8467">Auftrag</text><line style="stroke:#181818;stroke-width:0.5;" x1="99" x2="233" y1="326" y2="326"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="95" x="104" y="342.9951">name : string</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="112" x="104" y="359.292">dauer : int/float</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="105" x="104" y="375.5889">wert : int/float</text><line style="stroke:#181818;stroke-width:0.5;" x1="99" x2="233" y1="382.8906" y2="382.8906"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="124" x="104" y="399.8857">__repr__() : string</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="62" x="104" y="416.1826">__init__()</text></g><!--class Maschinenbelegungsplan--><g id="elem_Maschinenbelegungsplan"><rect codeLine="2" fill="#F1F1F1" height="227.2656" id="Maschinenbelegungsplan" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="318" x="7" y="7"/><ellipse cx="71.75" cy="23" fill="#ADD1B2" rx="11" ry="11" style="stroke:#181818;stroke-width:1.0;"/><path d="M74.0938,18.6719 C73.1563,18.2344 72.5625,18.0938 71.6875,18.0938 C69.0625,18.0938 67.0625,20.1719 67.0625,22.8906 L67.0625,24.0156 C67.0625,26.5938 69.1719,28.4844 72.0625,28.4844 C73.2813,28.4844 74.4375,28.1875 75.1875,27.6406 C75.7656,27.2344 76.0938,26.7813 76.0938,26.3906 C76.0938,25.9375 75.7031,25.5469 75.2344,25.5469 C75.0156,25.5469 74.8125,25.625 74.625,25.8125 C74.1719,26.2969 74.1719,26.2969 73.9844,26.3906 C73.5625,26.6563 72.875,26.7813 72.1094,26.7813 C70.0625,26.7813 68.7656,25.6875 68.7656,23.9844 L68.7656,22.8906 C68.7656,21.1094 70.0156,19.7969 71.75,19.7969 C72.3281,19.7969 72.9375,19.9531 73.4063,20.2031 C73.8906,20.4844 74.0625,20.7031 74.1563,21.1094 C74.2188,21.5156 74.25,21.6406 74.3906,21.7656 C74.5313,21.9063 74.7656,22.0156 74.9844,22.0156 C75.25,22.0156 75.5156,21.875 75.6875,21.6563 C75.7969,21.5 75.8281,21.3125 75.8281,20.8906 L75.8281,19.4688 C75.8281,19.0313 75.8125,18.9063 75.7188,18.75 C75.5625,18.4844 75.2813,18.3438 74.9844,18.3438 C74.6875,18.3438 74.4844,18.4375 74.2656,18.75 L74.0938,18.6719 Z " fill="#000000"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="180" x="92.25" y="27.8467">Maschinenbelegungsplan</text><line style="stroke:#181818;stroke-width:0.5;" x1="8" x2="324" y1="39" y2="39"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="241" x="13" y="55.9951">geplante_auftraege : list[Auftrag]</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="176" x="13" y="72.292">planungsdauer : int/float</text><line style="stroke:#181818;stroke-width:0.5;" x1="8" x2="324" y1="79.5938" y2="79.5938"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="201" x="13" y="96.5889">get_gesamtwert() : int/float</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="208" x="13" y="112.8857">get_gesamtdauer() : int/float</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="115" x="13" y="129.1826">zuruecksetzen()</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="152" x="13" y="145.4795">hinzufuegen(Auftrag)</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="202" x="13" y="161.7764">greedy_planen(list[Auftrag])</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="220" x="13" y="178.0732">_waehle(list[Auftrag]) : Auftrag</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="306" x="13" y="194.3701">_filter_auftrage(list[Auftrag]) : list[Auftrag]</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="124" x="13" y="210.667">__repr__() : string</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="62" x="13" y="226.9639">__init__()</text></g><!--reverse link Maschinenbelegungsplan to Auftrag--><g id="link_Maschinenbelegungsplan_Auftrag"><path codeLine="4" d="M166,247.774 C166,263.77 166,279.562 166,293.967 " fill="none" id="Maschinenbelegungsplan-backto-Auftrag" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="#181818" points="166,234.316,162,240.316,166,246.316,170,240.316,166,234.316" style="stroke:#181818;stroke-width:1.0;"/></g><!--SRC=[bL9BRiCW4Drp2Y-TIb7ttVK0FK5LeQdpJ539D1e6HVNf2vPJvsEiT0NpFlDXw0B5sBocNeMSwSReZMAtzmp-H81BxE8n41kpZ3TFs-rVWghefN4e5uMbKGk730OVzNqoVzIzvIZXZX8anvxrkG_Vf6lbr3f4EkW9ektclwwnrAEs8Kb2znMX0xZTr8KP75AVyl3SJcElg9q0knzRego_UgiLbpqHzW4t-aoSbrHtzZuePDgGMqZnuBwi5Hp5sl7zeehdjQhMizSYDcPAdL6c0sRpZDz_WPahcwvGInrTikSMVW40]--></g></svg>
\ No newline at end of file
<?xml version="1.0" encoding="windows-1252" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" height="484px" preserveAspectRatio="none" style="width:519px;height:484px;background:#FFFFFF;" version="1.1" viewBox="0 0 519 484" width="519px" zoomAndPan="magnify"><defs/><g><ellipse cx="334" cy="16" fill="#222222" rx="10" ry="10" style="stroke:none;stroke-width:1.0;"/><rect fill="#F1F1F1" height="33.9688" rx="12.5" ry="12.5" style="stroke:#181818;stroke-width:0.5;" width="167" x="250.5" y="67"/><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="147" x="260.5" y="88.1387">erhaltene Liste kopieren</text><rect fill="#F1F1F1" height="33.9688" rx="12.5" ry="12.5" style="stroke:#181818;stroke-width:0.5;" width="109" x="279.5" y="142"/><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="89" x="289.5" y="163.1387">Auftrge filtern</text><g id="elem_#7"><polygon fill="#F1F1F1" points="302,217,314,229,302,241,290,229,302,217" style="stroke:#181818;stroke-width:0.5;"/></g><rect fill="#F1F1F1" height="33.9688" rx="12.5" ry="12.5" style="stroke:#181818;stroke-width:0.5;" width="155" x="102.5" y="295"/><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="135" x="112.5" y="316.1387">besten Auftrag whlen</text><rect fill="#F1F1F1" height="33.9688" rx="12.5" ry="12.5" style="stroke:#181818;stroke-width:0.5;" width="324" x="7" y="370"/><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="304" x="17" y="391.1387">gewhlten Auftrag geplanten Auftrgen hinzufgen</text><rect fill="#F1F1F1" height="33.9688" rx="12.5" ry="12.5" style="stroke:#181818;stroke-width:0.5;" width="413" x="100.5" y="445"/><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="393" x="110.5" y="466.1387">gewhlten Auftrag aus Liste der zu planenden Auftrge entfernen</text><ellipse cx="302" cy="312" fill="none" rx="10" ry="10" style="stroke:#222222;stroke-width:1.0;"/><ellipse cx="302.5" cy="312.5" fill="#222222" rx="6" ry="6" style="stroke:none;stroke-width:1.0;"/><!--link start to erhaltene Liste kopieren--><g id="link_start_erhaltene Liste kopieren"><path d="M334,26.177 C334,35.269 334,49.527 334,61.485 " fill="none" id="start-to-erhaltene Liste kopieren" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="#181818" points="334,66.791,338,57.791,334,61.791,330,57.791,334,66.791" style="stroke:#181818;stroke-width:1.0;"/></g><!--link erhaltene Liste kopieren to Auftr?ge filtern--><g id="link_erhaltene Liste kopieren_Auftrge filtern"><path d="M334,101.201 C334,111.692 334,125.463 334,136.818 " fill="none" id="erhaltene Liste kopieren-to-Auftrge filtern" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="#181818" points="334,141.844,338,132.844,334,136.844,330,132.844,334,141.844" style="stroke:#181818;stroke-width:1.0;"/></g><!--link Auftr?ge filtern to #7--><g id="link_Auftrge filtern_#7"><path d="M326.416,176.115 C320.711,188.24 313.009,204.607 307.792,215.692 " fill="none" id="Auftrge filtern-to-#7" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="#181818" points="305.586,220.379,313.0369,213.9381,307.7146,215.8547,305.798,210.5324,305.586,220.379" style="stroke:#181818;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="183" x="321.9289" y="216.1456">passender auftrag verbleibend?</text></g><!--link #7 to besten Auftrag w?hlen--><g id="link_#7_besten Auftrag whlen"><path d="M295.091,234.587 C278.823,245.388 237.335,272.933 208.885,291.822 " fill="none" id="#7-to-besten Auftrag whlen" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="#181818" points="204.436,294.776,214.1464,293.1304,208.6015,292.0104,209.7215,286.4655,204.436,294.776" style="stroke:#181818;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="10" x="253" y="272.2104">ja</text></g><!--link besten Auftrag w?hlen to gew?hlten Auftrag geplanten Auftr?gen hinzuf?gen--><g id="link_besten Auftrag whlen_gewhlten Auftrag geplanten Auftrgen hinzufgen"><path d="M177.559,329.201 C175.978,339.692 173.903,353.463 172.192,364.818 " fill="none" id="besten Auftrag whlen-to-gewhlten Auftrag geplanten Auftrgen hinzufgen" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="#181818" points="171.434,369.844,176.7315,361.5412,172.1797,364.8999,168.821,360.3481,171.434,369.844" style="stroke:#181818;stroke-width:1.0;"/></g><!--link gew?hlten Auftrag geplanten Auftr?gen hinzuf?gen to gew?hlten Auftrag aus Liste der zu planenden Auftr?ge entfernen--><g id="link_gewhlten Auftrag geplanten Auftrgen hinzufgen_gewhlten Auftrag aus Liste der zu planenden Auftrge entfernen"><path d="M199.285,404.0202 C220.617,415.3049 249.25,430.4512 271.633,442.2912 " fill="none" id="gewhlten Auftrag geplanten Auftrgen hinzufgen-to-gewhlten Auftrag aus Liste der zu planenden Auftrge entfernen" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="#181818" points="276.309,444.7649,270.2247,437.0201,271.8895,442.4265,266.4832,444.0913,276.309,444.7649" style="stroke:#181818;stroke-width:1.0;"/></g><!--link gew?hlten Auftrag aus Liste der zu planenden Auftr?ge entfernen to Auftr?ge filtern--><g id="link_gewhlten Auftrag aus Liste der zu planenden Auftrge entfernen_Auftrge filtern"><path d="M328.737,444.9808 C344.111,431.6532 362,411.1674 362,388 C362,228 362,228 362,228 C362,211.171 354.577,193.645 347.375,180.601 " fill="none" id="gewhlten Auftrag aus Liste der zu planenden Auftrge entfernen-to-Auftrge filtern" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="#181818" points="344.745,176.007,345.746,185.8049,347.2295,180.346,352.6884,181.8296,344.745,176.007" style="stroke:#181818;stroke-width:1.0;"/></g><!--link #7 to end--><g id="link_#7_end"><path d="M302,241.263 C302,255.665 302,280.629 302,296.455 " fill="none" id="#7-to-end" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="#181818" points="302,301.836,306,292.836,302,296.836,298,292.836,302,301.836" style="stroke:#181818;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="25" x="303" y="272.2104">nein</text></g><!--SRC=[TOzB3e9044JtdgB32acuWeQzDo0iXbZpqKbBvgC9v-4OxhYOCq0C2rrsLVNhhh8wrlK9JWiRmA2ByG5qVmm63dn8NXDbSFEaGDAaZ5j48waOXFVWAno9716Add2zXUcJUYuew1NGtaH7FHANgTcIhtdIDitPLrYclQqmMC5V9LzdqeR7AEUtMh4_raJqMu7yrHWfK_ABEmw1Wqmj_fM3zSWEjGp37PNLyGC0]--></g></svg>
\ No newline at end of file
<?xml version="1.0" encoding="windows-1252" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" height="432px" preserveAspectRatio="none" style="width:620px;height:432px;background:#FFFFFF;" version="1.1" viewBox="0 0 620 432" width="620px" zoomAndPan="magnify"><defs/><g><ellipse cx="321.2658" cy="16" fill="#222222" rx="10" ry="10" style="stroke:none;stroke-width:1.0;"/><rect fill="#F1F1F1" height="33.9688" rx="12.5" ry="12.5" style="stroke:#181818;stroke-width:0.5;" width="236" x="203.2658" y="67"/><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="216" x="213.2658" y="88.1387">verbleibende Planungszeit ermitteln</text><rect fill="#F1F1F1" height="33.9688" rx="12.5" ry="12.5" style="stroke:#181818;stroke-width:0.5;" width="134" x="254.2658" y="142"/><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="114" x="264.2658" y="163.1387">neue Liste anlegen</text><rect fill="#F1F1F1" height="33.9688" rx="12.5" ry="12.5" style="stroke:#181818;stroke-width:0.5;" width="152" x="245.2658" y="217"/><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="132" x="255.2658" y="238.1387">erhaltene Liste prfen</text><g id="elem_#9"><polygon fill="#F1F1F1" points="429.2658,222,441.2658,234,429.2658,246,417.2658,234,429.2658,222" style="stroke:#181818;stroke-width:0.5;"/></g><g id="elem_#12"><polygon fill="#F1F1F1" points="356.2658,310,368.2658,322,356.2658,334,344.2658,322,356.2658,310" style="stroke:#181818;stroke-width:0.5;"/></g><rect fill="#F1F1F1" height="33.9688" rx="12.5" ry="12.5" style="stroke:#181818;stroke-width:0.5;" width="238" x="199.2658" y="393"/><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="218" x="209.2658" y="414.1387">Auftrag der neuen Liste hinzufuegen</text><rect fill="#F1F1F1" height="33.9688" rx="12.5" ry="12.5" style="stroke:#181818;stroke-width:0.5;" width="165" x="388.7658" y="305"/><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="145" x="398.7658" y="326.1387">neue Liste zurckgeben</text><ellipse cx="471.2658" cy="410" fill="none" rx="10" ry="10" style="stroke:#222222;stroke-width:1.0;"/><ellipse cx="471.7658" cy="410.5" fill="#222222" rx="6" ry="6" style="stroke:none;stroke-width:1.0;"/><!--link start to verbleibende Planungszeit ermitteln--><g id="link_start_verbleibende Planungszeit ermitteln"><path d="M321.2658,26.177 C321.2658,35.269 321.2658,49.527 321.2658,61.485 " fill="none" id="start-to-verbleibende Planungszeit ermitteln" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="#181818" points="321.2658,66.791,325.2658,57.791,321.2658,61.791,317.2658,57.791,321.2658,66.791" style="stroke:#181818;stroke-width:1.0;"/></g><!--link verbleibende Planungszeit ermitteln to neue Liste anlegen--><g id="link_verbleibende Planungszeit ermitteln_neue Liste anlegen"><path d="M321.2658,101.201 C321.2658,111.692 321.2658,125.463 321.2658,136.818 " fill="none" id="verbleibende Planungszeit ermitteln-to-neue Liste anlegen" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="#181818" points="321.2658,141.844,325.2658,132.844,321.2658,136.844,317.2658,132.844,321.2658,141.844" style="stroke:#181818;stroke-width:1.0;"/></g><!--link neue Liste anlegen to erhaltene Liste pr?fen--><g id="link_neue Liste anlegen_erhaltene Liste prfen"><path d="M321.2658,176.201 C321.2658,186.692 321.2658,200.463 321.2658,211.818 " fill="none" id="neue Liste anlegen-to-erhaltene Liste prfen" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="#181818" points="321.2658,216.844,325.2658,207.844,321.2658,211.844,317.2658,207.844,321.2658,216.844" style="stroke:#181818;stroke-width:1.0;"/></g><!--link erhaltene Liste pr?fen to #9--><g id="link_erhaltene Liste prfen_#9"><path d="M397.6248,234 C402.3448,234 407.0648,234 411.7838,234 " fill="none" id="erhaltene Liste prfen-to-#9" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="#181818" points="416.9058,234,407.9058,230,411.9058,234,407.9058,238,416.9058,234" style="stroke:#181818;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="164" x="449.1143" y="229.6215">weiterer Auftrag vorhanden?</text></g><!--link #9 to #12--><g id="link_#9_#12"><path d="M424.0158,241.185 C411.5988,255.813 380.3208,292.66 364.9988,310.712 " fill="none" id="#9-to-#12" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="#181818" points="361.4678,314.871,370.3418,310.5987,364.7037,311.0593,364.2431,305.4213,361.4678,314.871" style="stroke:#181818;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="10" x="398.2658" y="282.2104">ja</text><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="330" x="6" y="311.0531">Auftrag ist kurz genug fr die verbleibende Planungszeit?</text></g><!--link #12 to Auftrag der neuen Liste hinzufuegen--><g id="link_#12_Auftrag der neuen Liste hinzufuegen"><path d="M352.8928,330.6319 C347.2808,343.3324 335.8338,369.2408 327.6358,387.7926 " fill="none" id="#12-to-Auftrag der neuen Liste hinzufuegen" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="#181818" points="325.4178,392.813,332.7136,386.197,327.4383,388.2394,325.3959,382.9642,325.4178,392.813" style="stroke:#181818;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="10" x="341.2658" y="370.2104">ja</text></g><!--link Auftrag der neuen Liste hinzufuegen to erhaltene Liste pr?fen--><g id="link_Auftrag der neuen Liste hinzufuegen_erhaltene Liste prfen"><path d="M318.5418,392.9812 C319.0838,361.5483 320.2868,291.78 320.8958,256.425 " fill="none" id="Auftrag der neuen Liste hinzufuegen-to-erhaltene Liste prfen" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="#181818" points="320.9888,251.058,316.8351,259.9881,320.9031,256.0573,324.8339,260.1253,320.9888,251.058" style="stroke:#181818;stroke-width:1.0;"/></g><!--link #12 to erhaltene Liste pr?fen--><g id="link_#12_erhaltene Liste prfen"><path d="M353.0738,313.157 C347.8708,300.373 337.3908,274.622 329.8808,256.17 " fill="none" id="#12-to-erhaltene Liste prfen" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="#181818" points="327.8488,251.176,327.538,261.02,329.7343,255.8069,334.9474,258.0032,327.8488,251.176" style="stroke:#181818;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="25" x="342.2658" y="282.2104">nein</text></g><!--link #9 to neue Liste zur?ckgeben--><g id="link_#9_neue Liste zurckgeben"><path d="M432.9928,242.632 C439.2478,255.441 452.0648,281.685 461.1398,300.267 " fill="none" id="#9-to-neue Liste zurckgeben" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="#181818" points="463.3598,304.813,463.0044,294.9706,461.1655,300.3202,455.8159,298.4814,463.3598,304.813" style="stroke:#181818;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="25" x="453.2658" y="282.2104">nein</text></g><!--link neue Liste zur?ckgeben to end--><g id="link_neue Liste zurckgeben_end"><path d="M471.2658,339.1759 C471.2658,355.1231 471.2658,379.2657 471.2658,394.5766 " fill="none" id="neue Liste zurckgeben-to-end" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="#181818" points="471.2658,399.7863,475.2658,390.7863,471.2658,394.7863,467.2658,390.7863,471.2658,399.7863" style="stroke:#181818;stroke-width:1.0;"/></g><!--SRC=[XP2nRi9044Hxlc941KZy0YXzYlGHnHdFdHSk6xI-0ydVHiUFvOpj224XsfsdDwDTh9RhzUPCgoAbejR4LtIQDRGT9O7s8oanwgnWoanyIfi8fv61zokjSJ5Hf-Xajwlleurup2_5GgFX8_jaBk3yM_ZIezivKZCelWzkXvwUe2B2CLk7qf43_Ergg8LuEVHPDedgqjpFrd5S8zfbdzzjPsnvTob5TtY9QIt-7yP8FxofouNS7mEheG-BrV8F]--></g></svg>
\ No newline at end of file
<?xml version="1.0" encoding="windows-1252" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" height="357px" preserveAspectRatio="none" style="width:637px;height:357px;background:#FFFFFF;" version="1.1" viewBox="0 0 637 357" width="637px" zoomAndPan="magnify"><defs/><g><ellipse cx="282.8776" cy="16" fill="#222222" rx="10" ry="10" style="stroke:none;stroke-width:1.0;"/><rect fill="#F1F1F1" height="33.9688" rx="12.5" ry="12.5" style="stroke:#181818;stroke-width:0.5;" width="295" x="135.3776" y="67"/><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="275" x="145.3776" y="88.1387">ersten Auftrag der erhaltenen Liste speichern</text><rect fill="#F1F1F1" height="33.9688" rx="12.5" ry="12.5" style="stroke:#181818;stroke-width:0.5;" width="152" x="206.8776" y="142"/><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="132" x="216.8776" y="163.1387">erhaltene Liste prfen</text><g id="elem_#7"><polygon fill="#F1F1F1" points="390.8776,147,402.8776,159,390.8776,171,378.8776,159,390.8776,147" style="stroke:#181818;stroke-width:0.5;"/></g><g id="elem_#10"><polygon fill="#F1F1F1" points="317.8776,235,329.8776,247,317.8776,259,305.8776,247,317.8776,235" style="stroke:#181818;stroke-width:0.5;"/></g><rect fill="#F1F1F1" height="33.9688" rx="12.5" ry="12.5" style="stroke:#181818;stroke-width:0.5;" width="256" x="166.8776" y="318"/><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="236" x="176.8776" y="339.1387">aktuell gespeicherten Auftrag ersetzen</text><rect fill="#F1F1F1" height="33.9688" rx="12.5" ry="12.5" style="stroke:#181818;stroke-width:0.5;" width="281" x="350.3776" y="230"/><text fill="#000000" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="261" x="360.3776" y="251.1387">aktuell gespeicherten Auftrag zurckgeben</text><ellipse cx="490.8776" cy="335" fill="none" rx="10" ry="10" style="stroke:#222222;stroke-width:1.0;"/><ellipse cx="491.3776" cy="335.5" fill="#222222" rx="6" ry="6" style="stroke:none;stroke-width:1.0;"/><!--link start to ersten Auftrag der erhaltenen Liste speichern--><g id="link_start_ersten Auftrag der erhaltenen Liste speichern"><path d="M282.8776,26.177 C282.8776,35.269 282.8776,49.527 282.8776,61.485 " fill="none" id="start-to-ersten Auftrag der erhaltenen Liste speichern" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="#181818" points="282.8776,66.791,286.8776,57.791,282.8776,61.791,278.8776,57.791,282.8776,66.791" style="stroke:#181818;stroke-width:1.0;"/></g><!--link ersten Auftrag der erhaltenen Liste speichern to erhaltene Liste pr?fen--><g id="link_ersten Auftrag der erhaltenen Liste speichern_erhaltene Liste prfen"><path d="M282.8776,101.201 C282.8776,111.692 282.8776,125.463 282.8776,136.818 " fill="none" id="ersten Auftrag der erhaltenen Liste speichern-to-erhaltene Liste prfen" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="#181818" points="282.8776,141.844,286.8776,132.844,282.8776,136.844,278.8776,132.844,282.8776,141.844" style="stroke:#181818;stroke-width:1.0;"/></g><!--link erhaltene Liste pr?fen to #7--><g id="link_erhaltene Liste prfen_#7"><path d="M359.2366,159 C363.9566,159 368.6766,159 373.3956,159 " fill="none" id="erhaltene Liste prfen-to-#7" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="#181818" points="378.5176,159,369.5176,155,373.5176,159,369.5176,163,378.5176,159" style="stroke:#181818;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="164" x="410.7261" y="154.6215">weiterer Auftrag vorhanden?</text></g><!--link #7 to #10--><g id="link_#7_#10"><path d="M385.6276,166.185 C373.2106,180.813 341.9326,217.66 326.6106,235.712 " fill="none" id="#7-to-#10" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="#181818" points="323.0796,239.871,331.9536,235.5987,326.3156,236.0593,325.855,230.4213,323.0796,239.871" style="stroke:#181818;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="10" x="359.8776" y="207.2104">ja</text><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="292" x="6" y="235.8145">Auftrag ist besser als aktuell gespeicherter Auftrag</text></g><!--link #10 to aktuell gespeicherten Auftrag ersetzen--><g id="link_#10_aktuell gespeicherten Auftrag ersetzen"><path d="M315.5026,256.8796 C311.9986,269.9816 305.3396,294.879 300.5346,312.8466 " fill="none" id="#10-to-aktuell gespeicherten Auftrag ersetzen" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="#181818" points="299.2326,317.715,305.4225,310.0543,300.5247,312.8848,297.6942,307.987,299.2326,317.715" style="stroke:#181818;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="10" x="308.8776" y="295.2104">ja</text></g><!--link aktuell gespeicherten Auftrag ersetzen to erhaltene Liste pr?fen--><g id="link_aktuell gespeicherten Auftrag ersetzen_erhaltene Liste prfen"><path d="M293.7726,317.9812 C291.6046,286.5483 286.7926,216.78 284.3546,181.425 " fill="none" id="aktuell gespeicherten Auftrag ersetzen-to-erhaltene Liste prfen" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="#181818" points="283.9846,176.058,280.6138,185.312,284.3289,181.0461,288.5948,184.7612,283.9846,176.058" style="stroke:#181818;stroke-width:1.0;"/></g><!--link #10 to erhaltene Liste pr?fen--><g id="link_#10_erhaltene Liste prfen"><path d="M314.6856,238.157 C309.4826,225.373 299.0026,199.622 291.4926,181.17 " fill="none" id="#10-to-erhaltene Liste prfen" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="#181818" points="289.4606,176.176,289.1498,186.02,291.3461,180.8069,296.5592,183.0032,289.4606,176.176" style="stroke:#181818;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="25" x="303.8776" y="207.2104">nein</text></g><!--link #7 to aktuell gespeicherten Auftrag zur?ckgeben--><g id="link_#7_aktuell gespeicherten Auftrag zurckgeben"><path d="M396.8336,165.122 C410.5856,176.949 445.1466,206.672 468.3176,226.599 " fill="none" id="#7-to-aktuell gespeicherten Auftrag zurckgeben" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="#181818" points="472.2186,229.953,468.0023,221.0523,468.4274,226.6932,462.7865,227.1183,472.2186,229.953" style="stroke:#181818;stroke-width:1.0;"/><text fill="#000000" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="25" x="446.8776" y="207.2104">nein</text></g><!--link aktuell gespeicherten Auftrag zur?ckgeben to end--><g id="link_aktuell gespeicherten Auftrag zurckgeben_end"><path d="M490.8776,264.1759 C490.8776,280.1231 490.8776,304.2657 490.8776,319.5766 " fill="none" id="aktuell gespeicherten Auftrag zurckgeben-to-end" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="#181818" points="490.8776,324.7863,494.8776,315.7863,490.8776,319.7863,486.8776,315.7863,490.8776,324.7863" style="stroke:#181818;stroke-width:1.0;"/></g><!--SRC=[VOz13e8m44NtdcB22aYu0iQzDp0i2ln2fQca7JJXREwyc2E294RPpd__pKoS9bcMmmU6eyEWsQk6QdY2RvMLeOoFHb8AFKpLmhlTL9ZJEUpzuwuXKMuqHJSOXXV98hnUX7Srt3uYRZ4fJcTLq8jU815HYH2afsmWrV40Qwd1ifbNuvVc5xk-8z-1HsmU3XimwHoCA-WlvcgZFp3DzFRwSP1orJKetoTGd2PF]--></g></svg>
\ No newline at end of file
Semester_2/Einheit_03/Pics/Komplexitaetsklassen.png

78.4 KiB

%% Cell type:markdown id:622edf27 tags:
# <font color='blue'>**Übung 3 - Greedy-Algorithmus**</font>
## <font color='blue'>**Problemstellung: Belegungsplan für eine Fertigungsmaschine**</font>
### <font color='blue'>**Problembeschreibung**</font>
Es soll eine Software für eine Fertigungsmaschine entwickelt werden, die aus einer Liste von Aufträgen eine möglichst gewinnbringende Auswahl zusammenstellt. Diese Auswahl muss in einem gegebenen Zeitraum abgearbeitet werden können. Die Aufträge sollen folgende Informationen enthalten:
* Name
* Wert
* Dauer
Die Software muss nicht zwingend die beste Auswahl finden.
### <font color='blue'>**Modellbildung**</font>
Diese Problemstellung lässt sich auf das typische "Rucksackproblem" zurückführen. Dabei entspricht der Belegungsplan mit begrenzter Zeit dem Rucksack mit begrenztem Gewicht. Die Aufträge entsprechen den Gegenständen und haben ebenfalls einen Wert. Statt Gewicht haben sie eine Dauer.
Da nicht zwingend die beste Auswahl gefunden werden muss, kann der schnelle Greedy-Algorithmus eingesetzt werden, bei dem der Belegungsplan immer um den nächstbesten zeitlich noch passenden Auftrag erweitert wird. Das Kriterium für den "besten" Auftrag muss dabei definiert werden. Für die Problemstellung ist der Wert pro Zeit ein vielversprechendes Kriterium.
### <font color='blue'>**Algorithmierung**</font>
Die Aufträge werden als einfache Klasse mit den o.g. Attributen umgesetzt. Dabei erhalten sie die `__init__()`-Methode. Die Klasse Auftrag erhält außerdem eine Implementierung der `__repr__()`-Methode. Wenn diese Methode definiert ist, können Objekte der entsprechenden Klasse mit `print(objekt)` verwendet werden, wobei der in der Methode zusammengestellte String ausgegeben wird.
Eine weitere Klasse `Maschinenbelegungsplan` verwaltet die Auswahl der Aufträge. Als Attribut hat sie die maximale Planungsdauer `planungsdauer` und eine Liste mit den geplanten Aufträgen. Diese Liste wird von zwei Methoden verwaltet: `zurücksetzen()` (leert die Liste) und `hinzufuegen()`, die der Liste den übergebenen Auftrag hinzufügt, aber nur, wenn dieser zeitlich noch in die verbleibende Zeit passt. Die `__init__()`-Methode setzt die maximale Plandauer. Auch diese Klasse soll eine implementierung von `__repr__()` erhalten, die alle geplanten Aufträge, sowie die Gesamtdauer und Gesamtzeit ausgibt. Die Methoden `get_gesamtwert()` und `get_gesamtdauer()` stellen die entsprechenden Informationen zur Verfügung, die dadurch ermittelt werden, dass über die geplante Liste iteriert und das entsprechende Attribut aller Aufträge zusammengezählt wird.
Die zentrale Methode `greedy_planen()` erhält eine Liste mit möglichen Aufträgen. Diese Liste wird zunächst kopiert, damit Änderungen in der Liste keine Auswirkungen außerhalb der Methode haben. Nun muss beim Greedy-Algorithmus immer ein Auftrag den geplanten Aufträgen hinzugefügt werden, solange noch Aufträge verfügbar sind, die in die verbleibende Zeit passen. Dazu wird die kopierte Liste gefiltert, sodass sie nur noch Aufträge enthält, die eine kürzere Auftragsdauer als die verbleibende Zeit haben. Dies wird an eine Hilfmethode `_auftraege_filtern()` (wird später beschrieben) ausgelagert. Das vorangestellt `_` signalisiert, dass diese Methode nur von der Klasse selbst, nicht aber "von außen" verwendet werden soll. Anschließend beginnt eine Schleife, solange noch Aufträge in der gefilterten Liste vorhanden sind. Der beste Auftrag wird ausgewählt (ausgelagert an die Methode `_waehlen()`, die später beschrieben wird). Der ausgewählte Auftrag wird der Liste der geplanten Aufträge hinzugefügt, und aus der Liste zu planender Aufträge entfernt (dazu steht die Listenmethode `remove(element)`zur Verfügung). Anschließend wird die Liste wieder gefiltert, da sich die verbleibende Zeit geändert hat. Sobald kein Auftrag mehr in der Liste zu planender Aufträge vorhanden ist, ist der Algorithmus beendet. Die Auswahl ist im Attribut `geplante_auftraege` gespeichert und verfügbar. Die Methode `greedy_planen()` ist hier in einem Aktivitätsdiagramm skizziert:
%% Cell type:markdown id:07ceadb0 tags:
![](Pics/greedy_algo.svg)
%% Cell type:markdown id:a6e4c8cd tags:
In der internen Hilfmethode `_filter_auftraege()` wird eine leere Liste angelegt. Anschließend wird jeder Auftrag in der übergebenen Liste darauf geprüft, ob die Dauer kürzer als die verbleibende Zeit (maximale Zeit abzüglich der bereits geplanten Aufträge) ist. Falls ja, wird der Auftrag an der neuen Liste hinzugefügt. Zum Schluss wird die gefüllte Liste zurückgegeben. Die Methode `_filter_auftraege()` ist hier in einem Aktivitätsdiagramm skizziert:
%% Cell type:markdown id:e5c67335 tags:
![](Pics/Greedy_filter.svg)
%% Cell type:markdown id:a96beeb6 tags:
In der internen Hilfsmethode `_waehle()` wird der erste Auftrag aus der übergebenen Liste zwischengespeichert. Anschließend wird jeder weitere Auftrag der Liste daraufhin geprüft, ob er "besser" als der aktuell gespeicherte Auftrag ist. Bei unserem gewählten Kriterium wird dazu der Quotient aus Wert und Dauer verglichen. Falls der neue Auftrag "besser" ist, dann wird der neue Auftrag anstelle des gespeicherten Auftrags gespeichert. Sind alle Aufträge geprüft ist der "beste" Auftrag gespeichert und kann zurückgegeben werden. Die Methode `_waehle()` ist hier in einem Aktivitätsdiagramm skizziert:
%% Cell type:markdown id:2c705209 tags:
![](Pics/Greedy_waehlen.svg)
%% Cell type:markdown id:54a0eed7 tags:
Die gesamte Klassenstruktur in einem UML-Diagramm:
%% Cell type:markdown id:f3742ac0 tags:
![](Pics/Greedy.svg)
%% Cell type:markdown id:cc7329cb tags:
### <font color='blue'>**Umsetzung**</font>
Auftrag:
%% Cell type:code id:30263737 tags:
``` python
class Auftrag:
def __init__(self, "?"):
"?"
def __repr__(self):
return self.name + ", Wert: " + "?"
```
%% Cell type:markdown id:0f2aa2d6 tags:
Maschinenbelegungsplan:
%% Cell type:code id:694c2e0b tags:
``` python
class Maschinenbelegungsplan:
def __init__(self, planungsdauer):
"?"
def get_gesamtwert(self):
"?"
def get_gesamtdauer(self):
"?"
def zuruecksetzen(self):
"?"
def hinzufuegen(self, auftrag):
"?"
def _filter_auftraege(self, auftraege):
"?"
def _waehle(self, auftraege):
"?"
def greedy_planen(self, ext_auftraege):
"?"
def __repr__(self):
ausgabe = "Der Maschinenbelegungsplan enthaelt:\n"
for auftrag in self.geplante_auftraege:
ausgabe += str(auftrag)
ausgabe += "Gesamtwert: " + str(self.get_gesamtwert()) + "\nGesamtdauer: " + str(self.get_gesamtdauer()) + "\n"
return ausgabe
```
%% Cell type:markdown id:188af0d7 tags:
### <font color='blue'>**Anwendung**</font>
In diesem Test kann die Implementierung ohne den Greedy-Algorithmus getestet werden. Die Methoden `greedy_planen()`, `_filter_auftraege()` und `_waehle()` werden noch nicht verwendet. Erweitere den Test gerne um mehreren Aufträgen.
%% Cell type:code id:b528354b tags:
``` python
A1 = Auftrag("Beispielauftrag", 50, 20)
print(A1)
masch1 = Maschinenbelegungsplan(100)
masch1.hinzufuegen(A1)
print(masch1)
masch1.zuruecksetzen()
print(masch1)
```
%% Cell type:markdown id:5bb0b4ff tags:
In diesem Test wird der Greedy Algorithmus mit einer Reihe von Aufträgen geprüft. Füge gerne weitere Aufträge hinzu odere ändere die Daten, um verschiedene Ergebnisse zu ermitteln.
%% Cell type:code id:649ac8f6 tags:
``` python
# Bei dem folgenden Test und dem Kriterium Wert/Zeit sollte das Ergebnis die Auftraege 2,4,5,6 enthalten.
A1 = Auftrag("Auftrag 1", 40, 100)
A2 = Auftrag("Auftrag 2", 35, 50)
A3 = Auftrag("Auftrag 3", 18, 45)
A4 = Auftrag("Auftrag 4", 4, 20)
A5 = Auftrag("Auftrag 5", 10, 10)
A6 = Auftrag("Auftrag 6", 2, 5)
a_liste = [A1, A2, A3, A4, A5, A6]
masch1.zuruecksetzen()
masch1.greedy_planen(a_liste)
print(masch1)
```
%% Cell type:markdown id:c0846c79 tags:
### <font color='blue'>**Anregungen zum selbst Programmieren:**</font>
* Beim Greedy-Algorithmus können verschiedene Bewertungskriterien zum Einsatz kommen, z.B. der höchste Wert und die kleinste Dauer (wir verwenden bisher Wert/Dauer). Füge mehrere Abwandlungen der `_waehle()` Methode hinzu, die nach diesen anderen Kriterien sortieren. Das zu verwendende Kriterium soll der Methode `greedy_planen()` als zusätzliches Argument übergeben werden. Abhängig von diesem Argument soll eine entsprechende `_waehle()`-Methode verwendet werden. Vergleiche die erhaltenen Auswahlen mit dem "Wert pro Dauer"-Kriterium.
* Verbinde das Ergebnis dieser Übung mit der letzten Übung, sodass die Maschine die Warteschlange entsprechend der Ergebnisse des Greedy-Algorithmus plant. Überlege dir dazu eine Programmarchitektur (z.B. ob der Belegungsplan innerhalb der Maschine gespeichert sein soll). Hier gibt es viele verschiedene Möglichkeiten. Teilweise sind Anpassungen erforderlich (z.B. da wir in dieser Übung den Aufträgen nur die für den Algorithmus relevanten Informationen gegeben haben. In der anderen Übung waren mehr Daten erforderlich).
%% Cell type:markdown id:622edf27 tags:
# <font color='blue'>**Übung 3 - Greedy-Algorithmus**</font>
## <font color='blue'>**Problemstellung: Belegungsplan für eine Fertigungsmaschine**</font>
### <font color='blue'>**Problembeschreibung**</font>
Es soll eine Software für eine Fertigungsmaschine entwickelt werden, die aus einer Liste von Aufträgen eine möglichst gewinnbringende Auswahl zusammenstellt. Diese Auswahl muss in einem gegebenen Zeitraum abgearbeitet werden können. Die Aufträge sollen folgende Informationen enthalten:
* Name
* Wert
* Dauer
Die Software muss nicht zwingend die beste Auswahl finden.
### <font color='blue'>**Modellbildung**</font>
Diese Problemstellung lässt sich auf das typische "Rucksackproblem" zurückführen. Dabei entspricht der Belegungsplan mit begrenzter Zeit dem Rucksack mit begrenztem Gewicht. Die Aufträge entsprechen den Gegenständen und haben ebenfalls einen Wert. Statt Gewicht haben sie eine Dauer.
Da nicht zwingend die beste Auswahl gefunden werden muss, kann der schnelle Greedy-Algorithmus eingesetzt werden, bei dem der Belegungsplan immer um den nächstbesten zeitlich noch passenden Auftrag erweitert wird. Das Kriterium für den "besten" Auftrag muss dabei definiert werden. Für die Problemstellung ist der Wert pro Zeit ein vielversprechendes Kriterium.
### <font color='blue'>**Algorithmierung**</font>
Die Aufträge werden als einfache Klasse mit den o.g. Attributen umgesetzt. Dabei erhalten sie die `__init__()`-Methode. Die Klasse Auftrag erhält außerdem eine Implementierung der `__repr__()`-Methode. Wenn diese Methode definiert ist, können Objekte der entsprechenden Klasse mit `print(objekt)` verwendet werden, wobei der in der Methode zusammengestellte String ausgegeben wird.
Eine weitere Klasse `Maschinenbelegungsplan` verwaltet die Auswahl der Aufträge. Als Attribut hat sie die maximale Planungsdauer `planungsdauer` und eine Liste mit den geplanten Aufträgen. Diese Liste wird von zwei Methoden verwaltet: `zurücksetzen()` (leert die Liste) und `hinzufuegen()`, die der Liste den übergebenen Auftrag hinzufügt, aber nur, wenn dieser zeitlich noch in die verbleibende Zeit passt. Die `__init__()`-Methode setzt die maximale Plandauer. Auch diese Klasse soll eine implementierung von `__repr__()` erhalten, die alle geplanten Aufträge, sowie die Gesamtdauer und Gesamtzeit ausgibt. Die Methoden `get_gesamtwert()` und `get_gesamtdauer()` stellen die entsprechenden Informationen zur Verfügung, die dadurch ermittelt werden, dass über die geplante Liste iteriert und das entsprechende Attribut aller Aufträge zusammengezählt wird.
Die zentrale Methode `greedy_planen()` erhält eine Liste mit möglichen Aufträgen. Diese Liste wird zunächst kopiert, damit Änderungen in der Liste keine Auswirkungen außerhalb der Methode haben. Nun muss beim Greedy-Algorithmus immer ein Auftrag den geplanten Aufträgen hinzugefügt werden, solange noch Aufträge verfügbar sind, die in die verbleibende Zeit passen. Dazu wird die kopierte Liste gefiltert, sodass sie nur noch Aufträge enthält, die eine kürzere Auftragsdauer als die verbleibende Zeit haben. Dies wird an eine Hilfmethode `_auftraege_filtern()` (wird später beschrieben) ausgelagert. Das vorangestellt `_` signalisiert, dass diese Methode nur von der Klasse selbst, nicht aber "von außen" verwendet werden soll. Anschließend beginnt eine Schleife, solange noch Aufträge in der gefilterten Liste vorhanden sind. Der beste Auftrag wird ausgewählt (ausgelagert an die Methode `_waehlen()`, die später beschrieben wird). Der ausgewählte Auftrag wird der Liste der geplanten Aufträge hinzugefügt, und aus der Liste zu planender Aufträge entfernt (dazu steht die Listenmethode `remove(element)`zur Verfügung). Anschließend wird die Liste wieder gefiltert, da sich die verbleibende Zeit geändert hat. Sobald kein Auftrag mehr in der Liste zu planender Aufträge vorhanden ist, ist der Algorithmus beendet. Die Auswahl ist im Attribut `geplante_auftraege` gespeichert und verfügbar. Die Methode `greedy_planen()` ist hier in einem Aktivitätsdiagramm skizziert:
%% Cell type:markdown id:07ceadb0 tags:
![](Pics/greedy_algo.svg)
%% Cell type:markdown id:a6e4c8cd tags:
In der internen Hilfmethode `_filter_auftraege()` wird eine leere Liste angelegt. Anschließend wird jeder Auftrag in der übergebenen Liste darauf geprüft, ob die Dauer kürzer als die verbleibende Zeit (maximale Zeit abzüglich der bereits geplanten Aufträge) ist. Falls ja, wird der Auftrag an der neuen Liste hinzugefügt. Zum Schluss wird die gefüllte Liste zurückgegeben. Die Methode `_filter_auftraege()` ist hier in einem Aktivitätsdiagramm skizziert:
%% Cell type:markdown id:e5c67335 tags:
![](Pics/Greedy_filter.svg)
%% Cell type:markdown id:a96beeb6 tags:
In der internen Hilfsmethode `_waehle()` wird der erste Auftrag aus der übergebenen Liste zwischengespeichert. Anschließend wird jeder weitere Auftrag der Liste daraufhin geprüft, ob er "besser" als der aktuell gespeicherte Auftrag ist. Bei unserem gewählten Kriterium wird dazu der Quotient aus Wert und Dauer verglichen. Falls der neue Auftrag "besser" ist, dann wird der neue Auftrag anstelle des gespeicherten Auftrags gespeichert. Sind alle Aufträge geprüft ist der "beste" Auftrag gespeichert und kann zurückgegeben werden. Die Methode `_waehle()` ist hier in einem Aktivitätsdiagramm skizziert:
%% Cell type:markdown id:2c705209 tags:
![](Pics/Greedy_waehlen.svg)
%% Cell type:markdown id:54a0eed7 tags:
Die gesamte Klassenstruktur in einem UML-Diagramm:
%% Cell type:markdown id:f3742ac0 tags:
![](Pics/Greedy.svg)
%% Cell type:markdown id:cc7329cb tags:
### <font color='blue'>**Umsetzung**</font>
Auftrag:
%% Cell type:code id:30263737 tags:
``` python
class Auftrag:
def __init__(self, name, wert, dauer):
self.name = name
self.wert = wert
self.dauer = dauer
def __repr__(self):
return self.name + ", Wert: " + str(self.wert) + " Dauer: " + str(self.dauer) +"\n"
```
%% Cell type:markdown id:0f2aa2d6 tags:
Maschinenbelegungsplan:
%% Cell type:code id:694c2e0b tags:
``` python
class Maschinenbelegungsplan:
def __init__(self, planungsdauer):
self.planungsdauer = planungsdauer
self.geplante_auftraege = []
def get_gesamtwert(self):
w = 0
for auftrag in self.geplante_auftraege:
w += auftrag.wert
return w
def get_gesamtdauer(self):
t = 0
for auftrag in self.geplante_auftraege:
t += auftrag.dauer
return t
def zuruecksetzen(self):
self.geplante_auftraege = []
def hinzufuegen(self, auftrag):
if self.planungsdauer - self.get_gesamtdauer() >= auftrag.dauer:
self.geplante_auftraege.append(auftrag)
else:
print("Der Auftrag dauert zu lange für die verbleibende Zeit")
def _filter_auftraege(self, auftraege):
restzeit = self.planungsdauer - self.get_gesamtdauer()
temp = []
for auftrag in auftraege:
if auftrag.dauer <= restzeit:
temp.append(auftrag)
return temp
# Alternative zu temp und expliziter Schleife mit list-comprehension
# return [auftrag for auftrag in auftraege if auftrag.dauer <= restzeit]
def _waehle(self, auftraege):
temp_auswahl = auftraege[0]
for auftrag in auftraege[1:]:
if auftrag.wert / auftrag.dauer > temp_auswahl.wert / temp_auswahl.dauer:
temp_auswahl = auftrag
return temp_auswahl
def greedy_planen(self, ext_auftraege):
auftraege = ext_auftraege.copy()
auftraege = self._filter_auftraege(auftraege)
while len(auftraege)>0:
auswahl = self._waehle(auftraege)
self.hinzufuegen(auswahl)
auftraege.remove(auswahl)
auftraege = self._filter_auftraege(auftraege)
def __repr__(self):
ausgabe = "Der Maschinenbelegungsplan enthaelt:\n"
for auftrag in self.geplante_auftraege:
ausgabe += str(auftrag)
ausgabe += "Gesamtwert: " + str(self.get_gesamtwert()) + "\nGesamtdauer: " + str(self.get_gesamtdauer()) + "\n"
return ausgabe
```
%% Cell type:markdown id:188af0d7 tags:
### <font color='blue'>**Anwendung**</font>
In diesem Test kann die Implementierung ohne den Greedy-Algorithmus getestet werden. Die Methoden `greedy_planen()`, `_filter_auftraege()` und `_waehle()` werden noch nicht verwendet. Erweitere den Test gerne um mehreren Aufträgen.
%% Cell type:code id:b528354b tags:
``` python
A1 = Auftrag("Beispielauftrag", 50, 20)
print(A1)
masch1 = Maschinenbelegungsplan(100)
masch1.hinzufuegen(A1)
print(masch1)
masch1.zuruecksetzen()
print(masch1)
```
%% Output
Beispielauftrag, Wert: 50 Dauer: 20
Der Maschinenbelegungsplan enthaelt:
Beispielauftrag, Wert: 50 Dauer: 20
Gesamtwert: 50
Gesamtdauer: 20
Der Maschinenbelegungsplan enthaelt:
Gesamtwert: 0
Gesamtdauer: 0
%% Cell type:markdown id:5bb0b4ff tags:
In diesem Test wird der Greedy Algorithmus mit einer Reihe von Aufträgen geprüft. Füge gerne weitere Aufträge hinzu odere ändere die Daten, um verschiedene Ergebnisse zu ermitteln.
%% Cell type:code id:649ac8f6 tags:
``` python
# Bei dem folgenden Test und dem Kriterium Wert/Zeit sollte das Ergebnis die Auftraege 2,4,5,6 enthalten.
A1 = Auftrag("Auftrag 1", 40, 100)
A2 = Auftrag("Auftrag 2", 35, 50)
A3 = Auftrag("Auftrag 3", 18, 45)
A4 = Auftrag("Auftrag 4", 4, 20)
A5 = Auftrag("Auftrag 5", 10, 10)
A6 = Auftrag("Auftrag 6", 2, 5)
a_liste = [A1, A2, A3, A4, A5, A6]
masch1.zuruecksetzen()
masch1.greedy_planen(a_liste)
print(masch1)
```
%% Output
Der Maschinenbelegungsplan enthaelt:
Auftrag 5, Wert: 10 Dauer: 10
Auftrag 2, Wert: 35 Dauer: 50
Auftrag 6, Wert: 2 Dauer: 5
Auftrag 4, Wert: 4 Dauer: 20
Gesamtwert: 51
Gesamtdauer: 85
%% Cell type:markdown id:c0846c79 tags:
### <font color='blue'>**Anregungen zum selbst Programmieren:**</font>
* Beim Greedy-Algorithmus können verschiedene Bewertungskriterien zum Einsatz kommen, z.B. der höchste Wert und die kleinste Dauer (wir verwenden bisher Wert/Dauer). Füge mehrere Abwandlungen der `_waehle()` Methode hinzu, die nach diesen anderen Kriterien sortieren. Das zu verwendende Kriterium soll der Methode `greedy_planen()` als zusätzliches Argument übergeben werden. Abhängig von diesem Argument soll eine entsprechende `_waehle()`-Methode verwendet werden. Vergleiche die erhaltenen Auswahlen mit dem "Wert pro Dauer"-Kriterium.
* Verbinde das Ergebnis dieser Übung mit der letzten Übung, sodass die Maschine die Warteschlange entsprechend der Ergebnisse des Greedy-Algorithmus plant. Überlege dir dazu eine Programmarchitektur (z.B. ob der Belegungsplan innerhalb der Maschine gespeichert sein soll). Hier gibt es viele verschiedene Möglichkeiten. Teilweise sind Anpassungen erforderlich (z.B. da wir in dieser Übung den Aufträgen nur die für den Algorithmus relevanten Informationen gegeben haben. In der anderen Übung waren mehr Daten erforderlich).
%% Cell type:markdown id:e7ac2738-e0bb-4c7f-a03f-10fa7ec5b71e tags:
# <font color='blue'>**Probabilistische Algorithmen**</font>
## **<font color='blue'>Zufallszahlen</font>**
Die Abbildung der Zufälligkeit in einem Algorithmus wird durch die Nutzung von **Zufallszahlen** realisiert. Leider sind Zufallszahlen auf einem **deterministischen Computer** nicht wirklich zufällig, deshalb spricht man auch von Pseudo-Zufallszahlen.
Üblicherweise werden Zufallszahlen in Form von Folgen generiert, deren Wertebereich durch die verwendeten Datentypen begrenzt ist.
Bei gleichen **Startbedingungen** ergeben sich jedoch immer die gleichen Zahlenfolgen.
Solche Zahlenfolgen sollten grundsätzlich immer im Hinblick auf ihre Verteilung - z.B. Gleichverteilung - überprüft werden.
### **<font color='blue'>Lineares Modulo-Kongruenzverfahren}</font>**
Ein Standardverfahren zur Bestimmung von Zufallszahlen
ist das lineare Modulo-Kon\-gru\-enz\-verfahren (LCM).
Hier ist die Zahlenfolge durch die Rekursion
\begin{equation}
\begin{split}
x _{n+1} \;=\; & (\;a \; x_n + c \;) \;\text{mod}\; m \\
&\text{mit} \;\; m > 0\;, \;\;\; 2<a<m \;\;
\text{und}\;\; 0 \leq c < m \;\;\;
\text{sowie}\;\;\; m, a, c \in \mathbb{N}
\end{split}
\end{equation}
definiert. Die aus einem Startwert $x_0$
entstehenden Folgen besitzen die Periodenlänge $m$
und enthalten die Zahlen $ 0, ..., m-1 $.
Oftmals wird $ m = 2^b $ gesetzt, wobei $b$ die
Wortlänge des verwendeten Datentyps ist.
Als wichtige Bedingungen für eine Gleichverteilung
dürfen $c$ und $m$ keine gemeinsamen Primfaktoren haben,
was durch die Wahl einer großen Primzahl entweder
für $c$ oder für $m$ gewährleistet wird,
und $a-1$ muss ein Vielfaches des Produkts der Primfaktoren von $m$ sein.
Als einfaches Beispiel sei hier die Folge angegeben für
\begin{equation}
\begin{split}
a = 13 = & (2 \cdot 2 \cdot 3+1) , \;\; c = 1, \;\;
m = 16 = (2 \cdot 2 \cdot 2 \cdot 2) \;\;\;
\text{und}\;\;\; x_0 = 0 \;\; : \\ \\
\rightarrow \;\;\; & 0, 1, 14, 7, 12, 13, 10, 3, 8, 9, 6, 15, 4, 5, 2, 11,\;\;\; 0, 1, 14, 7, \; ... \\
\end{split}
\end{equation}
Man erkennt die Periodizität nach $m=16$ Zahlen und
die Tatsache, dass jede Zahl des Wertebereichs
je Peridode genau einmal auftritt.
Eine Klassen-Implementierung könnte wie folgt mit $a = 526 \; (=3*5*5*7+1)$, $c = 121441 $ (Primzahl) und $m = 7441875 \; (=3*3*3*3*3*5*5*5*5*7*7)$
aussehen:
%% Cell type:code id:4900139a-9b1e-4ea0-a06b-7c1a75d55f1a tags:
``` python
class Random:
def __init__( self, seed, m=526, a=121441, c=7441875 ):
self.current = seed
self.m = m
self.a = a
self.c = c
def random(self):
self.current = ( self.a * self.current + self.c ) % self.m
return self.current / self.m
rand = Random(0,m=16,a=13,c=1,)
[rand.random() for i in range(20)]
```
%% Output
[0.0625,
0.875,
0.4375,
0.75,
0.8125,
0.625,
0.1875,
0.5,
0.5625,
0.375,
0.9375,
0.25,
0.3125,
0.125,
0.6875,
0.0,
0.0625,
0.875,
0.4375,
0.75]
%% Cell type:markdown id:e4cb460b-a786-48c7-8b4c-9ea8e006b406 tags:
Für viele Programmiersprachen stehen aber auch Bibliotheksfunktionen zur Verfügung.
Die **Python-Funktion** `random.seed(a=None)` initialisiert den
Zufallszahlengenerator mit dem seed-Wert `a`, während der wiederholte Aufruf von
random.random() die eigentliche Folge liefert.
%% Cell type:code id:9617f503-227d-488b-b20b-529bb1f2d506 tags:
``` python
import random
random.seed(a=0)
print( [random.random() for i in range(10)] )
```
%% Output
[0.8444218515250481, 0.7579544029403025, 0.420571580830845, 0.25891675029296335, 0.5112747213686085, 0.4049341374504143, 0.7837985890347726, 0.30331272607892745, 0.4765969541523558, 0.5833820394550312]
%% Cell type:markdown id:dfe9c6f0-fef8-4359-8b4b-79be71e091f1 tags:
Möchte man Zufallszahlen im Intervall $[a,b)$ erzeugen, so empfiehlt es sich die Operation
x = a + (b-a) * random()
auszuführen. Wichtig ist hierbei,
dass bei den Operationen alle Operanden
Gleitkommazahlen sind.
%% Cell type:code id:41915ab5-da68-403b-8bb2-f6120aeb24e1 tags:
``` python
import random
a = 10.
b = 20.
print( [ a + (b-a) * random.random() for i in range(10) ] )
```
%% Output
[16.88772796878589, 19.093176751828867, 16.974452210201353, 12.212161941402083, 18.79117350101749, 16.206988141223086, 11.331642201163177, 15.49033482059635, 18.920116396859395, 18.997928139729595]
%% Cell type:markdown id:d024bdde-545c-4c2a-916f-ab80e3717c1e tags:
## **<font color='blue'>Optimierung</font>**
Optimierungsprobleme werden mittels Monte-Carlo-Methoden durch die Vorgabe der Entwurfs- oder Entscheidungsparameter - zusammengefaßt im Vektor $ X = [x_1, x_2, ... , x_n] $ - über Zufallszahlen behandelt.
In welchem Bereich die einzelnen Variablen $x_i$ zufällig gewählt werden hängt natürlich von der Problemstellung ab.
Varianten ergeben sich auch dadurch, dass die Zufallszahlen für $x_i$ aus dem gesamten Wertebereich gewählt werden können oder dass auf einen vorhandenen Wert für $x_i$ eine zufällige Abweichung $\Delta x_i$ aufaddiert wird.
Für jeden zufälligen Vektor $X$ werden dann die Ziel- bzw. Gütefunktion $Q(X)$ bestimmt und die Gleichheit- und Ungleichheitsrestriktionen in der Form:
\begin{equation}
R(X) = 0 \qquad \text{und} \qquad U(X) > 0
\end{equation}
im Hinblick auf die Zulässigkeit des Vektors $X$ ausgewertet.
Beispielsweise kann bei einem zu optimierenden Bauteil die Zielfunktion die Masse sein und die Restriktionen das Einhalten der zulässigen Spannungen beschreiben.
Aus dem betrachteten Satz an zufälligen Vektoren $X$ wird abschließend die Lösung herausgesucht, die das Gütekriterien unter (bestmöglicher) Einhaltung der Restriktionen am besten erfüllt.
Gegebenenfalls kann die so gefundene Lösung Ausgangspunkt weiterer Verfahren (z.B. lokale Suche) sein oder auch einer Wiederholung des Verfahrens, wenn ein Startvektor durch zufällig gewählte $\Delta x_i$ modifiziert wird.
Diese Methodik eignet sich bei schwierig zu differenzierenden Gütefunktionen, bei denen andere, z.B. gradientenbasierte Verfahren versagen würden.
Darüberhinaus ist sie in der Lage globale Minima oder Maxima zumindest annähernd zu finden. Wichtig ist dabei eine ausreichend große Anzahl an Zufallsvektoren, um die Wahrscheinlichkeit einer richtigen Lösung zu erhöhen.
%% Cell type:markdown id:3e3d1714-626d-4be4-a351-7742b86f4d2c tags:
## **<font color='blue'>Genetische Verfahren</font>**
Genetische Verfahren versuchen, die Schritte einer sukzessiven, stochastischen Veränderung der Vektoren $X$ zur Findung von verbesserten Lösungen am Ablauf der biologischen Evolution zu orientieren.
Daher finden sich die Begrifflichkeiten der Evolutionbiologie bei den Elementen der genetischen Verfahren wieder.
Der Vektor $X$ wird **Genom** eines Individuum bezeichnet, seine Elemente als **Chromosomen**.
* **Population**, Menge möglicher Lösungen
* **Chromosom**, eine mögliche Lösung für ein Individuum
* **Genotyp**, Elemente enthalten in Chromosomen
* **Phenotyp**, Wert eines Gentyps
![](./Pics/Chromosom.png)
Die Mischung der Genome verschiedener Individuen (Eltern) wird als **Rekombination** bzw. **Crossover** bezeichnet und führt zum Generieren von Kindern.
Die zufällige Veränderung eines Genoms erfolgt hier im Rahmen der **Mutation**.
Welche Individuen ausgewählt werden, um Kinder zu erzeugen oder um am nächsten Entwicklungsschritt teilzunehmen, wird bei einer **Selektion** entschieden.
### **<font color='blue'>Genereller Ablauf</font>**
Der schematische Gesamtablauf beginnt mit einer ersten Population, einer Anzahl an Individuen, deren Chromosome initialisiert, d.h. mit zufälligen Werten belegt und bewertet werden.
Aus dieser Population werden Individuen als Eltern für eine Rekombination selektiert.
Die Rekombination liefert die Kindergeneration, aus der einige Individuen einer Mutation unterworfen werden.
Der Bewertung der neuen Individuen folgt das Zusammenführen mit der Elterngeneration.
Ein weitere Selektion reduziert diese vergrößerte Population auf die Anzahl der Individuen der Ausgangspopulation, indem die am schlechtesten bewerteten Individuen entfernt werden.
Diese Population dient dann als Ausgang für den nächsten Evolutionsschritt, der wieder mit einer Selektion von Eltern beginnt.
Dieser Evolutionsprozess ist iterativ und wird fortgeführt bis ein Abbruchkriterium erfüllt wird (maximale Zahl der Schritte, keine Verbesserung der Lösung).
![](./Pics/Ablauf-GA.svg)
### **<font color='blue'>Verfahrenselemente</font>**
Die genetischen Verfahren gehen zum einen auf die
**Evolutionsstrategien** von Rechenberg und
zum anderen auf die **genetische Algorithmen** von John Henry Holland zurück.
Mit Evolutionsstrategien wurden ursprünglich ingenieurtechnische Problemen behandelt.
Mit genetischen Algorithmen wurde versucht die grundsätzliche Struktur,
mit der in der natürlichen Evolution Informationen gespeichert und verarbeitet werden,
auf die Computeralgorithmen zu übertragen.
Genetische Algorithmen gehen genauer auf die natürlichen Gegebenheiten
der natürlichen Evolution ein,
wenngleich die Unterschiede der Evolutionsstrategien und
der genetischen Algorithmen nur in Verfahrensdetails zu erkennen sind:
#### **<font color='blue'>Codierung</font>**
Bei Evolutionsstrategien werden die Individuen mit realzahl-codierten Parametersätzen dargestellt, während bei den genetischen Algorithmen meist eine binäre Codierung stattfindet.
Beispiele der Codierung:
![](./Pics/Codierung.png)
#### **<font color='blue'>Bewertung</font>**
Beurteilung der Individuen wird in erster Linie durchgeführt, um die **Qualität einer möglichen Lösung** zu bewerten. Hierzu wird eine **Bewertungsfunktion** bzw. **Qualitätsfunktion** ausgewertet. Bei den genetischen Algorithmen kommt noch zusätzlich eine Bewertung im Hinblick auf eine Teilnahme an einer Rekombination durchgeführt. Hierzu wird eine **Fitnessfunktion** herangezogen, die sich von der Qualitätsfunktion unterscheiden kann.
#### **<font color='blue'>Selektion</font>**
Eine Selektion wird an zwei Stellen durchgeführt:
eine Auswahl von **Individuen zur Erzeugung neuer Individuen**
und eine Auswahl zur **Bildung der nächsten Generation**.
Während bei **Evolutionsstrategien** die Elterselektion vollkommen zufällig geschieht (Zufallswahl),
wird bei **genetischen Algorithmen** eine Selektion mit einer zur Bewertung mittels der Fitnessfunktion proportionalen Wahrscheinlichkeit ausgeführt (Turnierauswahl, Rouletteauswahl).
Dies führt dazu, dass hoch bewertete Individuen ihre Erbinformationen mit größerer Wahrscheinlichkeit verbreiten können als durchschnittlich oder schlecht bewertete Individuen.
* **Zufallswahl / Random Selection**, eine Methode zur zufälligen Auswahl von Chromosomenpaaren aus den Elternchromosomen, ohne dass die Fitnesswerte eine Rolle spielen. Einfach ausgedrückt: Es werden nur Zufallswerte erzeugt, um das Elternchromosom auszuwählen.
* **Turnierauswahl / Tournament selection**, bei dieser Auswahlmethode wird eine Auswahl auf der Grundlage von Fitnesswerten getroffen.
Die Auswahl beginnt damit, dass mittels einer zufälligen Wahl mehrere potenzielle Eltern ausgewählt werden, aus denen das Elternteil mit dem besten Fitnesswert ausgewählt wird.
* **Rouletteauswahl / Roulette wheel selection**, die Anwendung dieser Auswahlmethode basiert auf der Wahrscheinlichkeit eines jeden Chromosoms. Die Größe des Anteils der Chromosomen im Roulettekessel hängt vom Fitnesswert ab. Die Auswahl erfolgt, indem ein Zufallswert aus dem Bereich aller Fitnesswerte gezogen wird.
Zur Bildung der nächsten Generation ist grundsätzlich nur eine Einfachselektion eines Individuums möglich, bei der Auswahl von Eltern aber auch eine Mehrfachselektion.
Bei der Selektion zur Bildung der nächsten Generation wird die Bewertungsfunktion herangezogen.
Verfahrensunterschiede ergeben sich durch ein vollständiges Ersetzen der Population durch die besten, neu erzeugten Kinder oder durch die Auswahl der am besten beurteilten Individuen aus der Gemeinschaft von Eltern und Kindern.
#### **<font color='blue'>Rekombination</font>**
Die Rekombination spielt bei den genetischen Algorithmen
eine wichtigere Rolle als bei den Evolutionsstrategien.
Das Rekombinationsverfahren bzw. Crossover-Verfahren
dient dazu, den **Suchraum schneller und zielgerichteter zu durchschreiten**,
als dies durch zufälliges Suchen möglich wäre.
Zum Austausch der Erbinformationen sind (mindestens) zwei Elter-Chromosomen
notwendig und resultieren in zwei neuen Chromosomen.
Das einfachste Verfahren ist das **One-Point-Crossover**
bzw. die **1-Punkt-Kreuzung**.
Hierbei übernimmt ein Nachkomme $X_c$ bis zu einer
zufällig gewählten Bruchstelle $k$ die ersten Chromosomen
von Elter $X_a$ und die restlichen vom Elter $X_b$.
**Elter** ist die nur in der Genetik übliche Singularform von Eltern).
Ein zweites Kind $X_d$ entsteht durch das komplementäre Vorgehen:
\begin{equation}\;\;
\begin{matrix}
X_a = [ x_{1\,a},... , x_{k\,a}, x_{k+1\,a},... , x_{n\,a}] \\
X_b = [ x_{1\,b},... , x_{k\,b}, \; x_{k+1\,b},... , x_{n\,b}]
\end{matrix}
\;\;\;\; \rightarrow \;\;\;\;
\begin{matrix}
X_c = [ x_{1\,a},... , x_{k\,a}, \; x_{k+1\,b},... , x_{n\,b}]\\
X_d = [ x_{1\,b},... , x_{k\,b}, \; x_{k+1\,a},... , x_{n\,a}]
\end{matrix}
\end{equation}
<img src="./Pics/Cross-Over-1.gif" width="40%" height="40%">
Mit dieser Vorgehensweise werden benachbarte Informationen,
die in den Sequenzen $[ x_{1\,a},... , x_{k\,a}]$ usw.
enthalten sind auf die Kinder übertragen.
Verallgemeinerungen sind die $n$-Punkt-Kreuzungen
wie z.B. die **2-Punkt-Kreuzung** mit einer
zweiten Bruchstelle.
<img src="./Pics/Cross-Over-2.gif" width="40%" height="40%">
Demgegenüber steht das **Uniform-Crossover** bzw. die **Zufallsschablone**.
Hier übernimmt der Nachkomme $X_c$ die Informationen $i$ von $X_a$
mit der Wahrscheinlichkeit von $p = 0,5$ ansonsten von $X_b$.
Ein zweites Kind $X_d$ entsteht durch die komplementäre Übernahme.
\begin{equation}
X_c = [ x_{1\,c},... , x_{i\,c}, ... , x_{n\,c}]
\qquad \text{mit} \quad
x_{i\,c} =
\begin{cases}
x_{i\,a} & \text{mit} \; p = 0,5 \; , \\
x_{i\,b} & \text{sonst.}
\end{cases}
\end{equation}
Die Quell und Zielindizes können auch zufällig gewählt werden:
<img src="./Pics/Cross-Over-3.gif" width="40%" height="40%">
#### **<font color='blue'>Mutation</font>**
Die Mutation dient in erster Linie der Vermeidung zu schneller Konvergenz und der Überwindung lokaler Optima.
Bei Evolutionsstrategien besitzt die Mutation eine wichtige Rolle, da die Evolutionsstrategien meist auf der Verdoppelung der Individuen basiert.
Durch die Mutation der Kopien entsteht ein neues Individuum mit einem modifizierten Variablensatz (Genen).
Die Modifikation erfolgt in der Regel ungerichtet, d.h. jede Information $x_i$ wird unabhängig verändert.
Die Mutation erfolgt bei Evolutionsstrategien nach dem Prinzip der statistischen Normalverteilung, wodurch geringfügige Änderungen des Erbguts mit größerer Wahrscheinlichkeit auftreten als große.
Zur Bestimmung einer Mutation $x_m$ wird auf den Vektor des Nachkommens $X_c$ ein Vektor von unabhängigen Gauß-verteilten Zufallszahlen
mit dem Mittelwert 0 und der Standardabweichung $\sigma$ aufaddiert:
\begin{equation}
X_m = [ x_{1\,m},... , x_{i\,m}, ... , x_{i\,m}]
\qquad \text{mit} \quad
x_{i\,m} = x_{i\,c} + N(0,\sigma_i) \; .
\end{equation}
Die Streuung der einzelnen Parameter ist problemabhängig.
Um nun möglichst schnell das Optimum zu erreichen, gibt es Techniken, die Streuungen $\sigma_i$ in Abhängigkeit von Erfolg oder Misserfolg der Mutationen anzupassen.
Für genetische Algorithmen mit ihrer binären Codierung ($x_i \in \{0,1\}$) wird die Mutation meist durch ein Negieren des Chromosoms bei einer vorgegebenen Wahrscheinlichkeit durchgeführt:
\begin{equation}
X_m = [ x_{1\,m},... , x_{i\,m}, ... , x_{n\,m}]
\qquad \text{mit} \quad
x_{i\,m} =
\begin{cases}
\urcorner x_{i\,m} & \text{mit z.B. } p = 1/n \; , \\
x_{i\,m} & \text{sonst.}
\end{cases}
\end{equation}
$\urcorner$ ist darin der Negationsoperator,
der hier aus einer $0$ eine $\urcorner 0 = 1 $ macht
und umgekehrt: $\urcorner 1 = 0 $.
<img src="./Pics/Mutation-1.gif" width="40%" height="40%"> <img src="./Pics/Mutation-2.gif" width="40%" height="40%">
Ein-und Multi-Punkt-Mutation
<img src="./Pics/Mutation-3.gif" width="40%" height="40%">
Austausch-Mutation / Swap-Muatation
### **<font color='blue'>Grundarten</font>**
Betrachtet man die Populationsgröße und die Zahl der Kinder, die in jeder Generation erzeugt werden, unterscheidet man folgende Varianten, deren Nomenklatur typisch für die Evolutionstrategien ist:
* $(1+1)$ <br>
In jeder Generation besteht die Population aus **einem Individuum**.
Aus dem Elter wird 1 neues Individuum durch **Kopieren** generiert und anschließend **mutiert**.
Der fittere der beiden Individuen wird in die nächste Generation übernommen.
(vgl. Monte-Carlo-Methoden, Simulated Annealing)
* $(\mu\,/\,\rho+1)$ <br>
In jeder Generation besteht die Population aus $\mu$ **Individuen**.
Per Zufall werden $\rho$ **Eltern** ausgewählt und mit ihnen per Rekombination 1 **Kind** generiert und mutiert.
Das am **wenigsten fitte Individuum** wird aus den $\mu+1$ Individuen entfernt.
Die verbliebenen bilden die neue Generation.
* $(\mu\,/\,\rho+\lambda)$ <br>
Wie eben besteht eine Population aus $\mu$ **Individuen**.
Es werden $\lambda$ **Kinder** aus jeweils $\rho$ **Eltern** generiert und mutiert, wobei $\lambda \geq \mu$.
Die $\mu$ **fittesten Individuen von Eltern und Kindern** bilden dann wieder die nächste Generation.
* $(\mu\,/\,\rho,\lambda)$ <br>
Wieder besteht eine Population aus $\mu$ **Individuen**.
Mit ihnen werden $\lambda$ **Kinder** durch Rekombination aus jeweils $\rho$ **Eltern** generiert und mutiert, wobei wieder $\lambda \geq \mu$ ist.
Die $\mu$ **fittesten Kinder** bilden die nächste Generation.
Da die Eltern nicht mehr in der nächsten Generation enthalten sind,
kann es keine unsterblichen Individuen geben.
Jedoch können dadurch auch Generationen entstehen,
die schlechter sind als ihre Vorgänger.
* $[\mu'\,/\,\rho'\#\lambda' (\mu\,/\,\rho\#\lambda)^\gamma]$ mit $\# \in [+,]$ <br>
Grundsätzlich lassen sich auch mehrere Populationen **parallel** behandeln.
Bei der Nomenklatur bezieht sich dann die innere **Klammerebene** auf die Individuen der **einzelnen Population**, die äußere auf die **verschiedenen Populationen**.
Dies bedeutet, dass aus $\mu'$ **Elternpopulationen** $\lambda'$ **Kinderpopulationen** - ggf. durch Rekombination aus jeweils $\rho'$ **Populationen** - erzeugt werden.
Die Kinderpopulationen verhalten sich dann für $\gamma$ **Generationen** nach der Art $(\mu/\rho\#\lambda)$, wobei $\#$ gleich $+$ oder $,$ sein kann.
Abschließend werden die $\mu'+\lambda'$ bzw. $\lambda'$ Populationen bewertet und die besten $\mu'$ wieder für den nächsten Schritt als Menge der Ausgangspopulationen herangezogen.
%% Cell type:markdown id:4ff7fd2d-b88d-40b0-a2ac-2ebb4590e2fd tags:
## **<font color='blue'>Rucksackproblem</font>**
%% Cell type:code id:ebffe1ed-4a60-45b8-9a29-26e63694d6e0 tags:
``` python
from deap import base, creator, tools, algorithms
import random
import numpy
import matplotlib.pyplot as plt
random.seed( 42 ) # Für reproduzierbar zufällige Zahlen
# item, weight, value
if False:
items = [ ( "A", 100, 40 ),
( "B", 50, 35 ),
( "C", 45, 18 ),
( "D", 20, 4 ),
( "E", 10, 10 ),
( "F", 5, 2 ) ]
maxCapacity = 100
else:
items = [ ("map", 9, 150),
("compass", 13, 35),
("water", 153, 200),
("sandwich", 50, 160),
("glucose", 15, 60),
("tin", 68, 45),
("banana", 27, 60),
("apple", 39, 40),
("cheese", 23, 30),
("beer", 52, 10),
("suntan cream", 11, 70),
("camera", 32, 30),
("t-shirt", 24, 15),
("trousers", 48, 10),
("umbrella", 73, 40),
("waterproof trousers", 42, 70),
("waterproof overclothes", 43, 75),
("note-case", 22, 80),
("sunglasses", 7, 20),
("towel", 18, 12),
("socks", 4, 50),
("book", 30, 10) ]
maxCapacity = 400
NBR_ITEMS = len( items )
# Definition der Fitness-Funktion
def getKnapsackValue(zeroOneList):
totalWeight = totalValue = 0
for i in range(len(zeroOneList)):
item, weight, value = items[i]
if totalWeight + weight <= maxCapacity:
totalWeight += zeroOneList[i] * weight
totalValue += zeroOneList[i] * value
return totalValue,
# Konstanten des genetischen Algorithmus
POPULATION_SIZE = 2*NBR_ITEMS
MAX_GENERATIONS = 1*NBR_ITEMS
P_CROSSOVER = 0.9 # Wahrscheinlichkeit der Rekombination
P_MUTATION = 0.1 # Wahrscheinlichkeit der Mutation
HALL_OF_FAME_SIZE = 1
# Neue Klasse "Fitness", abgeleitet von base.Fitness, mit Attribut weights
# Fitness ist die Maximierung des Gesamtgewichts
creator.create( "FitnessMax", base.Fitness, weights=(1.0,))
# Neue Klasse "Individual", abgeleitet von "list", mit Attribut fitness.
creator.create( "Individual", list, fitness=creator.FitnessMax)
# Initalisieren eine neue Toolbox
toolbox = base.Toolbox()
# Registrieren des Attribut-Generatorfunktion randint() unter "attr_item" : Liefert zufällig 0 oder 1
toolbox.register( "zeroOrOne", random.randint, 0, 1 )
# Registrieren einer Individuum-Generatorfunktion unter "individualCreator" : initRepeat(container, func, n)
toolbox.register( "individualCreator", tools.initRepeat, creator.Individual, toolbox.zeroOrOne, NBR_ITEMS )
# Registrieren einer Populations-Generatorfunktion unter "populationCreator" : initRepeat(container, func, n), d.h. n muss beim Aufruf gegeben werden.
toolbox.register( "populationCreator", tools.initRepeat, list, toolbox.individualCreator )
# Registrieren der Fitness-Berechung unter "evaluate"
toolbox.register( "evaluate", getKnapsackValue)
# Registrieren der Selektion : Tournament mit Turniergröße 3
toolbox.register( "select", tools.selTournament, tournsize=3 )
# Registrieren der Rekombination Single-point crossover:
toolbox.register( "mate", tools.cxTwoPoint )
# Registrieren der Mutation FlipBit : indpb Wahrscheinlichkeit des Flips
toolbox.register( "mutate", tools.mutFlipBit, indpb=1.0/NBR_ITEMS )
# Gesamtablauf
def main():
# Erzugen einer initialen Population (Generation 0)
population = toolbox.populationCreator( n=POPULATION_SIZE )
# Definition des Statistik-Objects
stats = tools.Statistics( lambda ind: ind.fitness.values )
stats.register( "max", numpy.max )
stats.register( "avg", numpy.mean )
# Definition des Hall-of-Fame Objekts
hof = tools.HallOfFame(HALL_OF_FAME_SIZE)
# perform the Genetic Algorithm flow with hof feature added:
population, logbook = algorithms.eaSimple( population, toolbox,
cxpb=P_CROSSOVER, mutpb=P_MUTATION,
ngen=MAX_GENERATIONS,
stats=stats, halloffame=hof, verbose=True)
# print best solution found:
best = hof.items[0]
print( "++ Brute-Force Permutationen:", NBR_ITEMS**2 )
print( "-- Bestes Individuum : ", best )
print( "-- Beste Fitness : ", best.fitness.values[0] )
# extract statistics:
maxFitnessValues, meanFitnessValues, nevals = logbook.select( "max", "avg", "nevals" )
for i in range(1,len(nevals)):
nevals[i] = nevals[i-1] + nevals[i]
# plot statistics:
#sns.set_style("whitegrid")
plt.plot( maxFitnessValues, color='red' )
plt.plot( meanFitnessValues, color='green' )
plt.plot( nevals, color='blue' )
plt.xlabel( 'Generation' )
plt.ylabel( 'Max / Average Fitness' )
plt.title( 'Max and Average fitness over Generations' )
plt.grid()
plt.show()
if __name__ == "__main__":
main()
```
%% Output
gen nevals max avg
0 44 805 595.818
1 42 840 694.818
2 40 887 755.614
3 40 902 806.864
4 40 940 829.205
5 42 937 856.886
6 42 965 886.182
7 37 965 899.886
8 38 967 923.091
9 41 965 943.273
10 42 965 948.114
11 40 965 955.818
12 40 965 960.841
13 40 965 964.25
14 42 965 958.068
15 41 965 961.523
16 37 965 956.068
17 41 965 960.568
18 37 965 957.273
19 42 965 959.318
20 42 965 962.045
21 44 965 961.136
22 39 965 960.045
++ Brute-Force Permutationen: 484
-- Bestes Individuum : [1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
-- Beste Fitness : 967.0
%% Cell type:markdown id:f7f74e23-10e4-41d2-b16f-e3103254a027 tags:
### **<font color='blue'>Übungsaufgabe</font>**
Wie sieht der Algorithmus für das Rucksackproblem aus, wenn man die Indizes der ausgewählten Elemente im Lösungsvektor (Python `set`) speichert?
%% Cell type:code id:c11c0a14-0714-40f6-919c-c2209899256b tags:
``` python
```
%% Cell type:markdown id:b063833e-e3ce-4dcc-82a8-1fa689b46a34 tags:
## **<font color='blue'>DEAP - Distributed Evolutionary Algorithms in Python</font>**
DEAP ist ein Framework für evolutionäre Berechnungen, dass insbesondere dem schnellen Prototyping und dem Testen von Ideen dient.
Sein Design versucht, Algorithmen explizit und Datenstrukturen transparent zu machen.
Es beinhaltet auch eine einfache Parallelität, bei der sich die Benutzer nicht mit Implementierungsdetails wie Synchronisierung und Lastausgleich befassen müssen, sondern nur mit der funktionalen Dekomposition.
DEAP ist aus drei Kern-Modulen sowie weiteren Modulen aufgebaut:
<img src="./Pics/Deap_Architecture.png" width="40%" height="40%">
* **Base** <br>
Das Basismodul **Base** enthält **Objekte** und **Datenstrukturen**, die in evolutionären Verfahren häufig verwendet werden und nicht bereits in der Python-Standardbibliothek implementiert sind. Da Python bereits viele der benötigten Datenstrukturen bereitstellt, implementiert dieses Modul eigentlich nur drei Klassen: eine **generische Fitness**, eine **Toolbox** und einen **speziellen Baum**.
Die Toolbox ist ein Container für die Werkzeuge (**Operatoren**), die der Benutzer in seinem evolutionären Algorithmus verwenden möchte.
Beispielsweise kann eine Nutzer eine eigene Mutation nutzen, indem er eine nahekommende verfügbare `MutationXYZ` bei der Registrierung der eigenen `Mutation` in der Toolbox nutzt.
* **Creator** <br>
Das **Creator**-Modul ist eine **Meta-Fabrik**, die die **Erstellung von Klassen** durch Vererbung und Komposition unter Verwendung eines funktionalen Programmierparadigma ermöglicht, d.h. ohne den Aufwand des Nutzers für die Definition von Klassen. **Attribute**, sowohl Daten als auch Funktionen, können dynamisch hinzugefügt werden, um neue Klassen zu erstellen.
* **Tools** <br>
Das Modul **Tools** enthält häufig verwendete Operatoren (**Operators**) der evolutionären Algorithmen. Sie werden eingesetzt, um die Individuen in ihrer Umgebung zu **verändern, auszuwählen** und zu **verschieben**.
Außerdem bietet es Objekte, die verschiedene Analyseaufgaben wie Checkpointing, Statistikauswertung und Genealogie erleichtern.
Eine Toolbox selbst ist eine Behälter für die vom Nutzer ausgewählten Werkzeuge.
* **Algorithms** <br>
Die Kernfunktionalitäten von DEAP werden durch das Algorithmenmodul **Algorithms** ergänzt, das häufig verwendete **Populations-Methoden** enthält:
z.B. (μ , λ), (μ + λ). DEAP ist jedoch in keiner Weise auf diese Verfahren beschränkt.
Sie sind nur ein Ausgangspunkt für Nutzer, die ihre eigenen maßgeschneiderten Algorithmen entwickeln wollen.
* **GP - Genetic Programming** <br>
Operatoren und Werkzeuge, die nicht in den Kernmodulen enthalten sind, finden sich in eigenen Modulen,
z.B. Operatoren und Datenstrukturen der Genetischen Programmierung (GP) im **GP**-Modul.
* **Benchmark** <br>
Das Modul **Benchmark** enthält verschiedene State-of-the-Art-Benchmark-Funktionen, die zur Bewertung der Algorithmenleistung verwendet werden können.
* **DTM - Distributed Task Manager** <br>
Das letzte Modul des Frameworks mit dem Namen **DTM**, der für Distributed Task Manager, behandelt die Parallelität.
### **<font color='blue'>Wichtige Klassen und Funktionen</font>**
* **Creator**
* `creator.create(name, base[, attribute[, ... ]])` : Erzeugt eine neue Klasse mit dem Namen `name`, die von `base` im Creator-Modul erbt. Die neue Klasse kann Attribute haben, die durch die nachfolgenden Schlüsselwortargumente definiert werden.
<br>
* **Toolbox**
* Class `base.Toolbox()` : Erzeugt ein neues Toolboxobjekt.
* `register(alias, method[, argument[, ... ]])` : Registriert eine Methode `method` in der Toolbox unter dem Namen `alias`. Es können Standardargumente angeben werden, die automatisch übergeben werden, wenn die registrierte Methode aufgerufen wird. Die Standardargumente können dann beim Funktionsaufruf überschrieben werden.
<br>
* **Fitness**
* Class `base.Fitness([values ])` : Die Fitness ist ein Maß für die Qualität einer Lösung. Wenn `values` als Tupel angegeben werden, wird die Fitness mit diesen Werten initialisiert. Die Werte der Fitness `values` können über `individual.fitness.values = values` gesetzt werden.
<br>
* **Operatoren**
Sie stellen die Grundfunktionen für die Transformation (Rekombination, Mutation) oder Selektion der Individuen bereit. Werden beispielsweise zwei Individuen für eine Rekombination bereitgestellt, so erfolgt diese Rekombination in-place. Das Lösen der Nachkommen von ihren Eltern und das Zurücksetzen der Fitness obliegt dem Nutzer. Im Allgemeinen bietet es sich an mit dem Aufruf der `toolbox.clone()` Funktion ein Individuum zu clonen und mittel `del` das Attribut `values` zu leeren.
* **Initalisierung** - in DEAP implementiert:
* `tools.initRepeat(container, func, n)` : Aufruf des Funktions`container`s mit einer Generatorfunktion, die dem n-fachen Aufruf der Funktion func entspricht.
* `container` – Datentyp zum Speichern der Ergebnisse von `func`.
* `func` – Funktion, die n-mal aufgerufen wird, um den `container` zu füllen.
* `n` – Anzahl der Wiederholungen von `func`.
* Return: Eine Instanz des gefüllten `container`
<br>
* **Rekombination/Crossover** - in DEAP implementiert:
* `tools.cxOnePoint(ind1, ind2)` : Führt eine Ein-Punkt-Kreuzung an den gegebenen Individuen `ind1`, `ind2` durch. Beide Individuen werden in-place verändert und zurückgegeben.
* `tools.cxTwoPoint(ind1, ind2)` : Führt eine Zwei-Punkt-Kreuzung an den gegebenen Individuen `ind1`, `ind2` durch. Beide Individuen werden in-place verändert und zurückgegeben. Nutzt die `randint()` von Python.
* `tools.cxUniform(ind1, ind2, indpb)` : Führt eine uniforme Kreuzung an den gegebenen Individuen `ind1`, `ind2` durch. Die Attribute der beiden Individuen werden in-place entsprechend der `indpb`-Wahrscheinlichkeit vertauscht. Nutzt die `random()` von Python.
<br>
* **Mutationen** - in DEAP implementiert:
* `tools.mutFlipBit(individual, indpb)` : Umkehrung des Attribute mittels des not-Operators bei gegebener Wahrscheinlichkeit `indpb` des gegebenen Individuums und Rückgabe der Mutante. Anwendung normalerweise auf boolesche Werte.
* `tools.mutGaussian(individual, mu, sigma, indpb)` : Gaußsche Mutation mit dem Mittelwert `mu` und der Standardabweichung `sigma` auf das gegebene Individuum mit reellen Attributen. `indpb` ist die Wahrscheinlichkeit mit der ein Attribut mutiert werden soll.
<br>
* **Selektion** - in DEAP implementiert:
* `tools.selTournament(individuals, k, tournsize)` : Auswahl von `k` Individuen aus den gegebenen Individuen `individuals` unter Verwendung von `k` Turnieren mit `tournsize` Individuen.
* `tools.selRoulette(individuals, k)` : Auswahl von `k` Individuen aus den gegebenen Individuen durch `k` Drehungen eines Roulettes. Die Auswahl erfolgt nur aufgrund des erste Fitnesswerts.
* `tools.selBest(individuals, k)` : Auswahl der `k` besten Individuen aus den gegebenen Individuen.
<br>
* **Statistik** - in DEAP implementiert:
* Class `tools.Statistics([key][, n])` : Liefert ein Statistikobjekt, das die verlangten Daten enthält. Bei der Erstellung erhält das Statistikobjekt ein Schlüsselargument, um die erforderlichen Daten zu erhalten. Ein Statistik-Objekt kann für jede registrierte Funktion als 3-dimensionale Matrix angesehen werden.
* `register(name, function)` : Registrierung einer Funktion, die bei jedem Aufruf von `update()` auf die Sequenz angewendet wird.
* `update(seq, index=0, add=False)` : Wendet auf die Eingabesequenz `seq` jede registrierte Funktion an und speichert das Ergebnis in einer Liste, zugehörig zur Funktion und dem Datenindex `index`.
* `tools.mean(seq), tools.median(seq), tools.var(seq), tools.std(seq)` : Returns the arithmetic mean, the median, the variance and the square root of the variance of the sequence `seq`.
<br>
* **HallOfFame** - in DEAP implementiert:
* Class `tools.HallOfFame(maxsize)` : Die Ruhmeshalle enthält die besten Individuen, die im Laufe der Evolution in der Population vorhanden waren. Sie ist immer so sortiert, dass das erste Element der Ruhmeshalle das Individuum ist, das den besten jemals erreichten ersten Fitnesswert hat.
* Class `tools.ParetoFront([similar])` : Die Ruhmeshalle der Pareto-Front enthält alle nicht-dominierten Individuen, die jemals in der Population vorhanden waren.
* **Algorithms**
Das Modul Algorithmen soll Algorithmen enthalten, um sehr gängige evolutionäre Algorithmen auszuführen.
Die hier verwendeten Methoden dienen eher der Bequemlichkeit als als Referenz,
da die Implementierung jedes evolutionären Algorithmus unendlich variieren kann.
Die meisten Algorithmen in diesem Modul verwenden die in der Toolbox registrierten Operatoren.
Im Allgemeinen werden die Schlüsselwörter `mate()` für Crossover, `mutate()` für Mutation, `select()` für Selektion und `evaluate()` für Evaluation verwendet.
* `algorithms.eaSimple(population, toolbox, cxpb, mutpb, ngen[, stats, halloffame, verbose ])` : Dieser Algorithmus reproduziert den einfachen evolutionären Algorithmus.
Diese Funktion erwartet, dass die Aliase `toolbox.mate()`, `toolbox.mutate()`, `toolbox.select()` und `toolbox.evaluate()` registriert sind.
Der Algorithmus verwendet λ = κ = μ und geht wie folgt vor. Zunächst wird die Population (P(0)) initialisiert, indem jedes Individuum bewertet wird.
Dann wird die Evolutionsschleife gestartet, die mit der Auswahl der Population P(g+1) beginnt. Der Crossover-Operator wird auf einen Teil von P(g+1) entsprechend der `cxpb`-Wahrscheinlichkeit angewandt. Die resultierenden und die unveränderten Individuen werden in P'(g+1) platziert.
Danach wird ein Teil von P'(g+1) über `mutpb` ausgewählt, mutiert und in P''(g+1) platziert, die unberührten Individuen werden auch in P''(g + 1) übertragen. Schließlich werden diese neuen Individuen bewertet und die Evolutionsschleife wird fortgesetzt, bis `ngen` Generationen abgeschlossen sind. Die Operatoren werden in der folgenden Reihenfolge angewendet:
<br>
evaluate(population)
for i in range(ngen):
descendants = select(population)
descendants = mate(descendants)
descendants = mutate(descendants)
evaluate(descendants)
population = descendants
* `algorithms.eaMuPlusLambda(population, toolbox, mu, lambda_, cxpb, mutpb, ngen[, stats, halloffame, verbose ])` : Dies ist der (μ + λ) evolutionäre Algorithmus. `mu` ist die Anzahl der Individuen, die für die nächste Generation ausgewählt werden, `lambda`die Anzahl der Kinder, die in jeder Generation erzeugt werden.
Zunächst werden alle Individuen bewertet. Dann beginnt die Evolutionsschleife mit der Erzeugung von `lambda` Nachkommen aus der Population, die Nachkommen werden durch Kreuzung, Mutation oder Reproduktion erzeugt proportional zu den Wahrscheinlichkeiten `cxpb`, `mutpb` und `1 - (cxpb + mutpb)`. Die Nachkommen werden ausgewertet und die Population der nächsten Generation wird sowohl aus den Nachkommen als auch aus der Population ausgewählt.
<br>
evaluate(population)
for i in range(ngen):
descendants = varOr(population, toolbox, lambda_, cxpb, mutpb)
evaluate(descendants)
population = select(population + descendants, mu)
* **Variations**
* `algorithms.varAnd(population, toolbox, cxpb, mutpb)` : Teil eines evolutionären Algorithmus, der nur den Variationsteil (Crossover und Mutation) anwendet. Die veränderten Individuen haben noch keine Fitness-Bewertung. Die Individuen werden geklont, so dass die erzeugte Population unabhängig ist von der Eingabepopulation ist.
Bei der Variation wird zunächst die elterliche Population $P_p$ mit der `toolbox.clone()`-Methode dupliziert und das Ergebnis in die Nachkommenpopulation $P_o$ gesetzt. Eine erste Schleife über $P_o$ wird ausgeführt, um aufeinanderfolgende Individuen zu paaren. Entsprechend der Kreuzungswahrscheinlichkeit `cxpb` werden die Individuen $x_i$ und $x_{i+1}$ mit der Methode `toolbox.mate()` gepaart. Die resultierenden Kinder $y_i$ und $y_{i+1}$ ersetzen ihre jeweiligen Eltern in $P_o$.
Eine zweite Schleife über das resultierende $P_o$ wird ausgeführt, um jedes Individuum mit einer Wahrscheinlichkeit `mutpb` zu mutieren.
Wenn ein Individuum mutiert ist, ersetzt es seine nicht mutierte Version in $P_o$.
Das resultierende $P_o$ wird zurückgegeben.
Diese Variante wird `And` genannt, weil sie sowohl Crossover als auch Mutation auf die Individuen anwenden kann.
Man beachte, dass beide Operatoren nicht systematisch angewandt werden, die resultierenden Individuen können nur durch Crossover, nur durch Mutation, durch Crossover und Mutation und durch Reproduktion entsprechend der angegebenen Wahrscheinlichkeiten erzeugt werden.
* `algorithms.varOr(population, toolbox, lambda_, cxpb, mutpb)` : Bei jeder `lambda`-Iteration wählt die Variation eine der drei Operationen: Kreuzung, Mutation oder Reproduktion. Im Falle einer Kreuzung werden zwei Individuen zufällig aus der elterlichen Population $P_p$ ausgewählt. Diese Individuen werden mit der Methode `toolbox.clone()` geklont und dann mit der Methode `toolbox.mate()` gepaart. Nur das erste Kind wird an die Nachkommenpopulation $P_o$ angehängt, das zweite Kind wird verworfen. <br>
Im Falle einer Mutation wird ein Individuum zufällig aus $P_p$ ausgewählt, geklont und dann mit Hilfe der Methode `toolbox.mutate()` mutiert. Die resultierende Mutante wird an $P_o$ angehängt. Im Falle einer Reproduktion wird ein Individuum nach dem Zufallsprinzip aus $P_p$ ausgewählt, geklont und an $P_o$ angehängt. <br>
Diese Variante wird `Or` genannt, weil niemals ein Nachkomme aus beiden Operationen Crossover und Mutation resultieren wird. Die Summe der beiden Wahrscheinlichkeiten soll in [0, 1] liegen, die Reproduktionswahrscheinlichkeit ist `1 - cxpb - mutpb`.
Semester_2/Einheit_04/Pics/.ipynb_checkpoints/Mutation-1-checkpoint.gif

1.29 MiB

Semester_2/Einheit_04/Pics/.ipynb_checkpoints/Mutation-2-checkpoint.gif

1.16 MiB

Semester_2/Einheit_04/Pics/.ipynb_checkpoints/Mutation-3-checkpoint.gif

744 KiB

<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentScriptType="application/ecmascript" contentStyleType="text/css" height="550px" preserveAspectRatio="none" style="width:417px;height:550px;" version="1.1" viewBox="0 0 417 550" width="417px" zoomAndPan="magnify"><defs><filter height="300%" id="fgrty9rvwu9y2" width="300%" x="-1" y="-1"><feGaussianBlur result="blurOut" stdDeviation="2.0"/><feColorMatrix in="blurOut" result="blurOut2" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .4 0"/><feOffset dx="4.0" dy="4.0" in="blurOut2" result="blurOut3"/><feBlend in="SourceGraphic" in2="blurOut3" mode="normal"/></filter></defs><g><ellipse cx="211.5" cy="20" fill="#000000" filter="url(#fgrty9rvwu9y2)" rx="10" ry="10" style="stroke: none; stroke-width: 1.0;"/><rect fill="#FEFECE" filter="url(#fgrty9rvwu9y2)" height="39.0679" rx="12.5" ry="12.5" style="stroke: #A80036; stroke-width: 1.5;" width="171" x="126" y="50"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="151" x="136" y="74.9659">initialisiere Population</text><rect fill="#FEFECE" filter="url(#fgrty9rvwu9y2)" height="39.0679" rx="12.5" ry="12.5" style="stroke: #A80036; stroke-width: 1.5;" width="296" x="63.5" y="176.1358"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="276" x="73.5" y="201.1018">bestimme Fitnesswert für Elternselektion</text><rect fill="#FEFECE" filter="url(#fgrty9rvwu9y2)" height="39.0679" rx="12.5" ry="12.5" style="stroke: #A80036; stroke-width: 1.5;" width="292" x="65.5" y="235.2038"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="272" x="75.5" y="260.1697">selektiere Individuen/Eltern für Paarung</text><rect fill="#FEFECE" filter="url(#fgrty9rvwu9y2)" height="39.0679" rx="12.5" ry="12.5" style="stroke: #A80036; stroke-width: 1.5;" width="197" x="113" y="294.2717"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="177" x="123" y="319.2376">erzeuge Rekombinationen</text><rect fill="#FEFECE" filter="url(#fgrty9rvwu9y2)" height="39.0679" rx="12.5" ry="12.5" style="stroke: #A80036; stroke-width: 1.5;" width="157" x="133" y="359.3056"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="137" x="143" y="384.2716">erzeuge Mutationen</text><rect fill="#FEFECE" filter="url(#fgrty9rvwu9y2)" height="39.0679" rx="12.5" ry="12.5" style="stroke: #A80036; stroke-width: 1.5;" width="335" x="44" y="418.3735"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="315" x="54" y="443.3395">bestimme Fitnesswert für Populationsselektion</text><rect fill="#FEFECE" filter="url(#fgrty9rvwu9y2)" height="39.0679" rx="12.5" ry="12.5" style="stroke: #A80036; stroke-width: 1.5;" width="300" x="61.5" y="477.4415"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="280" x="71.5" y="502.4074">selektiere Individuen für neue Population</text><polygon fill="#FEFECE" filter="url(#fgrty9rvwu9y2)" points="141,109.0679,282,109.0679,294,121.0679,282,133.0679,141,133.0679,129,121.0679,141,109.0679" style="stroke: #A80036; stroke-width: 1.5;"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="30" x="215.5" y="148.0339">nein</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="141" x="141" y="126.4999">Stopkriteria erreicht?</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="12" x="117" y="116.9659">ja</text><ellipse cx="24" cy="167.0679" fill="none" filter="url(#fgrty9rvwu9y2)" rx="10" ry="10" style="stroke: #000000; stroke-width: 1.0;"/><ellipse cx="24.5" cy="167.5679" fill="#000000" filter="url(#fgrty9rvwu9y2)" rx="6" ry="6" style="stroke: none; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.5;" x1="211.5" x2="211.5" y1="30" y2="50"/><polygon fill="#A80036" points="207.5,40,211.5,50,215.5,40,211.5,44" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.5;" x1="211.5" x2="211.5" y1="215.2038" y2="235.2038"/><polygon fill="#A80036" points="207.5,225.2038,211.5,235.2038,215.5,225.2038,211.5,229.2038" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.5;" x1="211.5" x2="211.5" y1="274.2717" y2="294.2717"/><polygon fill="#A80036" points="207.5,284.2717,211.5,294.2717,215.5,284.2717,211.5,288.2717" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.5;" x1="211.5" x2="211.5" y1="333.3396" y2="359.3056"/><polygon fill="#A80036" points="207.5,349.3056,211.5,359.3056,215.5,349.3056,211.5,353.3056" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.5;" x1="211.5" x2="211.5" y1="398.3735" y2="418.3735"/><polygon fill="#A80036" points="207.5,408.3735,211.5,418.3735,215.5,408.3735,211.5,412.3735" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.5;" x1="211.5" x2="211.5" y1="457.4415" y2="477.4415"/><polygon fill="#A80036" points="207.5,467.4415,211.5,477.4415,215.5,467.4415,211.5,471.4415" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.5;" x1="211.5" x2="211.5" y1="133.0679" y2="176.1358"/><polygon fill="#A80036" points="207.5,166.1358,211.5,176.1358,215.5,166.1358,211.5,170.1358" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.5;" x1="211.5" x2="211.5" y1="516.5094" y2="526.5094"/><line style="stroke: #A80036; stroke-width: 1.5;" x1="211.5" x2="391" y1="526.5094" y2="526.5094"/><polygon fill="#A80036" points="387,339.3056,391,329.3056,395,339.3056,391,335.3056" style="stroke: #A80036; stroke-width: 1.5;"/><line style="stroke: #A80036; stroke-width: 1.5;" x1="391" x2="391" y1="121.0679" y2="526.5094"/><line style="stroke: #A80036; stroke-width: 1.5;" x1="391" x2="294" y1="121.0679" y2="121.0679"/><polygon fill="#A80036" points="304,117.0679,294,121.0679,304,125.0679,300,121.0679" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.5;" x1="129" x2="24" y1="121.0679" y2="121.0679"/><line style="stroke: #A80036; stroke-width: 1.5;" x1="24" x2="24" y1="121.0679" y2="157.0679"/><polygon fill="#A80036" points="20,147.0679,24,157.0679,28,147.0679,24,151.0679" style="stroke: #A80036; stroke-width: 1.0;"/><line style="stroke: #A80036; stroke-width: 1.5;" x1="211.5" x2="211.5" y1="89.0679" y2="109.0679"/><polygon fill="#A80036" points="207.5,99.0679,211.5,109.0679,215.5,99.0679,211.5,103.0679" style="stroke: #A80036; stroke-width: 1.0;"/><!--
@startuml
skinparam defaultFontSize 14
skinparam classAttributeIconSize 0
scale max 1024 width
start
:initialisiere Population;
while (Stopkriteria erreicht?) is (nein)
:bestimme Fitnesswert für Elternselektion;
:selektiere Individuen/Eltern für Paarung;
:erzeuge Rekombinationen;
:erzeuge Mutationen;
:bestimme Fitnesswert für Populationsselektion;
:selektiere Individuen für neue Population;
endwhile (ja)
stop
@enduml
PlantUML version 1.2018.13(Mon Nov 26 18:11:51 CET 2018)
(GPL source distribution)
Java Runtime: OpenJDK Runtime Environment
JVM: OpenJDK 64-Bit Server VM
Java Version: 11.0.18+10-post-Ubuntu-0ubuntu120.04.1
Operating System: Linux
OS Version: 5.15.0-71-generic
Default Encoding: UTF-8
Language: de
Country: DE
--></g></svg>
\ No newline at end of file
@startuml
skinparam defaultFontSize 14
skinparam classAttributeIconSize 0
scale max 1024 width
start
:initialisiere Population;
while (Stopkriteria erreicht?) is (nein)
:bestimme Fitnesswert für Elternselektion;
:selektiere Individuen/Eltern für Paarung;
:erzeuge Rekombinationen;
:erzeuge Mutationen;
:bestimme Fitnesswert für Populationsselektion;
:selektiere Individuen für neue Population;
endwhile (ja)
stop
@enduml
Semester_2/Einheit_04/Pics/Chromosom.png

29.6 KiB

Semester_2/Einheit_04/Pics/Codierung.png

80.1 KiB

Semester_2/Einheit_04/Pics/Cross-Over-1.gif

1.81 MiB