AuK/Systematische Suche SS11

Aus ProgrammingWiki

Wiki-Quelltext dieser Seite.

<language>Scheme</language>
;Autoren
:[mailto://s3roluet@stud.hs-zigr.de Ronny Lüttke]<br/>
:[mailto://s3pawein@stud.hs-zigr.de Paul Weinhold]

<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 =

Bei den Rucksackproblemen handelt es sich um Optimierungsprobleme. Betrachtet werden dabei nicht-leere Mengen von '''''n''''' Objekten.<br/>
Jedes Objekt besitzt dabei zwei Kernattribute:

* einen Wert '''''w'''''
* ein Gewicht '''''g'''''

Als Begrenzung dient ein fest definierter Wert, die Kapazitätsbeschränkung '''''K'''''.

Gesucht ist die Menge derjenigen Objekte, die in der Summe einen maximalen Wert bei bestmöglicher Ausnutzung der Kapazität haben.

== Beispiel ==
<span id="img1">[[Bild:Syssuche_1.png|thumb|right|400px|Mögliche Kombinationen [http://programmingwiki.de/images/e/e7/Syssuche_1.png (Original)]]]</span>

Ein LKW einer Spedition ist bis 50t belastbar. Es müssen einige Container transportiert werden:
* Container 1: Gewicht 10, Wert 60
* Container 2: Gewicht 20, Wert 100
* Container 3: Gewicht 30, Wert 120

Die Objektmenge besteht nun aus den folgenden Wertpaaren:

<math>M = \left \{ \left ( 10, 60 \right ), \left ( 20,100 \right ), \left ( 30,120 \right )\right \}</math>

Die Kapazitätsbeschränkung '''''K''''' ist hier 50

<math>K = 50</math>
<br style="clear:both;"/>

== Unterarten ==

;0/1-Rucksackproblem (0-1 knapsack problem)
:Ein Objekt darf nur komplett oder gar nicht in die Ergebnismenge übernommen

;Bruchteil-Rucksack-Problem
:Ein beliebiger Teil '''''t''''' (0% &le; t &le; 100%) jedes Gegenstands darf übernommen werden

Auf dieser Seite beschränken wir uns auf das 0/1-Rucksackproblem. Bruchteil-Rucksack-Probleme werden bei den [[AuK/Greedy-Algorithmen SS11|Greedy-Algorithmen]] besprochen.

== Ziel ==

In mathematischer Schreibweise sieht die oben genannte Zielstellung folgendermaßen aus:

<math>\sum_{i=1}^{n} x_{i}w_{i} \rightarrow max</math>

Wobei folgendes gelten muss:

<math>\sum_{i=1}^{n} x_{i}g_{i} \leq K</math>, mit <math>x_{i} \in {0, 1}</math>


== Teilmengen-Summen-Problem (subset-sum-problem) ==

Hierbei handelt sich um einen Spezialfall des 0/1-Rucksackproblems. 
Das spezielle ist hier, dass nur eine Menge natürlicher Zahlen gegeben ist:

<math>M = \left \{ a_{i} \: | \: 1 \leq i \leq n \: \wedge a_{i},n \in \mathbb{N} \right \}</math>

Gewicht und Wert sind dabei gleich:

<math>g_{i} = w_{i}</math>, mit <math>g_{i}, w_{i} \in \mathbb{N}</math>

Gesucht ist eine Teilmenge dieser Menge deren Summe diesem Grenzwert möglichst nahe kommt, ihn dabei
erreichen, aber nicht erreichen darf.

Praktisch kann dieser Fall benutzt werden um die maximale Beladung eines LKW zu bekommen, 
wenn die Güter in Kisten gleicher Abmessung verpackt sind. Das Fahrzeug darf nicht überlastet werden,
aber die Kapazität sollte auch möglichst gut ausgenutzt werden.

== Intuitives Verfahren ==

Versucht man den Rucksack intuitiv zu befüllen, würde man wohl zuerst zufällig Gegenstände einpacken. 
Anschließend tauscht man je zwei Gegenstände aus. 
Man nimmt einen Gegenstand aus dem Rucksack wieder heraus und legt statt dessen einen höherwertigen zurück.
Dabei beachtet man aber, dass die maximale Kapazität nicht überschritten wird.
Die Frage ob man auch den maximalen Wert erreicht hat, kann aber nicht wirklich beantwortet werden,
da diese Herangehensweise keinerlei System besitzt.

== ein anderer Versuch ==

Man könnte auch annehmen, dass mit  einer „gierigen“ Herangehensweise ein optimales Ergebnis erreicht werden kann. Dabei geht immer der noch verbleibende Container mit dem höchsten Wert pro Gewicht in die Ergebnismenge ein, solange die obere Grenze '''''K''''' nicht erreicht wird. 

<math>q_{c1} = \frac{60}{10} = 6</math>

<math>q_{c2} = \frac{100}{20} = 5</math>

<math>q_{c3} = \frac{120}{30} = 4</math>

Unter dieser Annahme müsste Container 1 ein Teil der Ergebnismenge sein, da er den größten Quotienten aus Wert und Gewicht besitzt.
Die optimale Ergebnismenge enthält Container 2 und 3. Alle Ergebnisse mit Container 1 führen zu einen nicht optimalen Ergebnis, 
obwohl Container 1 das bessere Verhältnis aus Wert und Gewicht aufweist. [[#img1|Abbildung 1]] zeigt deutlich, dass diese Strategie bei
0/1-Rucksackproblemen zu keinem optimalen Ergebnis führt.

Die beiden nachfolgenden Suchstrategien führen stets zu einem optimalen Ergebnis. Es werden jeweils sämtliche Packmöglichkeiten erzeugt und anschließend bewertet. Die maximale Kapazität bleibt dabei vorerst außen vor.

Allerdings können diese Verfahren für große '''''n''''' zu unbeherrschbarem Aufwand führen. Aus diesem Grund werden auch Wege aufgezeigt, 
den Suchraum noch einzuschränken.


= Tiefensuche =

Bei der Tiefensuche wird ein Suchbaum aufgebaut. Bäume sind in diesem Fall Gebilde aus der [http://de.wikipedia.org/wiki/Graphentheorie Graphentheorie], welche aus Knoten, Kanten und Blättern bestehen. Ein solcher Baum wird durchlaufen, wobei zunächst zum nächtmöglichen Folgeknoten weitergegangen wird. 

== Aufbau des Algorithmus ==

#Bestimmung des Startknotens
#Ersten Knoten expandieren und alle Folgeknoten speichern
#Tiefensuche rekursiv auf jeden gespeicherten Folgeknoten anwenden
#Abbruch:
#*bei Fund eines passenden Elements
#*wenn kein Knoten mehr im Speicher ist

== Erläuterung ==
<span id="img2">[[Bild:Tiefsuch.png|thumb|right|400px|Suchbaum für eine Tiefensuche mit 3 Objekten [http://programmingwiki.de/images/3/3b/Tiefsuch.png (Original)]]]</span>

Beim Rucksackproblem entsteht ein Binärbaum, dessen Schichten den einzelnen Objekten zugeordnet werden können. Die Knoten stehen hier für Kombinationen aus Objekten, die Kanten repräsentieren die Entscheidung, ob ein Objekt aufgenommen werden soll (grün) oder nicht (rot). Aufgebaut wird dieser von links her, weshalb man auch von einem ''dynamischen'' Baum spricht

Die Suche geht soweit in die Tiefe, bis ein passender Knoten gefunden wird, oder keine Folgeknoten mehr zur Verfügung stehen. In letzterem Fall wird eine Ebene zurück gesprungen und mit dem nächsten Folgeknoten fortgesetzt. Dieses zurückspringen bezeichnet man als ''Backtracking'' In der untersten Schicht findet man nun alle möglichen Kombinationen der Objekte wieder, insgesamt also <math>2^{n}</math> mögliche Kombinationen.

;Herleitung:
:<math>\sum_{k=0}^{n}\binom{n}{k}z^k = \binom{n}{0} z^0 + \binom{n}{1} z^1 + \cdots  + \binom{n}{n} z^n = (1 + z)^n \xrightarrow{z=1} 2^n</math>
<br style="clear:both;"/>

== Vorgehensweisen ==

Angewandt auf statische binärbäume erhält man, je nachdem, wann der Inhalt notiert wird 3 verschiedene Vorgehensweisen:

*prewalk: Notation beim ersten Durchgang
*centralwalk: Notation beim zweiten Durchgang
*postwalk: Notation beim dritten Durchgang

Handelt es sich um einen Rechenbaum mit 2-stelligen Operationen, so entsprechen die jeweiligen Ergebnisse der Präfix-, Infix- bzw. Postfix-Notation.

== Eigenschaften ==

=== Laufzeit ===

Im schlimmsten Fall müssen alle möglichen Pfade zu allen möglichen Knoten in Betracht gezogen werden. Geht man also davon aus, dass das Überprüfen eines Knotens konstant erfolgt so gilt allgemein <math>\mathcal{O}(\vert V \vert + \vert E \vert)</math>, wobei <math>\vert V \vert</math> für die Summe aller Knoten und <math>\vert E \vert</math> für die Summe aller Kanten steht.

Betrachtet man den Binärbaum, der beim Rucksackproblem entsteht ergibt sich die folgende Laufzeitberechnung:

:<math>\vert V \vert + \vert E \vert</math>

<math>\Rightarrow ( 2^{0} + 2^{1} + 2^{2} + \cdots + 2^{n} ) + ( 2^{1} + 2^{2} + 2^{3} + \cdots + 2^{n} )</math>

<math>\Rightarrow \sum_{i=0}^{n} 2^{i} + \sum_{i=1}^{n} 2^{i}</math>

<math>\Rightarrow 2^{0} + 2 \sum_{i=1}^{n} 2^{i}</math>

<math>\Rightarrow 1 + \sum_{i=1}^{n} 2^{i+1}</math>

<math>\Rightarrow 2 \left ( 2^{n+1} -2 \right )</math>

<math>\Rightarrow 2^{n+2}-3</math>

<math>\Rightarrow 4 \cdot 2^{n} - 3</math>

<math>\Rightarrow \mathcal{O}(2^{n})</math>

Man erhält also im worst-case eine exponentielle Laufzeit. Für viele Objekte kann eine solche Suche einige Zeit in Anspruch nehmen oder sogar unvertretbar werden.

=== Vollständigkeit ===

[[Bild:Tiefcycle.png|thumb|right|Beispiel für eine fehlschlagende Tiefensuche [http://programmingwiki.de/images/b/bb/Tiefcycle.png (Original)]]]
Allgemein hat die Tiefensuche keinen Anspruch auf Vollständigkeit, da es sich bei dem gegebenen Graphen nicht zwangsläufig um einen Baum handeln muss. Da immer zunächst einmal in die nächste Unterebene gegangen wird, kann es passieren, dass sich der Algorithmus einen Endloszyklus erzeugt, wenn keine Zyklenerkennung eingebaut ist.

Da beim Rucksackproblem aber ein Binärbaum mit fester Tiefe und ohne Zyklen entsteht, kann dieser Fall nicht eintreten.

=== Optimalität ===

Dadurch, das die Tiefensuche zunächst die Folgeknoten betrachtet, kann es im allgemeinen Fall passieren, dass ein längerer Pfad gefunden wird, als optimal gewesen wäre. Dieser wird aber im Allgemeinen dafür schneller gefunden.

Bezogen auf das Rucksackproblem ist dies Nebensächlich, da ohnehin alle Möglichkeiten in betracht gezogen werden sollten.

<br style="clear:both;"/>

== Beispiel der Tiefensuche in Scheme ==
<popup caption="Beispiel">
<code id="4dbb1c3e4ff53">
(define rucksack1
  (lambda (g-ls s-ls)
    (if (null? g-ls)
      s-ls
      (begin 
        (rucksack1 (cdr g-ls) (cons (car g-ls) s-ls))
        (rucksack1 (cdr g-ls) s-ls)))))
</code>

<run id="4dbb1c3e4ffb9">
;(trace 'rucksack1)
(rucksack1 '(10 20 30) '())
</run>
</popup>

= Breitensuche =

Die Breitensuche durchläuft einen Baum im Gegensatz zur Tiefensuche, nach dem dem Prinzip "Breite zuerst". Dabei wird der Baum schichtweise aufgebaut.<br>
Angewandt auf das Rucksackproblem stellt jede Schicht alle möglichen Kombinationen von '''''n''''' Elementen im Rucksack dar. Jeder Knoten besitzt für '''''n''''' Nicht-Rucksackgegenstände '''''n''''' Teilbäume.

== Ablauf des Algorithmus ==
    	
#Bestimmung des Startknotens
#Alle vom Startkonten ereichbaren Knoten durchlaufen und in eine Schlange stellen (Breite zuerst)
#Abbruch: bei leerer Schlange
#Ersten Knoten aus der Schlange entnehmen und damit Schritt 1 wiederholen

== Aufbau eines Breitensuchbaums ==

Die [[#img3|Abbildung 3]] zeigt den Aufbau eines Breitensuchbaums angewandt auf das Rucksackproblem. Grün dargestellt ist die Menge aller Gegenstände im Rucksack und rot die Nicht-Rucksackgegenstände während eines Knotens
	
<span id="img3">[[Bild:Breitsuch_all.png|framed|center|'''Abbildung 3:''' Aufbau eines Breitensuchbaums]]</span>

== Anzahl der Knoten ==

Die Anzahl der Knoten ist wie folgt definiert:

<math>1+n+n(n-1)+n(n-1)(n-2)+...+n(n-1)(n-2)(n-3)...(n-(n-1))</math>

<math>= \frac{n!}{0!}+\frac{n!}{1!}+\frac{n!}{2!}+...+\frac{n!}{n!}</math>


<math>= \sum_{k=0}^n \frac{n!}{k!}</math>

== Überflüssige Knoten ==

Bei der Betrachtung der Knoten fällt auf, dass für das Rucksackproblem überflüssige Knoten existieren. Für jeden möglichen Rucksack mit '''''n''''' Gegensänden gibt es <math>n!</math> Knoten, die den gleichen Inhalt repräsentieren. Für das Lösen eines Rucksackproblemes spielt die Reihenfolge der enthaltnenen Gegenstände jedoch keine Rolle. Würde man das Vefahren der Breitensuche in dieser Form für eine große Anzahl von Gegenständen verwenden, so würde es sich schnell als ungeignet erweisen. 

Die Gegenstandsliste müsste in der Breitenbewegung reduziert werden um redundante Ergebnise auszuschließen. Entscheidet man sich für die Aufnahme eines Gegenstandes in den Rucksack, so dürfen die nebenstehenden Knoten der jeweiligen Schicht, diesen nicht mehr in ihrer Gegenstandsliste beinhalten.

Diese Vorgehensweise führt zu einen reduzierten Breitensuchbaum.

= Beschränkung des Suchbaumes =

= Nichtdeterminismus =
<!--(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 '())))))
(initialize-amb-fail)
(bag-of (sumsuche 7))-->

= Übungen =
[[AuK/Systematische Suche Übungen SS11|Zu den Übungen]] (gegebenenfalls Rechtsklick>in neuem Tab/Fenster öffnen)

= Bilder =
[[Bild:Breitsuch_red.png]]
[[Bild:Breitsuch_uni.png]]
[[Bild:Tiefredu.png]]
[[Bild:Breitredu.png]]

= Quellen =

== Literatur ==

;Christian Wagenknecht
:Algorithmen und Komplexität.- Fachbuchverlag Leipzig, 2003. - ISBN 3-446-22314-2
;Thomas H. Cormen, Charles E. Leiserson, Ronald Rivest, Clifford Stein
:Algorithmen - Eine Einführung, Oldenbourg Verlag München Wien, 2. Auflage, 2007. - ISBN 978-3-486-58262-8
;Jochen Ziegenbalg
:Algorithmen - Von Hammurapi bis Gödel, Spektrum Akademischer Verlag Heidelberg Berlin Oxford, 1996. - ISBN 3-8274-0114-3
;Markus von Rimscha
:Algorithmen kompakt und verständlich, Vieweg + Teubner Verlag Wiesbaden, 2008. - ISBN 978-3-8348-0569-0

== Websites ==