AuK/Systematische Suche WS13-14
Aus ProgrammingWiki
Wiki-Quelltext dieser Seite.
<language>Scheme</language>
;Autoren
:[mailto://s3hakret@stud.hszg.de Hannes Kretschmer]<br/>
:[mailto://s3juhils@stud.hszg.de Julian Hilsberg]
<code id="4dbb20be19248" hidden>;; TRACING + NICHTDETERMINISMUS
(import debugging) (define amb-fail '*) (define initialize-amb-fail (lambda () (set! amb-fail (lambda () (error "amb" "It fails")))))
(initialize-amb-fail) (define-syntax amb (syntax-rules () ((amb alt ...) (let ((+prev-amb-fail amb-fail)) (call/cc (lambda (+sk) (call/cc
(lambda (+fk) (set! amb-fail (lambda () (set! amb-fail +prev-amb-fail) (+fk 'fail))) (+sk alt))) ... (+prev-amb-fail))))))) (define-syntax
bag-of (syntax-rules () ((bag-of e) (let ((+prev-amb-fail amb-fail) (+results '())) (if (call/cc (lambda (+k) (set! amb-fail (lambda () (+k
#f))) (let ((+v e)) (set! +results (cons +v +results)) (+k #t)))) (amb-fail)) (set! amb-fail +prev-amb-fail) (reverse! +results))))) (define
choice (lambda (ls) (eval (cons 'amb (map (lambda (x) (list 'quote x)) ls))))) (define multichoice (lambda (ls n) (if (= n 0) '() (let ((item
(choice ls))) (cons item (multichoice (delete item ls) (- n 1))))))) (define delete (lambda (item ls) (cond ((null? ls) '()) ((eqv? (car ls)
item) (cdr ls)) (else (cons (car ls) (delete item (cdr ls))))))) (define randomchoice (lambda (n) (letrec ((enumerate (lambda (i) (if (= i 0)
'() (cons i (enumerate (- i 1))))))) (let ((numberlist (enumerate n))) (choice numberlist)))))
</code>
=Rucksackprobleme=
Aus einer Menge von Gegenständen die einen Wert $w$ und ein Gewicht $g$ besitzen, gilt es einen Rucksack zu füllen bei dem der Gesamtwert der eingepackten Gegenstände maximal ist, ohne die Kapizitätsgrenze $K$ des Rucksacks zu überschreiten.
<center> <math> $\[ \sum_{i=1}^{n} x_i w_i \rightarrow max $ </math> , wobei <math> $\[ \sum_{i=1}^{n} x_i g_i \le K $</math>, mit <math> $ x_i \in \{0,1\} $</math> </center>
Allgemein beschreiben Rucksackprobleme: Beladungsprobleme, Verschnittprobleme oder Probleme der Budgetkontrolle.
==Beispiel==
Thomas will sein Geld anlegen und versucht ein Investment-Portfolio zu erstellen, aus dem er sich maximalen Gewinn erhofft. Er hat 10000€ ($K$) zur Verfügung und will diese in unterschiedliche Aktien verteilen, welche eine Bewertung ($w$) und einen festen zu investierenden Wert ($g$) besitzen. Nun geht es darum ein Portfolio zusammenzustellen (Rucksack), in welchem die Gesamtbewertung aller gekauften Aktien maximal ist, dabei darf sein Budget nicht überzogen werden.<br>
Zur Verfügung stehende Aktien:<br>
<table style="width: 40%" border="1" >
<tr>
<td>'''Aktie'''</td>
<td>'''Wert (in €)'''</td>
<td>'''Bewertung (0-10, höher ist besser)'''</td>
</tr>
<tr>
<td>Mapple</td>
<td>2500</td>
<td>8</td>
</tr>
<tr>
<td>Minisoft</td>
<td>3000</td>
<td>4</td>
</tr>
<tr>
<td>Le-Nogo</td>
<td>5000</td>
<td>5</td>
</tr>
<tr>
<td>Wogitech</td>
<td>6000</td>
<td>3</td>
</tr>
<tr>
<td>Joogle</td>
<td>4000</td>
<td>7</td>
</tr>
</table>
<br>
==Varianten==
Es gibt viele unterschiedliche Formen des Rucksackproblems:
<ol>
<li>'''0/1 Rucksackproblem:'''<br> Hier wird ein Gegenstand entweder vollständig (1) oder gar nicht (0) eingepackt. Außerdem darf der selbe Gegenstand nur einmal eingepackt werden.</li>
<li>'''Bruchteil-Rucksackproblem:''' <br> Man kann einen beliebigen Teil $t$, mit $0\% \le t \le 100\%$, eines Gegenstandes einpacken.</li>
<li>'''Unbeschränktes Rucksackproblem:''' <br> Bei dieser Variante des Problems gibt es keine Kapazitätsobergrenze, d.h. $K = \infty$.</li>
<li>'''Teilmengen-Summen-Problem (subset-sum problem):''' <br> In diesem speziellen Fall geht es darum die Gewichtskapazität des Rucksacks optimal auszunutzen, da der Wert mit dem Gewicht übereinstimmt, d.h. $g_i = w_i$ für alle $i$.</li>
</ol>
In diesem Artikel wird ausschließlich das 0/1 Rucksackproblem betrachtet. Bruchteil-Rucksackprobleme werden im Artikel der Greedy-Algorithmen behandelt.
=Lösungsverfahren=
==Intuitives Verfahren==
Ohne viel zu denken könnte man den Rucksack zufällig mit Gegenständen füllen, bis die Kapazitätsgrenze erreicht ist. Durch Austausch von jeweils 2 Objekten kann man den Gesamtwert nachträglich erhöhen. Ein im Rucksack befindlicher Gegenstand wird herausgenommen und durch einen höherwertigen, zuerst nicht eingepackten ersetzt, jedoch ohne die Kapazitätsgrenze zu überschreiten.<br>
Dieses Verfahren ist aber nicht optimal. Erstens kann man bei diesem nicht systematischen Verfahren nie sicher sein das Wertmaximum zu erreichen.
Ein weiteres Problem tritt auf, wenn wir das obige Beispiel betrachten. Wenn man noch nachträglich die Aktien tauscht um eine bessere Bewertung des Portfolios zu erreichen, bezahlt man Gebühren für jede Transaktion und verpasst somit die Chance einer besseren Gesamtbewertung.<br>
Es wäre also von Vorteil, Verfahren zu finden, die im Voraus die beste Lösung finden.
==Tiefensuche==
Das Tiefe-zuerst-Suchverfahren (engl. depth first search) oder kurz: die Tiefensuche stellt ein Lösungsverfahren für das 0/1 Rucksackproblem dar.<br>
===Ablauf===
'''Voraussetzungen'''
<ul>
<li>$w_i := g_i$</li>
<li>Liste mit Gegenständen im Rucksack s-ls (zunächst leer),</li>
<li>Liste der Gegenstände, die in den Rucksack übergehen können g-ls</li>
</ul>
'''Vorgehensweise'''
<ul>
<li>mit jedem Schritt wird entschieden, ob ein Gegenstand in den Rucksack gelegt werden soll, oder nicht</li>
<li>der Prozess endet (spätestens) sobald die Liste der Gegenstände außerhalb des Rucksacks leer ist</li>
</ul>
===Erstellung eines Suchbaums===
[[Bild:Tiefensuche1.png|200px|thumb|right|Entscheidungsbaum für die Gewichtsliste (30 50 10)]]
Der Entscheidungsbaum entwickelt sich '''dynamisch'''. Das heißt er wird bei der Suche von links her als ein Art Protokoll generiert. Praktisch betrachtet wird Gegenstand für Gegenstand in den Rucksack gepackt, wobei der letzte Gegenstand wieder ausgepackt, und die jeweils andere Entscheidung als zuvor getroffen wird. Hierbei spricht man von '''Backtracking'''.
Der Entscheidungsbaum im Bild rechts besitzt 8 Blätter. Es ergeben sich allgemein $2^n$ Blätter, wobei $n$ = Anzahl der Gegenstände. Die Anzahl der Blätter lassen sich mittels <math> $\[ \sum_{k=0}^{n}$</math> ${n \choose k} z^k$ berechnen.
===Laufzeit===
===Speicherplatzverbrauch===
===Codebeispiel===
<code id="5267bf214b241">
(define rucksack1
(lambda (g-ls s-ls)
(if (null? g-ls)
s-ls
(begin
(rucksack1 (cdr g-ls) (cons (car g-ls) s-ls)) ; Tiefe
(rucksack1 (cdr g-ls) s-ls))))) ; Breite
</code>
<run id="5267bf214b2cc">
;(trace 'rucksack1)
(rucksack1 '(30 50 10) '())
</run>
==Breitensuche==
===Ablauf===
Die Breitensuche (Breite-zuerst-Suche, engl. breadth-first search) durchläuft die Knoten eines Graphen nach der Entfernung vom Startknoten geordnet.
Die direkt vom Startknoten mit einer Kante erreichbaren, werden zuerst bearbeitet, dann die mit zwei, danach die mit drei usw.
===Erstellung eines Suchbaumes===
[[Bild:Breitensuche1.png|200px|thumb|right|Breitensuchbaum für 0/1-Rucksackproblem]]
Eine Alternative zu Lösung des Rucksackproblems, besteht darin den Suchbaum schichtenweise zu erstellen.
Man ist nicht gezwungen einen binären Suchbaum zu verwenden, sondern es geht darum den Suchraum durch Breitensuche eine baumartige Struktur zu geben.
Wie auf dem rechten Bild zu sehen besitzt der Baum, für das Rucksackproblem mit drei Gegenständen genau 16 Knoten. Die Anzahl der Knoten kann berechnet werden:
<math> $\[ \sum_{k=0}^{n} \dfrac{n!}{k!}$ </math>
Einige der Knoten sind aber überflüssig, da die Reihenfolge der eingepackten Sachen keine Rolle spielt. In dieser Form ist das Verfahren zur Lösung des Rucksackproblems ungeeignet.
===Breitensuche mit verbrauchend gelesener Basismenge===
[[Bild:Breitensuche2.png|200px|thumb|right|Reduzierter Suchbaum]]
Um einen brauchbaren Suchbaum zu erhalten, muss man die Gegenstandsliste in jedem Schritt stärker minimieren, als nur durch entfernen des einzupackenden Gegenstandes. <br>
Einen Baum auf den dieses Verfahren angewandt wurde, zeigt Bild 2.2.Schon vorgekommene Zustände sollen ausgeschlossen werden. Die 8 Blätter zeigen sämtliche Packzustände des Rucksacks.
===Laufzeit===
Im Worst-Case müssen alle Knoten des Breitensuchbaumes betrachtet werden. Daher beträgt die Laufzeit: <math>\mathcal{O}(\vert V \vert + \vert E \vert)</math>
===Speicherplatzverbrauch===
Da alle bisher gesehenen Knoten gespeichert werden, ist der Speicherplatzverbrauch außerordentlich groß. Damit ist die Breitensuche für sehr große Probleme ungeeignet.
=Beschränkung des Suchbaumes=
==Tiefensuche mit Beschränkung==
Bei der beschränkten Tiefensuche wird nun auch die Kapazitätsbeschränkung ($K$) beim "Packen des Rucksacks" mit einbezogen. Im folgenden Beispiel ist eine Prozedur zur Erstellung eines Berechnungsprotokolles zur Lösung des Problems unter Berücksichtigung von $K$ in Scheme dargestellt:
<code id="526e23e042f72">
;;; Tiefensuche mit Beschraenkung
(define rucksack2
(lambda (g-ls s-ls)
(if (null? g-ls)
s-ls
(begin
(if (hoechstens-maxgewicht? s-ls 42)
(rucksack2 (cdr g-ls) (cons (car g-ls) s-ls)))
(if (hoechstens-maxgewicht? s-ls 42)
(rucksack2 (cdr g-ls) s-ls))))))
(define hoechstens-maxgewicht?
(lambda (ls k)
(<= (apply + ls) k)))
</code>
<run id="526e2e358df36">(trace 'rucksack2)
(rucksack2 '(30 50 10) '())</run>
[[Bild:Tiefensuche_2.png|200px|thumb|right|Reduzierter Baum mit (30 50 10) für $K = 42$]]
Am reduzierten Suchbaum zu erkennen ist, das lediglich die Blätter (10,30), (30), (10) und () die gewählte Kapazitätsbeschränkung erfüllen. Gesucht ist allerdings der größtmögliche Wert, d.h. bei $w_i = g_i$ wird <math> $\sum_{g_{i} \in s-ls}g_i \rightarrow max \le K</math> gefordert. Da rucksack2 mit dem Aufruf (30 50 10) lediglich eine leere Liste zurückgibt, muss die Prozedur modifiziert werden. Die Prozedur rucksack3 liefert das "Blatt" des Suchbaumes mit dem größtmöglichen Wert des Rucksackinhaltes.
<code id="526e2e358de6e">
;;; Beschraenkte Tiefensuche mit Ergebnisliste
(define rucksack3
(lambda (g-ls k)
(let ((max 0) (max-s-ls '()))
(letrec
((helfer
(lambda (g-ls s-ls) ; (display s-ls)(newline)
(if (null? g-ls)
(let ((gesamtgewicht (apply + s-ls))) ; apply wendet + auf die Liste an.
(if (and
(> gesamtgewicht max)
(hoechstens-maxgewicht? s-ls k))
(begin
(set! max gesamtgewicht)
(set! max-s-ls s-ls)))
(cons max-s-ls max))
(begin
(if (hoechstens-maxgewicht? s-ls k)
(helfer (cdr g-ls) (cons (car g-ls) s-ls)))
(if (hoechstens-maxgewicht? s-ls k)
(helfer (cdr g-ls) s-ls)))))))
(helfer g-ls '())))))
; > (rucksack3 '(30 50 10) 42)
; ((10 30) . 40)
</code>
<run id="526e2e358dfb2">(rucksack3 '(30 50 10) 42)</run>
=Nichtdeterminismus=
Beim Rucksackproblem wird der Suchprozess bei der Tiefensuche als Baum dargestellt. Dieser Baum existiert aber nicht wirklich, d.h. er wird nicht als Datenstruktur repräsentiert.
Der Programmierer denkt in Bäumen, wenn er versucht das Verfahren in seiner Programmiersprache implementiert. Bietet die gewählte Programmiersprache das Mittel Rekursion, kann das die Arbeit weiter vereinfachen. Denn so kann die rekursive Baumstruktur direkt in eine rekursive Programmstruktur abgebildet werden. Trotzdem muss der Programmierer noch viel geistige Energie in die Organisation des Suchprozesses stecken, um einen erfolgreichen Algorithmus zu schreiben. <br> Es wäre von Vorteil nur das potentielle Resultat zu beschreiben und sich nicht um den Prozess zur Findung dieses Ergebnisses kümmern zu müssen. So kommen wir zur deskriptiven Programmierung. <br>
Um das zu realisieren eignen sich nichtdeterministische Algorithmen besonders gut.
Ein nichtdeterministischer Algorithmus "erahnt" den richtigen Pfad von der Wurzel bis zum Resultat-Blatt, falls eins existiert.
Nichtdeterminismus beschreibt eine übernatürliche Fähigkeit, das Richtige vorherzusehen. <br>
Nun könnte man denken, durch den Einsatz des Nichtdeterminismus kann man auch die Effizienz des Algorithmus steigern, dass gelingt aber nur in den seltensten Fällen. Der Nichtdeterminismus wird durch deterministische Sprachmittel simuliert, im Hintergrund findet eine Tiefensuche statt, die bei vollständigem Durchlauf einen exponentiellen Aufwand erfordert.
Der große Vorteil vom Einsatz des Nichtdeterminismus ist die Verbesserung der kognitiven Effizienz, die hilft komplexere Probleme zu lösen.
==Nichtdeterminismus in Scheme==
Um den Nichtdeterminismus in Scheme zu nutzen benötigt man einige Befehle, die man zusätzlich implementieren muss.
'''choice:''' Nimmt eine Liste und gibt "das richtige Element" daraus zurück. Wenn der Befehl eingebettet ist, wird der Wert zurück gegeben, der zum "Erfolg des Gesamtausdrucks" führt.
<run id="526e2fc4d0286">
(= (choice '(3 6 9)) 7)</run>
'''bag-of:''' Nimmt eine Liste und gibt eine Liste der Elemente zurück, die zum Erfolg führen.
<run id="526e2fc4d0333">(bag-of (< (choice '(3 6 9 12 15)) 10))</run>
'''multichoice:''' Nimmt eine Liste $ls$ und eine natürliche Zahl $n$ und gibt die $n$ "richtigen" Elemente aus $ls$ zurück.
<run id="526e2fc4d03d6">(multichoice '(1 2 3 4 5 6 7 8) 4)</run>
'''randomchoice:''' Nimmt eine natürliche Zahl $n$ und berechnet das richtige Element aus der Liste (1 2 3 ... $n$).
'''initialize-amb-fail:''' Diese nullstellige Prozedur (thunk), dient zur Initialisierung des Ambigious-Systems.
==Nichtdeterministische Summandensuche==
Das folgende Programm sucht nach Summanden für eine angegebene ganze Zahl.
<code id="526a659a98442">
(define summanden
(lambda ()
(list (choice '(1 2 3 4 5 6))(choice '(1 2 3 4 5 6)))))
(define sumsuche
(lambda (summe)
(let ((kandi (summanden)))
(if (= (apply + kandi) summe)
kandi (choice '())))))
</code>
<run id="526a659a984dd">
(initialize-amb-fail)
(bag-of (sumsuche 5))
</run>
==Anwendung auf das Rucksackproblem==
Nun nutzen wir die nichtdeterministischen Sprachmittel um unser Rucksackproblem zu lösen.
<code id="526e2fc4d00f3">
(define rucksack4
(lambda (gls k)
(let ((sls (multichoice gls (randomchoice (length gls)))))
(if (<= (apply + sls) k)
sls
(choice '())))))
</code>
<run id="526e2fc4d0476">(initialize-amb-fail)
(rucksack4 '(30 50 10) 45)</run>
Jetzt wird in diese Prozedur noch die Forderung nach der maximalen Gewichtssumme der eingepackten Gegenstände eingebaut.
<code id="526e2fc4d019b">
(define rucksack5
(lambda (gls k)
(let ((max 0) (resultat '()))
(let ((alle-sls (bag-of (rucksack4 gls k))))
(map
(lambda (sls)
(let ((slssumme (apply + sls)))
(if (> slssumme max)
(begin
(set! max slssumme)
(set! resultat sls)))))
alle-sls))
(cons resultat max))))
</code>
<run id="526e2fc4d0516">(initialize-amb-fail)
(rucksack5 '(56 21 91 34 8 43) 172)</run>