Grundlagen der Informatik 2 – Algorithmik Vorlesung 1 und 5

Werbung
Grundlagen der Informatik 2 – Algorithmik
Vorlesung 1 und 5
Prof. Dr. Wolfram Conen
[email protected]
Raum: P -1.08
Version 0.9β , 1. Mai 2005
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Inhalte
• Dijkstra: Korrektheit, Datenstrukturen zur effizienten Implementierung (Priority
Queues, Exkurs: Heap Sort, Radix Sort, Bucketsort); Minimal spannende Bäume
(Union-Find)
• Greedy-Algorithmen, Huffman-Codierung, Hashing
• Algorithmen für uninformierte und informierte Suche nach Problemlösungen
• NP-Vollständigkeit: TSP und andere schwere Probleme; Approximation
• Strings und Suffix-Trees
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Version 0.9β , 1. Mai 2005, Seite 2
Was wir nicht mehr tun können . . .
• (Effiziente) randomisierte Algorithmen zur (exakten oder approximativen) Problemlösung
• Andere Modelle der Berechenbarkeit (Termersetzungssysteme, DNA-Computer
etc.)
• Logik und formal nicht-erkennbare Wahrheiten (Gödel)
• Moderne Logik und das Semantic Web (Description Logic, Temporal and Action
Logics)
• Weitere Grundlegende Algorithmen und Methoden der KI, z.B. zum maschinellen
Lernen, zur symbolischen Wissensverarbeitung, zum Data Mining, zur Sprachverarbeitung etc.
• . . . und viel, viel mehr, das auf den Inhalten unserer GIN1a/1b/2-Veranstaltungen
aufbaut.
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Version 0.9β , 1. Mai 2005, Seite 3
Ziele aus der Modulbeschreibung . . .
• Erkennen der grundlegenden Bedeutung von mathematischen/theoretischen Instrumenten für das Finden und die Analyse von Problemlösungen.
• Beherrschen der grundlegenden Begrifflichkeiten, der wichtigsten Resultate und
der wesentlichen Beweisverfahren.
• Überblicksartige Kenntnisse der grundlegenden theoretischen Resultate und Methoden, die wichtigen Einfluss auf weitere Felder der Informatik haben (z.B. auf
Algorithmik, Sprachen, künstliche Intelligenz).
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Version 0.9β , 1. Mai 2005, Seite 4
Literatur...
Kaufen Sie erstmal nichts! Ich nenne Literatur bei den einzelnen Inhalten!
Für die Jäger und Sammler unter ihnen (zum Nachschlagen für den Schrank) hier
das meist gelobte Buch zu Algorithmen und Datenstrukturen: Cormen, Leierson,
Rivest, Stein Introduction to Algorithms, MIT Press, 2001 (2. Auflage, 1202 Seiten,
ca. 60 Euro)
Für den Blick über den Tellerrand dieser Vorlesung:
• Schöning: Ideen der Informatik, Oldenbourg, 2002
• Gritzmann, Brandenberg: Das Geheimnis des kürzesten Weges, Springer, 2.
Aufl., 2003
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Version 0.9β , 1. Mai 2005, Seite 5
Unterlagen...
Für Skriptversionen, Übungsblätter, aktuelle Nachrichten usw. guckst du wie gehabt
hier:
http://www.informatik.fh-gelsenkirchen.de/conen
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Version 0.9β , 1. Mai 2005, Seite 6
Algorithmik: Dijkstra
Dijkstra revisited (1)
Input: Gewichteter Digraph G = (V, E) mit nicht-negativen Gewichten
Output: Kürzeste s, v -Wege und deren Längen Distanz(v ) für alle v ∈ V
BEGIN S ← {s}, Distanz(s) ← 0
/* Bogenlänge(s, v) = ∞, wenn es keinen Bogen von s nach v gibt */
(1) FOR ALL v ∈ V /{s} DO Distanz(v ) ← Bogenlänge(s, v ); Vorgänger(v ) ← s; END FOR
WHILE S 6= V DO
/* ⇐= Hier auf alle Knoten erweitert! */
(2) finde v ∗ ∈ V \S mit Distanz(v ∗ ) = min{Distanz(v ): v ∈ V \S}; (3) S ← S ∪ {v ∗ }
(4) FOR ALL v ∈ V \S DO
IF Distanz(v ∗ )+Bogenlänge(v ∗ , v ) < Distanz(v ) THEN
Distanz(v ) ← Distanz(v ∗ )+Bogenlänge(v ∗ , v ); Vorgänger(v ) ← v ∗
Satz 1. D IJKSTRAS Algorithmus arbeitet korrekt.
Beweis: Wir zeigen, dass die folgenden Zusicherungen vor jeder Ausführung von (2) und am Ende gelten:
(i) Für alle Knoten v ∈ S und alle Knoten w ∈ V \S gilt Distanz(v ) ≤ Distanz(w).
(ii) Für alle Knoten v ∈ S gilt: Distanz(v ) ist die Länge eines kürzesten s-v -Weges in G. Falls Distanz(v ) < ∞, dann
gibt es einen s-v -Weges der Länge Distanz(v ), dessen letzter Bogen (Vorgänger(v ),v ) ist (außer für v = s) und
dessen Knoten alle in S liegen.
(iii) Für alle Knoten w ∈ V \S ist Distanz(w) die Länge eines kürzesten s-w-Weges im Untergraph W mit den Knoten
VW = S ∪ {w}. Falls Distanz(v ) < ∞, dann Vorgänger(w) ∈ S und Distanz(w) = Distanz(Vorgänger(w) +
Bogenlänge(Vorgänger(w,v )).
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Version 0.9β , 1. Mai 2005, Seite 7
Algorithmik: Dijkstra
Dijkstra revisited (2)
Input: Gewichteter Digraph G = (V, E) mit nicht-negativen Gewichten
Output: Kürzeste s, v -Wege und deren Längen Distanz(v ) für alle v ∈ V
BEGIN S ← {s}, Distanz(s) ← 0
/* Bogenlänge(s, v) = ∞, wenn es keinen Bogen von s nach v gibt */
(1) FOR ALL v ∈ V /{s} DO Distanz(v ) ← Bogenlänge(s, v ); Vorgänger(v ) ← s; END FOR
WHILE S 6= V DO
/* ⇐= Hier auf alle Knoten erweitert! */
(2) finde v ∗ ∈ V \S mit Distanz(v ∗ ) = min{Distanz(v ): v ∈ V \S}; (3) S ← S ∪ {v ∗ }
(4) FOR ALL v ∈ V \S DO
IF Distanz(v ∗ )+Bogenlänge(v ∗ , v ) < Distanz(v ) THEN
Distanz(v ) ← Distanz(v ∗ )+Bogenlänge(v ∗ , v ); Vorgänger(v ) ← v ∗
Beweis (Forts.): (Per Induktion, hier abgekürzt) Klarerweise gelten alle Bedingungen nach der Initialisierung (Induktionsanfang). Wir müssen also noch beweisen,
dass (2),(3) und (4) die Gültigkeit der Bedingungen nicht verletzen.
Anmerkung: Der Algorithmus terminiert natürlich: in jeder Runde wird ein Knoten aus V \S zu S hinzugefügt. Deshalb ist klar, dass die Gültigkeit von (ii) nach
Abschluß des Algorithmus garantiert, dass alle kürzesten Wege gefunden wurden
(denn das genau sicher (ii) zu jedem Zeitpunkt für alle Knoten aus S zu – und in S
sind am Ende eben alle Knoten enthalten!)
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Version 0.9β , 1. Mai 2005, Seite 8
Algorithmik: Dijkstra
Dijkstra revisited (3)
Input: Gewichteter Digraph G = (V, E) mit nicht-negativen Gewichten
Output: Kürzeste s, v -Wege und deren Längen Distanz(v ) für alle v ∈ V
BEGIN S ← {s}, Distanz(s) ← 0
/* Bogenlänge(s, v) = ∞, wenn es keinen Bogen von s nach v gibt */
(1) FOR ALL v ∈ V /{s} DO Distanz(v ) ← Bogenlänge(s, v ); Vorgänger(v ) ← s; END FOR
WHILE S 6= V DO
/* ⇐= Hier auf alle Knoten erweitert! */
(2) finde v ∗ ∈ V \S mit Distanz(v ∗ ) = min{Distanz(v ): v ∈ V \S}; (3) S ← S ∪ {v ∗ }
(4) FOR ALL v ∈ V \S DO
IF Distanz(v ∗ )+Bogenlänge(v ∗ , v ) < Distanz(v ) THEN
Distanz(v ) ← Distanz(v ∗ )+Bogenlänge(v ∗ , v ); Vorgänger(v ) ← v ∗
(Wir nehmen nun an, dass die Bedingungen vor der Ausführung von (2) gegolten
haben und zeigen, dass sie dann auch nach (2),(3) und (4) gelten!)
Sei nun v ∗der Knoten, der im Schritt (2) ausgewählt wird (das gilt für den ganzen
Rest des Beweises!)
Zu (i): Für jedes v ∈ S und jedes w ∈ V \S gilt Distanz(v ) ≤ Distanz(v ∗) ≤
Distanz(w) wg. (i) und der Art der Auswahl in (2), also gilt (i) nach (3) und (4) weiterhin.
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Version 0.9β , 1. Mai 2005, Seite 9
Algorithmik: Dijkstra
Dijkstra revisited (4)
Input: Gewichteter Digraph G = (V, E) mit nicht-negativen Gewichten
Output: Kürzeste s, v -Wege und deren Längen Distanz(v ) für alle v ∈ V
BEGIN S ← {s}, Distanz(s) ← 0
/* Bogenlänge(s, v) = ∞, wenn es keinen Bogen von s nach v gibt */
(1) FOR ALL v ∈ V /{s} DO Distanz(v ) ← Bogenlänge(s, v ); Vorgänger(v ) ← s; END FOR
WHILE S 6= V DO
/* ⇐= Hier auf alle Knoten erweitert! */
(2) finde v ∗ ∈ V \S mit Distanz(v ∗ ) = min{Distanz(v ): v ∈ V \S}; (3) S ← S ∪ {v ∗ }
(4) FOR ALL v ∈ V \S DO
IF Distanz(v ∗ )+Bogenlänge(v ∗ , v ) < Distanz(v ) THEN
Distanz(v ) ← Distanz(v ∗ )+Bogenlänge(v ∗ , v ); Vorgänger(v ) ← v ∗
Hält (ii) nach (3) (und damit auch nach (4), denn dort wird nichts mehr an S verändert)? Um das zu prüfen, reicht
es wg. der Gültigkeit von (iii) vor (3) zu zeigen, dass kein s − v ∗ -Pfad, der einen beliebigen Knoten w aus V \S enthält,
kürzer sein kann, als Distanz(v ∗ ). Nehmen wir also an, dass ein solcher Pfad P mit w doch existieren würde und sei w
der erste Knoten ausserhalb von S , auf den wir bei der Reise von s nach v ∗ treffen.
Weil (iii) vor (3) galt, ist Distanz(w) ≤ Pfadkosten(P[s,w] ).1 Weil die Bogengewichte nicht-negativ sind, gilt
Pfadkosten(P[s,w] ) ≤ Pfadkosten(P ) < Distanz(v ∗ ). Das impliziert aber Distanz(w) < Distanz(v ∗ ), im Widerspruch
zur Wahl von v ∗ in (2) (hier wird versteckt nochmal (iii) verwendet: die Distanz von v ∗ war vor (2) minimal unter allen
Knoten aus V \S ).
1P
[s,w] ist der Teil des Weges P , der von s nach w führt, s. auch Übungen zu GIN1b (eindeutig, weil P ein Weg ist).
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Version 0.9β , 1. Mai 2005, Seite 10
Algorithmik: Dijkstra
Dijkstra revisited (5)
Input: Gewichteter Digraph G = (V, E) mit nicht-negativen Gewichten
Output: Kürzeste s, v -Wege und deren Längen Distanz(v ) für alle v ∈ V
BEGIN S ← {s}, Distanz(s) ← 0
/* Bogenlänge(s, v) = ∞, wenn es keinen Bogen von s nach v gibt */
(1) FOR ALL v ∈ V /{s} DO Distanz(v ) ← Bogenlänge(s, v ); Vorgänger(v ) ← s; END FOR
WHILE S 6= V DO
/* ⇐= Hier auf alle Knoten erweitert! */
(2) finde v ∗ ∈ V \S mit Distanz(v ∗ ) = min{Distanz(v ): v ∈ V \S}; (3) S ← S ∪ {v ∗ }
(4) FOR ALL v ∈ V \S DO
IF Distanz(v ∗ )+Bogenlänge(v ∗ , v ) < Distanz(v ) THEN
Distanz(v ) ← Distanz(v ∗ )+Bogenlänge(v ∗ , v ); Vorgänger(v ) ← v ∗
Noch zu zeigen ist, dass (iii) nach (2),(3) und (4) gilt: Falls für ein w aus V \S in (4) der Vorgänger auf v ∗ und
die Distanz auf Distanz(v ∗ ) + Bogenlänge(v ∗ ,w) gesetzt wird (also ein Update erfolgt), dann existiert ein s-w-Weg im
Teilgraph H mit den Knoten VH = S ∪ {w} der Länge Distanz(v ∗ ) + Bogenlänge(v ∗ ,w) mit dem letzten Bogen (v ∗ , w)
(achten sie wieder darauf, dass (iii) für v ∗ galt). Gilt nun nach dem Update (iii) weiter für alle w aus V \S ?
Nehmen Sie an, dass es nach (3) und (4) ein w und einen s-w-Weg P im Teilgraph H mit VH = S ∪ {w} gibt, der
kürzer ist, als Distanz(w) (also (iii) verletzt). P muß nun v ∗ enthalten (nur dieser Knoten wurde zu S hinzugefügt), sonst
wäre (iii) bereits vor (3) verletzt gewesen (achten sie darauf, dass Distanz(w) niemals ansteigt)!
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Version 0.9β , 1. Mai 2005, Seite 11
Algorithmik: Dijkstra
Dijkstra revisited (6)
Input: Gewichteter Digraph G = (V, E) mit nicht-negativen Gewichten
Output: Kürzeste s, v -Wege und deren Längen Distanz(v ) für alle v ∈ V
BEGIN S ← {s}, Distanz(s) ← 0
/* Bogenlänge(s, v) = ∞, wenn es keinen Bogen von s nach v gibt */
(1) FOR ALL v ∈ V /{s} DO Distanz(v ) ← Bogenlänge(s, v ); Vorgänger(v ) ← s; END FOR
WHILE S 6= V DO
/* ⇐= Hier auf alle Knoten erweitert! */
(2) finde v ∗ ∈ V \S mit Distanz(v ∗ ) = min{Distanz(v ): v ∈ V \S}; (3) S ← S ∪ {v ∗ }
(4) FOR ALL v ∈ V \S DO
IF Distanz(v ∗ )+Bogenlänge(v ∗ , v ) < Distanz(v ) THEN
Distanz(v ) ← Distanz(v ∗ )+Bogenlänge(v ∗ , v ); Vorgänger(v ) ← v ∗
Sei nun v der Vorgänger von w in P . Da v ∈ S ist, wissen wir wg. (i), dass Distanz(v ) ≤ Distanz(v ∗ ) (v ∗ wurde zuletzt
ausgewählt, war also vor (2) in V \S und da galt ja (i) auch schon mit v ∈ V !) und dass Distanz(w) ≤ Distanz(v ) +
Bogenlänge(v, w) (wäre es anders hätte (4) bereits in früheren Runden zu einem Update führen müssen!). Wir schließen:
Distanz(w) ≤ Distanz(v ) + Bogenlänge(v, w) ≤ Distanz(v ∗ ) + Bogenlänge(v, w) ≤ Pfadkosten(P).
Die letzte Ungleichung gilt, weil wg. (ii) Distanz(v ∗ ) die Länge einen kürzesten s-v ∗ -Weges in S ist und weil P einen sv ∗ -Weg und den Bogen (v, w) enthält. Aber natürlich steht Distanz(w) ≤ Pfadkosten(P ) im Widerspruch zur Annahme,
dass P kürzer ist, als Distanz(w) – also kann es einen solchen Weg P nicht geben (also gilt (iii) nach (3) und (4)!). q.e.d.
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Version 0.9β , 1. Mai 2005, Seite 12
Algorithmik: Heap und Co. (für Dijkstra)
Bucket/Radix Sort, PQueues, Heaps
[s. PPT-Präsentationen bzw. die zugehörigen PDF-Files]
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Version 0.9β , 1. Mai 2005, Seite 13
GIN2 – 2. Vorlesung, SS04
Prof. Dr. Wolfram Conen
29.3.2005
Rund um Dijkstra:
- Count/Bucket/Radix-Sort
- Priority Queues/Heap
26.04.2005
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
1
Schnelles Sortieren
6 12 5 9 10 11 1 2 4 8 3 7
1 2 3 4
5 6
7 8
9 10 11 12
Situation: n (Schlüssel-)Werte aus [1,n]
Keine Duplikate.
Kosten? O(n)
26.04.2005
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
2
Schnelles Sortieren: (einfacher) BucketSort
6 12 5 9 6 10 11 1
Töpfe:
Ergebnis:
1
6
5 6
9 10 11 12
1 5 6 6 9 10 11 12
Situation: m Töpfe, Schlüsselwerte aus [1,m], Duplikate
26.04.2005
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
3
Schneller Sortieren: BucketSort in
mehreren Phasen (Radixsort)
Situation: n Werte aus [0,L,mk-1], Duplikate möglich
Kosten normaler Bucketsort: O(n+mk)
Idee: Wir wenden ihn mehrfach an!
Beispiel: n Werte aus [0,L,m2-1]
1. Phase: Werti einfügen in Bk mit k = Werti MOD m
2. Phase: Ergebnisliste durchlaufen,
Werti nach Bk mit k = Werti DIV m,
dort am ENDE anfügen
(allgemein: 1. MOD m, 2. DIV m, 3. DIV m2 usw.)
26.04.2005
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
4
Schneller Sortieren: BucketSort in
mehreren Phasen
Beispiel: n = m = 10, Werte aus [0,L,99], 1. Phase
3 18 24 6 47 7 56 34 98 60
3 MOD 10 = 3
18 MOD 10 = 8
Töpfe:
60
24
3 34
6 47 18
56 7 98
Ergebnis 1. Phase: 60 3 24 34 6 5 47 7 18 98
26.04.2005
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
5
Schneller Sortieren: BucketSort in
mehreren Phasen
Beispiel: n = 10, Werte aus [0,L,99], 2. Phase
60 3 24 34 6 56 47 7 18 98
60 DIV 10 = 6
18 DIV 10 = 1
Töpfe:
Ergebnis:
26.04.2005
3
6
7 18 24 34 47 56 60
98
3 6 7 18 24 34 47 56 60 98
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
6
Radixsort aus anderer Sicht...
„
Zahlen werden nach
Zifferpositionen sortiert
„
Zuerst nach der Ziffer ganz
rechts (der am wenigsten
signifikanten Ziffer)
„
Dann rücken wir nach links...
„
In den Phasen wird „stabil“
sortiert, d.h. die relative
Reihenfolge bei Gleichstand
ändert sich nicht (sonst geht es
nicht!)
26.04.2005
329
457
657
839
436
720
355
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
720
355
436
457
657
839
329
720
329
436
839
355
457
657
329
355
436
457
657
720
839
7
Bucket/Radix Sort: Review
„
Wenn wir die Größe des Schlüsselbereichs als Konstante
ansehen, dann sortieren wir zu Kosten von O(n) (mit
möglicherweise großen Konstanten!)
„
Aus einer sortieren Folge können wir zu Kosten von O(1) das
minimale Element finden und entnehmen
„
Aber Dijkstra verändert ja auch noch die Distanz-Werte der
Knoten...
26.04.2005
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
8
Priority Queues
„
INSERT: Warteschlangen, in die Elemente gemäß einer Priorität
eingeordnet werden
„
DELETE MIN: Es wird jeweils das Element höchster Priorität
entnommen (das soll das Element mit dem
minimalen Wert sein)
„
Für uns noch wichtig: DECREASE KEY – der Wert eines
Knotens verringert sich!
„
Das sind genau die Operationen, die wir im Dijkstra brauchen:
Initialer Aufbau des Queues (INSERT), Updates der Knotenwerte
(DECREASE KEY), Entnahme des „besten“ Knotens (DELETE
MIN)
26.04.2005
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
9
Priority Queues
„
Q ← INSERT(Q,v): Füge Knoten v mit Wert Wert(v) in
Priority-Queue Q ein
„
(Q,v*) ← DELETE MIN(Q): Liefere den Knoten mit dem
minimalen Wert und lösche ihn aus dem Priority-Queue Q,
liefere Q zurück
„
Q ← DECREASE KEY(Q,v,Wert): Verringere den Wert des
Knotens v auf Wert.
„
Anmerkung: Normalerweise geben wir Q niccht explizit
zurück, weil wir nur auf einer Queue arbeiten
26.04.2005
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
10
Priority Queues: Implementierung
„
Wie kann man das effizient implementieren?
„
Z.B. mittels eines sogenannte Heaps! (wir betrachten zunächst
nur die Operationen INSERT und DELETE MIN)
„
Was ist ein Heap (=Haufen)?
Das ist ein partiell-geordneter Baum:
Definition: Ein partiell-geordneter (binärer) Baum ist ein binärer
Wurzelbaum T, in dem für jeden Teilbaum T´ mit Wurzel w gilt:
∀ y ∈ T´: Wert(w) · Wert(y)
26.04.2005
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
11
Partiell-geordneter Baum
(Schlüssel-)Werte: 4 6 6 7 10 10 12 13 13 19
4
6
10
12
13
6
19
13
10
7
Alle Wurzeln erfüllen die Bedingung! Ist der Baum eindeutig? Nein.
26.04.2005
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
12
Partiell-geordneter Baum
(Schlüssel-)Werte: 4 6 6 7 10 10 12 13 13 19
4
6
10
12
13
6
19
10
7
13
Alle Wurzeln erfüllen die Bedingung!
Aber der Baum ist nicht mehr „balanciert“!
26.04.2005
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
13
Heap: INSERT
„
Wir betrachten linksvollständige partiell
geordnete Bäume:
‰ alle Ebenen bis auf
die letzte sind voll
besetzt
‰ auf der letzten Ebene
sitzen die Knoten
soweit links wie
möglich
26.04.2005
Algorithm INSERT(Q,v)
Füge v auf der ersten freien
Position der untersten Ebene
ein (wenn voll, neue Ebene
beginnen)
p ← Vater(v)
Solange p existiert und Wert(v)
< Wert(p) tue
Vertausche die Werte
von p und v;
v ← p; p ← Vater(p)
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
14
Heap: INSERT
Algorithm INSERT(Q,v)
Füge v auf der ersten freien
Position der untersten Ebene
ein (wenn voll, neue Ebene
beginnen)
p ← Vater(v)
Solange p existiert und Wert(v) <
Wert(p) tue
Vertausche die Werte
von p und v;
v ← p; p ← Vater(p)
Einfügen von 5
4
6
p→ 6
12
13
10
19
7
13
5
10
←v
Wert(v) < Wert(p)? Klar!
Also: Vertauschen!
26.04.2005
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
15
Heap: INSERT
Algorithm INSERT(Q,v)
Füge v auf der ersten freien
Position der untersten Ebene
ein (wenn voll, neue Ebene
beginnen)
p ← Vater(v)
Solange p existiert und Wert(v) <
Wert(p) tue
Vertausche die Werte
von p und v;
v ← p; p ← Vater(p)
Einfügen von 5
4
6 ←p
v→ 5
12
13
10
19
7
13
10
6
Wert(v) < Wert(p)? Klar!
Also: Vertauschen!
26.04.2005
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
16
Heap: INSERT
Algorithm INSERT(Q,v)
Füge v auf der ersten freien
Position der untersten Ebene
ein (wenn voll, neue Ebene
beginnen)
p ← Vater(v)
Solange p existiert und Wert(v) <
Wert(p) tue
Vertausche die Werte
von p und v;
v ← p; p ← Vater(p)
Einfügen von 5
p→
4
5 ←v
12
13
10
6
19
7
13
10
6
Wert(v) < Wert(p)? Nein!
Also: Fertig!
26.04.2005
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
17
Heap: INSERT
Algorithm INSERT(Q,v)
Füge v auf der ersten freien
Position der untersten Ebene
ein (wenn voll, neue Ebene
beginnen)
p ← Vater(v)
Solange p existiert und Wert(v) <
Wert(p) tue
Vertausche die Werte
von p und v;
v ← p; p ← Vater(p)
26.04.2005
Ist INSERT korrekt?
Wir betrachten eine einzelne
Vertauschung der Werte von v und
p, es gilt also Wert(v) < Wert(p).
Wert(p) ist minimal bzgl. aller
Unterbäume von p (und damit aller
Unterbäume von v – das gilt auch
nach dem Positionswechsel!)
Wg. Wert(v) < Wert(p) ist dann
auchWert(v) nach Vertauschung
minimal für alle Unterbäume, also
ist der neue Baum partiell geordnet
(unter der Annahme, dass der
Ausgangsbaum partiell geordnet
war).
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
18
Heap: DELETE MIN
Algorithm DELETE MIN (Q): v*
Sei w die Wurzel des Heaps mit den
Söhnen sl, sr;
v* ← w;
Sei k der letzte Knoten (unten, rechts)
Wert(w) ← Wert(k); Lösche k;
Solange sl oder sr existieren und
(Wert(w) > Wert(sl) oder Wert(w) >
Wert(sr)) tue
Vertausche den Wert von w mit
dem Wert des Sohn mit dem
kleineren Wert, dieser Sohn sei s;
w ← s;
sl ← Linker_Sohn(w);
sr ← Rechter_Sohn(w)
Gib Q und Min zurück.
26.04.2005
Entfernen des Minimums:
w→
4
5
10
12
13
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
6
19
7
13
10
6 ←k
19
Heap: DELETE MIN
Algorithm DELETE MIN (Q): v*
Entfernen des Minimums:
Sei w die Wurzel des Heaps mit den
Söhnen sl, sr;
v* ← w;
w→ 6
Sei k der letzte Knoten (unten, rechts)
Wert(w) ← Wert(k); Lösche k;
s=
Solange sl oder sr existieren und
sr → 10
sl → 5
(Wert(w) > Wert(sl) oder Wert(w) >
Wert(sr)) tue
Vertausche den Wert von w mit
12
6
13
10
dem Wert des Sohn mit dem
kleineren Wert, dieser Sohn sei s;
w ← s;
13
19 7
sl ← Linker_Sohn(w);
sr ← Rechter_Sohn(w)
Bedingung für s = sl erfüllt!
Gib Q und Min zurück.
Also: Tauschen
26.04.2005
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
20
Heap: DELETE MIN
Algorithm DELETE MIN (Q): (Q, Min)
Entfernen des Minimums:
Sei w die Wurzel des Heaps mit den
Söhnen sl, sr;
v* ← w;
5
Sei k der letzte Knoten (unten, rechts)
Wert(w) ← Wert(k); Lösche k;
Solange sl oder sr existieren und
w→ 6
10
(Wert(w) > Wert(sl) oder Wert(w) >
Wert(sr)) tue
Vertausche den Wert von w mit
sl → 12 sr → 6
13
10
dem Wert des Sohn mit dem
kleineren Wert, dieser Sohn sei s;
w ← s;
13
19 7
sl ← Linker_Sohn(w);
sr ← Rechter_Sohn(w)
Bedingung nicht erfüllt!
Gib Q und Min zurück.
Also: Fertig!
26.04.2005
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
21
Heap: DELETE MIN
Algorithm DELETE MIN (Q): (Q, Min) Ist DELETE MIN korrekt?
Sei w die Wurzel des Heaps mit den
Wir betrachten eine einzelne
Söhnen sl, sr;
vertauschung der Werte von w und
s, es gilt also Wert(s) < Wert(w).
v* ← w;
Sei k der letzte Knoten (unten, rechts)
Wert(s) ist minimal bzgl. aller
Wert(w) ← Wert(k); Lösche k;
Unterbäume von s. Es wurde
Solange sl oder sr existieren und
ausgewählt, also ist es auch
(Wert(w) > Wert(sl) oder Wert(w) >
minimal im Vergleich zum anderen
Wert(sr)) tue
Kind-Baum – das gilt auch nach
Vertausche den Wert von w mit
dem Positionswechsel!)
dem Wert des Sohn mit dem
kleineren Wert, dieser Sohn sei s;
w ist möglicherweise nicht minimal
w ← s;
für seinen Unterbaum. Das wird
sl ← Linker_Sohn(w);
aber weiterbehandelt (w sinkt dann
sr ← Rechter_Sohn(w)
weiter!) bis schließlich Wert(w) ·
Wert(sl) und Wert(w) · Wert(sr).
Gib Q und Min zurück.
26.04.2005
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
22
Priority Queue
„
Mit dem Heap lassen sich INSERT und DELETE MIN mit
Aufwand O(log n) realisieren!
„
Das gleiche gilt für Updates, also DECREASE KEY-Operationen
(analog zu INSERT plus schnelles Auffinden, kommt noch)!
„
Damit können wir (für sparsam besetzte Graphen) Dijkstra
verbessern! (Wenn die Graphen Kanten in der Größenordnung
von O(n2) haben, also z.B. jeder Knoten eine Kante zu jedem
anderen Knoten hat, dann hilft alles nichts im Worst-Case, weil
dann das Update bzw. die Anzahl der Vergleiche die Kosten
dominiert)
„
Wie es noch besser geht: spätere Veranstaltung
26.04.2005
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
23
(Allgemeiner) Bucket Sort
„
Was ist, wenn wir viel mehr mögliche Werte, als zur Verfügung
stehende Töpfe haben?
„
Situation:
‰
‰
‰
„
Idee: Wir teilen [0..k-1] in m Töpfe auf.
‰
„
z.b. k = 100, m = 5, Breite k/m, also 20
Jeder Topf steht für einen bestimmten Bereich
‰
„
n INTEGER aus dem Wertebereich [0,..,k-1],
Platz für m Töpfe,
k >> m (d.h. k ist deutlich größer als m).
T1: [0..19], T2: [20..39], ...
Dann füllen wir die Töpfe und sortieren diese!
26.04.2005
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
24
Schnelles Sortieren: BucketSort
Töpfe: 5, Werte 10, Wertebereich [0..99]
60 16 48 91 12 25 36 89 23 21
[0..19]
Töpfe:
26.04.2005
[20..39] [40..59] [60..79] [80..99]
16
12
25
36
23
21
48
60
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
91
89
25
(Allgemeiner) Bucket Sort: Aufwand
„
„
Finden des richtigen Buckets für einen Wert v durch Vergleich
mit den Bucketgrenzen:
Naiv:
‰
‰
‰
Laufe mit i von Bucket 1 bis m,
Vergleiche den Wert v mit der oberen Bucketgrenze UB(Bi)
Falls v <= UB(Bi), dann gehört v in Bi, stop; sonst weitersuchen
„
Worstcase für einen einzelnen Wert: m Vergleiche (bzw. m-1,
denn man muß nur bis m-1 laufen)
„
Maximal also n*m Vergleiche, bis alle Werte ihren Platz
gefunden haben (im letzten oder vorletzen Bucket)
26.04.2005
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
26
(Allgemeiner) Bucket Sort: Aufwand
„
„
Finden des richtigen Buckets für einen Wert v durch Vergleich
mit den Bucketgrenzen:
Besser: Binäre Suche
‰
‰
‰
‰
1: Setze aktuelles Bucketintervall [l..r] auf [1..m]
2: Falls l == r, stoppe (richtiges Intervall gefunden!)
3: Bestimme den mittleren Bucket, Bm, m = d (r+1-l)/2 e+(l-1) des
Intervalls
3: Falls v · UB(Bm), dann suche weiter in [l..m] „unten“
sonst in [m+1..r] „oben“ (Update der Grenzen, zurück zu 2)
„
Aufwand für einen Wert: O(log2(m)), für alle n*O(log2(m))
„
Allerdings immer! (Best case oben: alle im ersten Intervall, O(n))
26.04.2005
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
27
(Allgemeiner) Bucket Sort: Aufwand
„
„
„
„
„
„
„
„
„
„
Binäre Suche am Beispiel: 10 Buckets [0..9],[10..19], ..., [90..99]
m = 10, k = 100, Breite k/m = 10, Wert v = 27
Bucketintervall [1..10], Mittlerer Bucket: d (10+1-1) / 2 e = d 5 e = 5
Upper Bound von Bucket 5 = UB(B5) = 49, 27 <= 49? JA
Bucketintervall [1..5], Mittlerer Bucket: d (5+1-1) / 2 e = d 3 e = 3
Upper Bound von Bucket 3 = UB(B3) = 29, 27 <= 29? JA
Bucketintervall [1..3], Mittlerer Bucket: d (3+1-1) / 2 e = d 2 e = 2
Upper Bound von Bucket 2 = UB(B2) = 19, 27 <= 19? NEIN
Bucketintervall [3..3], FERTIG.
Aufwand: 3 Vergleiche von v mit Bucketgrenzen, O(log m)
3
4
‰ Zweierlogarithmus von 10 liegt zwischen 3 und 4, 2 = 8, 2 = 16
26.04.2005
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
28
(Allgemeiner) Bucket Sort: Aufwand
„
Noch besser: Wir können den Bucket, in den v gehört, direkt
ausrechnen!
‰
‰
‰
„
„
„
„
„
Bucketindex i = (v DIV m)+1, also ganzzahlige Division
+1, weil wir hier von 1 bis m zählen, bei 0..m nicht +1.
z.B. (27 DIV 10)+1 = 2+1 = 3
Kosten konstant, O(1)
Man kann die Bucketaufteilung auch anders gestalten, z.B. mehr
Buckets zwischen 0..49, wenn dort die meisten Werte landen,
usw. (sofern man etwas über die VERTEILUNG der Werte weiß)
Je nach Anzahl der Buckets und der Wertverteilung können auch
andere Möglichkeiten, den Bucket für einen Wert v zu
bestimmen, sinnvoll sein (s. Hashfunktionen, späteres Kapitel)
Eine genaue Analyse der Durchschnittskosten hängt i.A. von der
Wertverteilung und dem Verteilen der Werte auf die Buckets ab
Um einen einzelnen Bucket „online“ zu sortieren, eignet sich ein
HEAP ganz hervorragend!
26.04.2005
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
29
(Allgemeiner) Bucket Sort: Aufwand
„
Zum Platzfinden kommt der Aufwand für das Speichern in den
Buckets, O(m+n) Speicher, O(m+n) Zeit
‰
„
m für die Zeiger/Update auf die Listen, n Einträge in den Listen
(konstante Kosten für Anfügen am Beginn oder am Ende, wenn man
zwei Verweise auf Anfang und Ende vorhält).
Und die Kosten für das Sortieren der einzelnen Buckets!
‰
‰
‰
Worst-Case: alle Werte in einem Bucket, O(n log n) (z.B. mit
Quicksort oder Heapsort)
Best-Case: n <= m, n gleichmäßig verteilt: Sortieren kostet nichts!
Average Case: hängt von der Verteilung der Werte auf die Buckets
ab, wenn wir eine Gleichverteilung (Best Case!) annehmen, dann:
„
„
„
ist d n / m e = A die Anzahl der Elemente in einem Bucket, Sortierkosten
dann m * (A log A)
HINWEIS: Wenn wir log A schreiben, dann meinen wir Logarithmus zur
Basis 2, es sei denn, wir geben etwas anderes an
Gesamt O(n)+O(m+n)+Sortierkosten, wird durch Maximum von
O(m+n) und Sortierkosten (O(n log n) im Worst Case) dominiert
26.04.2005
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
30
Weitere Alternative: Counting Sort
„
„
n INTEGER, Werte aus [0..k], k = O(n)
Idee: Wir zählen zu jedem Wert i aus 0..k, wie oft er in der
Eingabe vorkommt, das halten wir in einem Array C fest.
‰
„
Dann bestimmen wir zu jedem Wert i aus 0..k, wieviele Werte in
der Eingabe kleiner oder gleich i sind, wir verändern C
entsprechend
‰
„
Wenn es keine Duplikate gibt, ist das Overkill, dann geht auch das
direkte sortierte Ablegen auf der ersten Folie!
Um auch mit Duplikaten umgehen zu können, dekrementieren
wir C[v] um eins, sobald v abgelegt wurde
‰
„
Kosten Θ(k)
Wenn alle Werte aus der Eingabe verschieden sind, dann gibt
C[v] für jeden Wert v die Position im Ergebnisarray an
‰
„
Kosten Θ(k) für Initialisierung auf 0, Θ(n) für das Zählen
Kosten Θ(n)
Gesamtkosten Θ(k + n), for k = O(n) also Θ(n)
26.04.2005
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
31
Counting Sort
„
„
„
„
„
„
„
n = 8, k = 5
Eingabe:
Eingabe- und Ausgabearray
laufen von 1 bis n.
C:
Counting Sort:
C auf 0 initialisieren
Vorkommen zählen
Anzahl <= bestimmen
Eingabe sortieren
Resultat:
26.04.2005
2 5 3 0 2 3 0 3
0
0
2
2
01
1
0
0
2
0 0
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
2
0
2
3
42
2
3
0
3
7
46
4
0
0
7
2 3
3
5
0
1
8
77
3
5
32
Counting Sort ist STABIL
„
Stabiles Sortieren:
‰ Eingabezahlen mit dem gleichen Wert behalten in der Ausgabe
die relative Reihenfolge ihres Auftretens in der Eingabe bei.
(Counting Sort ist genau „umgekehrt“ stabil...man kann aber auch
von rechts die Eingabe in das Ergebnisarray einsortieren, dann
bleibt die relative Reihenfolge, z.B. der 2er, „stabil“)
‰ Normalerweise nur wichtig, wenn an dem Schlüssel noch weitere
Daten hängen und diese nach einem weiteren Kriterium
„vorsortiert“ sind, z.B. Adressensortieren zuerst nach
Hausnummer, dann nach Strassennamen
„
‰
26.04.2005
macht man natürlich schlauer durch Gruppierung: zuerst
nach Strassennamen, dann Gruppenweise nach
Hausnummer – mit einem stabilen Sortierverfahren geht
es aber so wie oben direkt ohne Gruppenbildung
Für uns aber eine wichtige Eigenschaft beim Radixsort!
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
33
Übungen
„
Abgabe der Übungszettel Pflicht für Studenten des ZWEITEN
Semesters
„
Regeln:
‰ 3*nicht abgegeben: Gespräch, ggfs. keine Zulassung,
‰ 4*nicht abgegeben: keine Prüfung
‰ Verspätetes Nachreichen möglich für 2 Blätter
„
Diplom-Studenten sollten diese Veranstaltung nicht hören!
‰ Erste Prüfungsmöglichkeit wäre das nächste WS (solange
werden für Diplomer noch die alten Inhalte geprüft)
26.04.2005
(c) W. Conen, FH GE, GIN2, SS05,
Vorlesung 2, Version 1.0alpha
34
GIN2 – 4. Vorlesung, SS05
Prof. Dr. Wolfram Conen
19.4.2005
Rund um Dijkstra:
- Kosten, Dials Variante
26.04.2005
(c) W. Conen, FH GE, GIN2
1
GIN2 – 3. Vorlesung, SS05
Prof. Dr. Wolfram Conen
12.4.2005
Rund um Dijkstra:
- Heap-Implementierung mit Arrays
- Bottom-Up-Heaps
01.04.2004
(c) W. Conen, FH GE, GIN2
1
Heap-Implementierung mit Arrays
„
Zur Erinnerung: Was ist ein Heap (=Haufen)?
Das ist ein partiell-geordneter Baum:
Definition: Ein partiell-geordneter (binärer) Baum ist ein binärer
Wurzelbaum T, in dem für jeden Teilbaum T´ mit Wurzel w gilt:
∀ y ∈ T´: Wert(w) · Wert(y)
„
Dies ist ein Min-Heap, auch ·-Heap.
„
In Max-Heaps bzw. ≥-Heap gilt Wert(w) ≥ Wert(y), d.h. der Wert
jeder Wurzel eines Teilbaums ist größergleich den Werten unter ihr.
„
Ein Heap kann Priority-Queues unmittelbar implementieren!
„
Aber wie implementiert man einen Heap?
01.04.2004
(c) W. Conen, FH GE, GIN2
2
Partiell-geordneter Baum
(Schlüssel-)Werte: 4 6 6 7 10 10 12 13 13 19
4
6
10
12
13
6
19
13
10
7
Hier unser Heap aus der letzen Vorlesung als Baum...
01.04.2004
(c) W. Conen, FH GE, GIN2
3
Partiell-geordneter Baum
(Schlüssel-)Werte: 4 6 6 7 10 10 12 13 13 19
4
6
12
13
19
Idee: Kinder von
Position i sind
an Pos. 2i und Pos. 2i+1
10
6
13
10
7
Und hier als Array:
4 6 10 12 6 13 10 13 19 7
Pos. 1
01.04.2004
2 3
4
(c) W. Conen, FH GE, GIN2
5 6 7
8 9 10
4
Heap: INSERT
„
Wir betrachten links-vollständige partiell geordnete Bäume:
‰
‰
alle Ebenen bis auf die letzte sind voll besetzt
auf der letzten Ebene sitzen die Knoten soweit links wie möglich
Algorithm INSERT(Q,v)
Füge v auf der ersten freien Position
der untersten Ebene ein (wenn
voll, neue Ebene beginnen)
p ← Vater(v)
Solange p existiert und
Wert(v) < Wert(p) tue
Vertausche die Werte
von p und v;
v ← p; p ← Vater(p)
01.04.2004
Nun mit Array, nennen wir es H.
⇐ Das ist immer eins mehr, als das
Ende des Arrays (also: erweitern!)
Für gerade Pos. v ist p = i/2, sonst (i1)/2 für i>1 bzw. nichts für Wurzel
⇐ Falls H[i].wert < H[p].wert tue
hilf←H[i].wert; H[i].wert←H[p].wert;
H[p].wert←hilf;
⇐ Genauso, Vater wie oben finden.
(c) W. Conen, FH GE, GIN2
5
Heap: INSERT
Algorithm INSERT(Q,v)
Füge v auf der ersten freien
Position der untersten Ebene
ein (wenn voll, neue Ebene
beginnen)
p ← Vater(v)
Solange p existiert und Wert(v) <
Wert(p) tue
Vertausche die Werte
von p und v;
v ← p; p ← Vater(p)
Einfügen von 5
4 6 10 12 6 13 10 13 19 7 5
p=5⇐
v = 11
Wert(v) < Wert(p)? Klar! Vertauschen!
4 6 10 12 5 13 10 13 19 7 6
pNeu= 2
vNeu= pAlt= 5
vAlt
Wert(v) < Wert(p)? Klar! Vertauschen!
01.04.2004
(c) W. Conen, FH GE, GIN2
6
Heap: INSERT
Algorithm INSERT(Q,v)
Füge v auf der ersten freien
Position der untersten Ebene
ein (wenn voll, neue Ebene
beginnen)
p ← Vater(v)
Solange p existiert und Wert(v) <
Wert(p) tue
Vertausche die Werte
von p und v;
v ← p; p ← Vater(p)
01.04.2004
Einfügen von 5
4 5 10 12 6 13 10 13 19 7 6
vNeu= 2
vAlt= 5
pNeu=1
Wert(v) < Wert(p)? Nein! Fertig!
(c) W. Conen, FH GE, GIN2
7
Heap: INSERT
Nach dem Einfügen von 5:
4 5 10 12 6 13 10 13 19 7 6
4
Und als Baum:
5
10
12
13
01.04.2004
6
19
(c) W. Conen, FH GE, GIN2
7
13
10
6
8
Heap: INSERT
Oder „andersherum“:
4 5 10 12 6 13 10 13 19 7 6
13
19 7
12
6
6
13
5
10
10
4
01.04.2004
(c) W. Conen, FH GE, GIN2
9
Heap: INSERT
Oder „andersherum“ und etwas verzerrt:
4 5 10 12 6 13 10 13 19 7 6
13 19 7
12 6
5
6
13 10
10
4
01.04.2004
(c) W. Conen, FH GE, GIN2
10
Heap mit Array
„
DELETE MIN völlig analog
„
Kleines Randproblem: dynamisch wachsende Arrays
‰ in manchen Programmiersprachen kein Problem
‰ Oft weiß man auch die Anzahl Objekte vorab und will
diese „nur“ sortieren (oder sortiert ausgeben, wie beim
Dijkstra)
„
Initiales Einfügen aller Elemente per Insert ist dann nicht
sehr effizient, soll heißen: es geht besser!
01.04.2004
(c) W. Conen, FH GE, GIN2
11
Heap mit Array: Aufbau
„
Annahme: Wir kennen n vorab.
„
Dann können wir alle Blattpositionen füllen, ohne
Vergleiche/Tauschoperationen ausführen zu müssen!
„
Warum? Das kann die partielle Ordnung nicht verletzen!
„
Wie geht das?
„
Wir füllen das Array „von hinten nach vorn“ und
beginnen mit dem Einfügen per INSERT erst ab Position
(n DIV 2) (für n=10 also Position 5)
01.04.2004
(c) W. Conen, FH GE, GIN2
12
Heap mit Array: Aufbau (1)
„
Naives Einfügen-von-vorn (mit INSERT) kostet
log1 + log 2 + ... + log n = Ω(n log n)
„
Jetzt haben wir Kosten für das Füllen von O(n):
‰
‰
‰
01.04.2004
Sei a ein Array mit n Elementen.
Zahl der Vergleiche, um eine partielle Ordnung zu erzeugen, ist
höchstens 2mal so groß, wie die Summe der Entfernungen aller
Knoten bis zur Blattebene! (Jeder Knoten sinkt von ganz oben bis
ganz unten)
Fortsetzung nächste Folie...
(c) W. Conen, FH GE, GIN2
13
Heap mit Array: Aufbau (2)
„
„
„
„
„
„
„
Die Summe dieser Abstände übersteigt die Zahl n NICHT:
Etwas die Hälfte der Knoten sind Blätter, etwa 1/4 hat den
Abstand 1, etwa 1/8 den Abstand 2 usw.
Beispiel: n = 31 (Tiefe 4, also 5 Schichten, vollbesetzt)
Abstandssumme: 26, allgemein für vollständige Binärbäume
der Tiefe k (mit n = 2k+1 – 1 Knoten):
‰ n - k -1 (best case)
Beispiel: n=32 (also Tiefe 5, Schicht 6 hat nur einen Knoten!)
Abstandssumme: 31, allgemein mit n = 2k+1
‰ n-1 (worst case)
Abstandssumme also insgesamt immer kleiner als n.
01.04.2004
(c) W. Conen, FH GE, GIN2
14
Heap „verkehrt“
„
„Normale“ Heapanwendung: Heapsort
„
Ohne weiteren nennenswerten Platz zu beanspruchen, wollen
wir „in situ“ (also direkt „am Ort“) sortieren.
„
Das geht leichter, wenn wir einen Heap bauen, der eine
„umgekehrte“ partielle Ordnungsbedingung erfüllt: die Wurzel ist
größergleich als die Söhne (also ein Max- bzw. ≥-Heap)
„
Dann wenden wir ein DELETE MAX an und tauschen die Wurzel
(n-1)-mal gegen das letzte Element des Heaps (und verkürzen
diesen dann „von hinten“ und sammeln so dahinter die
geordneten Elemente ein! (s. Applet))
01.04.2004
(c) W. Conen, FH GE, GIN2
15
Heapsort improved
„
„Normaler“ Heapsort (wie gerade beschrieben) hat Worst- und
average-case-Kosten von 2n log n + O(n)
„
Zur Erinnerung: Quicksort im average-case:
1,386 n log n + O(n), worst-case: O(n2)
„
Aber Heap-Sort kann es noch besser!
01.04.2004
(c) W. Conen, FH GE, GIN2
16
Bottom-Up Heapsort
„
Bisher haben wir beim Einsinken gleich Ordnung geschaffen:
‰ Der kleinere Sohn wurde ausgewählt und zur neuen
Wurzel gemacht.
‰ Das sind 2 Vergleiche: Söhne miteinander, Wurzel gegen
kleineren (bei Min-Heap) bzw. größeren Sohn (bei MaxHeap).
„
Jetzt stellen wir uns vor, wir hätten beispielsweise einen MinHeap bis zum Element a[2] bereits gefüllt und organisiert und
fügen nun die Wurzel a[1] hinzu.
„
Jetzt suchen wir zunächst eine „virtuellen“ Einsinkepfad bis
ganz nach unten...
‰ indem wir nur zwischen den Söhnen vergleichen und in
Richtung des kleineren Sohn weitergehen...
01.04.2004
(c) W. Conen, FH GE, GIN2
17
Bottom-Up Heapsort
„
... und folgen dann dem eben beschrittenen Pfad solange wieder
nach oben,
‰ bis wir den Wert von a[1] an die Stelle des momentan betrachteten
Elements q schreiben können
‰ dort ist erstmals a[1] > q.
„
Dann lassen wir den Wert des momentan betrachteten Elements und
alle anderen Werte auf die Position ihrer Väter rutschen
(die Wurzel ist ja gerade leer, die wird zuletzt gefüllt, dann stoppen
wir natürlich)
„
Auf den nächsten Folien findet sich ein Beispiel!
[Bottom-Up-Heapsort ist eine Idee von Prof. Wegener, U DO]
01.04.2004
(c) W. Conen, FH GE, GIN2
18
Bottom-Up Heapsort: Down-Phase
·-Heap, Wurzel
mit Wert 12
wird einsortiert.
12
4
6
Min?
12 Min? 11
13
01.04.2004
19
Min?
17
Min?
15
17
13
10
12
13
20
11
16
17
11
: „Virtueller“ Einfügepfad
(c) W. Conen, FH GE, GIN2
19
Bottom-Up Heapsort: Up-Phase (1)
·-Heap, Wurzel
mit Wert 12
wird einsortiert.
12
4
10
6
12
13
15
12 > 11?
19
17
17
12
13
20
11
16
17
11
13 <12?
: Suche nach Einfügepunkt
01.04.2004
(c) W. Conen, FH GE, GIN2
20
Bottom-Up Heapsort: Up-Phase (2)
·-Heap, Wurzel
mit Wert 12
wird einsortiert.
12
12
4
4
6
10
11
6
12
13
15
11
12
19
17
17
12
13
20
11
16
17
11
13
: Ringtausch
01.04.2004
(c) W. Conen, FH GE, GIN2
21
Bottom-Up Heapsort: Performance
„
Geht der Ringtausch bis zur Tiefe t (t · log n), erfordert das
log n + (log n – t) = 2 log n – t Vergleiche.
„
Gerade haben wir 4 + 2 Vergleiche benötigt (die grünen Ovale)!
„Normaler Heapsort“ hätte 8 Vergleiche benötigt (warum?)
„
Das benötigt im average case („Durchschnittsfall“) sehr nahe an
1*n*log n + O(n) – und besser geht es auch theoretisch für
Sortierverfahren mit paarweisen Schlüsselvergleichen kaum!
01.04.2004
(c) W. Conen, FH GE, GIN2
22
Bottom-Up Heapsort: Performance
„
In Experimenten war Bottom-Up-Heapsort etwa ab n > 400
besser, als Quicksort ...
„
... und ab n > 16000 besser, als Clever-Quicksort
„
bei Quicksort sind die Konstanten im Faktor O(n) also
günstiger, so dass er für kleine n, trotz der schlechteren
Konstante vorne, besser ist.
„
Für größere n ist aber Bottom-Up-Heapsort besser (mit
sich vergrößerndem Vorsprung)!
(genaue Analyse in Güting/Dieker)
„
01.04.2004
(c) W. Conen, FH GE, GIN2
23
Literatur
„
Allgemein zur Algorithmik:
‰
‰
„
Cormen, Leierson, Rivest: Introduction to Algorithms, MIT Press, 2001, 1184 Seiten,
knapp über 60 Euro (DAS Standardwerk, sehr präzise, schön gesetzte Darstellung,
Englisch, leider teuer, aber etwas, das man immer wieder in die Hand nehmen kann –
allerdings ohne Bottom-Up-Heapsort!) – gibt es seit Oktober 2004 auch übersetzt für
knapp 70 € vom Oldenbourg-Verlag (zur Qualität der Übersetzung kann ich nich nichts
sagen)
Bernd Owsnicki-Klewe: Algorithmen und Datenstrukturen, 2002, Wißner-Verlag, sehr
gut lesbarer, hinreichend präziser „Standard“-Streifzug durch die Algorithmik, recht
knappe Darstellung, aber nette Auswahl, orientiert sich u.a. auch an Cormen et. al (hat
deshalb auch nichts zu Bottom-Up-Heaps), 15,80 €
Ergänzend
‰
‰
‰
01.04.2004
Güting, Dieker: Datenstrukturen und Algorithmen, Teubner, 2. Aufl., 2003 (krumme
Grafiken und nicht sehr übersichtlich gesetzt, sonst sehr nett, Reihenfolge nicht immer
glücklich gewählt – vom Problem zur Datenstruktur ist meist besser als umgekehrt,
aber insgesamt les- und brauchbar, gibt es jetzt in der 3. Auflage für 29,90 – mit
Bottom-Up-Heaps)
Uwe Schöning: Algorithmik, Spektrum, 2001 (runder, tiefer, aber auch knapp und nicht
leicht)
Ottmann, Widmayer: Algorithmen und Datenstrukturen, Spektrum, 2002, dick und teuer
(noch teuer derzeit, als Cormen, Leierson, Rivest, und nicht ganz so schön, aber
umfassend und erprobt.
(c) W. Conen, FH GE, GIN2
24
Literatur
„
Speziell zu Heaps (für angehende Heap-Fans) z.B.
‰ Stefan Edelkamps (Juniorprof. an der Uni DO)
Diplomarbeit: Weak-Heapsort: Ein schnelles
Sortierverfahren
‰
... und jede Menge Forschungspapiere, z.B. On the
Performance of Weak-Heapsort (Edelkamp, Wegener,
1999) etc.
(das brauchen sie natürlich nicht zu lesen, aber hier finden
Sie Startpunkte, wenn sie ein „Sortier“-Spezialist werden
wollen ;-)
01.04.2004
(c) W. Conen, FH GE, GIN2
25
Kosten für Dijkstra mit Priority-Queues
(kurz: PQueues)
„
Zur Erinnerung:
‰
‰
‰
‰
‰
„
gerichteter Graph G = (V,E) mit nicht-negativen Gewichten w(·) (Bogenlänge in
unserem Algorithmus), Startknoten s ∈ V; zur einfacheren Darstellung: keine
Schleifen, jeder Knoten ist von s aus erreichbar
n = |V| = Anzahl Knoten, m = |E| = Anzahl Bögen
Dijkstra liefert alle kürzesten Wege von s zu den anderen Knoten; in S werden die
Knoten gesammelt, zu denen bereits kürzeste Wege bekannt sind
In D(v) = Distanz(v) wird die bisher bekannt gewordene kürzeste Länge von s über
Knoten in S zu v registriert;
V(v) = Vorgänger(v) vermerkt den Vorgänger auf dem kürzesten bisher gefundenen
Weg von s zu v (bei mehreren Wegen gleicher Länge wird hier der Vorgänger im
ersten gefunden Weg vermerkt).
Dijkstra-Ablauf:
‰
‰
Initialisieren der Distanzen aller Knoten (INIT-Phase), S ← {s} (INSERT)
Iteratives Hinzufügen aller Knoten aus V \ S zu S:
„
„
26.04.2005
jeweils Auswahl des Knotens v* mit der minimalen Distanz zu s (DELETE MIN)
Anschauen aller von Bögen, die von v* ausgehen, ggfs. Updates der Distanzen (und
Vorgängerbeziehung) (DECREASE KEY)
(c) W. Conen, FH GE, GIN2
2
Kosten für Dijkstra mit PQueues
„
Dijkstra-Ablauf:
‰
‰
„
Initialisieren der Distanzen aller Knoten
(INIT-Phase), S ← {s}
Iteratives Hinzufügen aller Knoten aus V \
S zu S:
„
„
jeweils Auswahl des Knotens v* mit der
minimalen Distanz zu s
Anschauen aller von Bögen, die von v*
ausgehen, ggfs. Updates der Distanzen
(und Vorgängerbeziehung)
‰
wir vernachlässigen hier, dass nur noch
Kanten nach V/S geprüft werden
„
Dijkstra Kosten:
‰
n * INSERTs
‰
n * Hinzufügen; jeweils mit
‰
‰
26.04.2005
1* DELETE MIN
„
Gradout(v*) Vergleiche
Insgesamt also
‰
Anzahl der aus einem Knoten herausführenden Bögen,
die Summe dieser Grade über alle Knoten ist natürlich
die Anzahl der Kanten, m
„
‰
n * INSERT
n * DELETE MIN
jeder Bogen wird angepackt, d.h. z.B.
m Vergleiche
max. DECREASE KEY-Aufrufe (maximale
Anzahl Updates),
s. nächste Folie
(c) W. Conen, FH GE, GIN2
3
Kosten für Dijkstra mit PQueues
Wieviele Updates gibt es maximal?
Beispiel: Graphen für 5 Knoten
„
„
„
„
„
5
„
2
1
1
s 0 7
5
5
3
„
„
4
2
„
„
9
9
8 1 5
7
6
7
6
7
26.04.2005
„
Insgesamt 10 Updates und 10 Kanten
Davon kann man 4 per INSERT erledigen
Also 6 DECREASE KEY
Wenn man eine beliebige Kante entfernt, hat man weniger
Updates!
Wenn man eine Kante hinzufügt, kann man nicht mehr
Updates bekommen!
⇒ Maximale Anzahl Updates für 5 Knoten: 5 * 2 = 5 * (51) / 2 = 10.
Maximale Anzahl allgemein für n Knoten: n*(n-1) / 2
Maximales Verhältnis Updates pro Kante: 1
Minimales Verhältnis:
(n-1) Updates bei n*(n-1) Kanten = 1/n
Updates: 4 + 3 + 2 + 1
(c) W. Conen, FH GE, GIN2
4
Kosten für Dijkstra mit PQueues
„
Wie läßt sich das abhängig von m ausdrücken?
‰ Maximal kann es m = n*(n-1) / 2 Kanten geben, die zu einem
Update führen können. Gibt es mehr Kanten, dann sind
mindestens m – n*(n-1)/2 hiervon irrelevant.
‰ Mindestens muss es m = n-1 Kanten geben (der Graph ist
zusammenhängend), und die müssen alle zu Updates führen
(Annahme: jeder Knoten ist erreichbar von s, maximales
Kantengewicht nicht vorher bekannt)
‰ Ausgehend von m gibt es also maximal m Updates, aber nie mehr
als n*(n-1)/2 insgesamt
‰ Hier sind die Updates in der Initialisierungsrunde mitgezählt, diese
führen aber nicht zu einem DECREASE KEY, da man in dieser
Runde das initiale INSERT mit dem Kantengewicht von s zu
diesem Knoten ausführen würde, es ist also die Anzahl der von s
ausgehenden Kanten jeweils abzuziehen:
‰ Also maximal (m-1)-DECREASE KEY, aber nie mehr als (n1)*(n-2)/2.
26.04.2005
(c) W. Conen, FH GE, GIN2
5
Kosten für Dijkstra mit PQueues
„
„
„
„
„
Von Interesse: Kosten für INSERT, DELETE MIN, DECREASE KEY
Alle anderen Operationen betrachten wir als elementar zu konstanten
Kosten, also Kosten in der Größenordnung O(1)
Zwei Fragen:
‰ Wieviel Aufwand verursachen die Operationen für eine konkrete
PQueue-Realisierung bzw. Implementierung
‰ Wie groß ist der Aufwand mindestens, unabhängig von einer
Implementierung (hierzu braucht man Annahmen über die Art der
Realisierung, z.B. Sortieren per Vergleich zwischen je zwei
Schlüsseln)?
Welche Aufwände sind interessant: Worst case (schlimmster Fall),
Best case (bester Fall), Average Case („durchschnittlicher Fall“)
Wenn man Operationen wiederholt ausführt, dann kann z.B. die erste
Ausführung teuer sein (Einfügen des ersten Elements in eine neu zu
errichtende Datenbank), weitere Folgeoperationen werden aber
deutlich günstiger. Dann analysiert man die amortisierten Kosten.
26.04.2005
(c) W. Conen, FH GE, GIN2
6
Kosten für Dijkstra mit PQueues
„
Simple Realisierung von PQueues:
‰
‰
‰
Annahme: alle Knoten sind eindeutig von 1 bis n nummeriert
Array [1..n] speichert die Distanzen D(v)
Kosten:
„
„
„
‰
Insgesamt also O(n2):
„
„
„
‰
INSERT: O(1)
DELETE MIN: Suche über das ganze Array, also O(n)
DECREASE KEY: O(1)
n*O(INSERT) + n*O(DELETE MIN) + O(m)*O(DECREASE KEY) =
n*O(1)
+
n*O(n)
+
O(m)*O(1) =
O(n)
+
O(n2)
+
O(m) = O(n2), weil m < n2.
Die ersten beiden Faktoren sind unabhängig von der Kantenanzahl,
ihre Kosten fallen IMMER an. Der zweite Faktor dominiert alle
anderen Kosten, d.h.:
„
26.04.2005
die Kosten fallen im worst, im average und im best case an!
(c) W. Conen, FH GE, GIN2
7
Kosten für Dijkstra mit PQueues
„
Realisierung von PQueues mit „normalem“ Heap:
‰
‰
Annahme: alle Knoten sind eindeutig von 1 bis n nummeriert
Ein Heap speichert die Knoten mit ihren Distanzen D(v) als Key
‰
Kosten:
„
INSERT, naiver Aufbau: ein Insert pro Knoten): log1+log2+...+log n-1=Ω(n log
n)=O(n log n)
„
DELETE MIN: Minimum entnehmen: O(1) + Heap reorganisieren maximal bis zur
Tiefe log n: O(log n) = O(log n)
„
DECREASE KEY: Key finden (direkt über Verweis auf Heapelement vom Knoten
aus einem Knotenarray [1..n]), also O(1) + Reorg: O(log n) = O(log n)
‰
Insgesamt also O(n):
„
(n log n) + n*O(DELETE MIN) + O(m)*O(DECREASE KEY) =
„
O(n log n) + n*O(log n)
+
O(m)*O(log n) = O(n*log n) + O(m*log n)
=
„
O((n +m) * log n) = O(m * log n)
[weil alle Knoten von s erreichbar sind nach Annahme und daher m
zwischen O(n) und O(n2) liegt, also nicht von n dominiert wird, dieses aber
ggfs. dominiert – im ersten Fall ergibt sich O(n + n) = O(n), im zweiten O(n2 +
n) = O(n2), also jeweils O(m), ebenso für alle „Zwischenfälle“!]
26.04.2005
(c) W. Conen, FH GE, GIN2
8
Kosten für Dijkstra mit PQueues
„
Mit einer „normalen“ Heap-Implementierung von Priorityqueues erreichen wir also Kosten für
Dijkstra von O(m* log n).
„
Das ist jetzt zunächst eine Worst-Case-Abschätzung (wenn es gar keine Updates gibt, haben
wir im Best case, z.B. nur O(n log n + m) an Kosten)
„
Es ist aber auch eine average case Abschätzung, Amortisierung spielt hier keine Rolle (es
wird nichts durch mehrfaches Verwenden billiger.
„
Insgesamt ist das besser, als die O(n2)-Implementierung bei der einfachen ArrayImplementierung, falls gilt
m = o(n2/log n) (sonst lohnt der Aufwand nicht!)
„
Typischerweise gibt es wesentlich mehr DECREASE KEY-Operationen, als EXTRACT MIN.
Heapvarianten, die günstigere (amortisierte) Kosten für DECREASE KEY-Operationen haben
(z.B. Fibonacci-Heap, Radix-Heap, beide gemeinsam) verbessern das Laufzeitverhalten
weiter.
„
Mit Fibonacci-Heaps erreichen wir O(n log n + m), weil die amortisierten Kosten für
DECREASE KEY nur bei O(1) liegen!
26.04.2005
(c) W. Conen, FH GE, GIN2
9
Kosten für Dijkstra mit PQueues
„
Problem unserer Analysen:
‰ Wir bleiben ein wenig „unscharf“: ist ihre konkrete Implementierung
effizient (in Sinne des Größenordnungmäßig erreichbaren)?
„
„
„
‰
Entspricht ihre Implementierung tatsächlich den Algorithmen für Heapbasierte Pqueues?
Sind die Annahmen über die Kosten „elementarer“ Operation für ihre
konkret verwendete Rechnerarchitektur und für ihre spezielle Maschine
gerechtfertigt?
Daraus ergibt sich auch, ob die „idealisierten“ Kostenanalysen für die
PQueue-Operationen gerechtfertigt sind und sie diese übernehmen
können.
Wenn ja, ist sie auch im Hinblick auf die „Konstanten“ (die im OKalkül ausgeblendet werden) „gut“?
„
26.04.2005
Ist das überhaupt interessant? Ja! Denn n*d1 = n*d2 = O(n), auch wenn
d1=5 und d2 = 500.000!
(c) W. Conen, FH GE, GIN2
10
Kostenanalysen generell
„
„Exaktere“ Analysen: Unterstellen sehr genau eine bestimmte (abstrakte)
Rechnerarchitektur (mit Details zur Darstellung von Zahlen, exakten Kosten zu
einzelnen Klassen von Operationen, etc.),
‰
z.B. verwendet Knuth in The Art of Computer Programming eine Architektur mit einer eigenen
(Assembler-)Sprache, deren Kosten (und Effekte!) sehr exakt analysierbar sind und in der er
die Algorithmen darstellt
„
Überlegungen zum Speicherbedarf haben wir noch gar nicht angestellt, ab einer
bestimmten Größe für die Laufzeit wichtig, wo gespeichert wird: Cache, primär
(Hauptspeicher), sekundär (Platte), tertiär Speicher (Netz, CD-RW, Band)
„
Weitere Probleme: Was genau sind „average cases“? Wie bestimmt man amortisierte
Kosten? etc. Details hierzu (generell und zu vielen Algorithmen und Datenstrukturen)
z.B. in Cormen, Leierson, Rivest, Stein: Introduction to Algortithms, 2nd Edition, MIT
Press, 2001.
„
Für diese und ähnliche Analysen (und für vieles mehr ;-) braucht man häufig
Zählargumente (Kombinatorik) und Wahrscheinlichkeitsanalysen (Probabilistik) – Prof.
Engels kann das sicher gut erklären, ansonsten kann es auch nicht schaden, mal ein
passendes Buch hierzu aufzuschlagen, z.B. Steger: Diskrete Strukturen (Band 1 und 2)
26.04.2005
(c) W. Conen, FH GE, GIN2
11
Kosten für Dijkstra mit PQueues
„
„
„
Jetzt schauen wir noch ein paar Spezialfälle an!
Angenommen, alle Bogengewichte sind ganzzahlig und aus dem
Intervall [1..C].
Für diesen Fall haben wir bereits eine Idee gesehen, die auf Dial
(1969) zurückgeht:
‰
verwende ein Array [0..C*(n-1)] von Töpfen (Buckets), um die
Knoten in Töpfe einzusortieren, die ihrem Gewicht entsprechen.
‰
Beginne vorn und schreite auf der Suche nach Knoten mit
minimalem Abstand durch die Töpfe
26.04.2005
(c) W. Conen, FH GE, GIN2
12
Kosten für Dijkstra mit PQueues
„
Der längste kürzeste Weg von s zu anderen Knoten kann höchstens
(n-1) Bögen lang sein:
1
s
„
„
„
„
2
n-1
z
Maximales Gewicht eines kürzesten Wegs: (n-1)*C
Wir haben Erreichbarkeit unterstellt, also können wir alle Knoten
ungleich s mit D(v) = (n-1)*C initialisieren.
Die Knoten-IDs sind weiter aus [1..n]. Wir merken uns für jeden
Knoten den Topf, in dem er gerade steckt.
Machen wir das an unserem Beispielgraphen durch!
26.04.2005
(c) W. Conen, FH GE, GIN2
13
Dials simple Vorgehensweise
5
C = 9, maximales Bogengewicht
C*(n-1) = 35, maximale kürzeste
Weglänge
1
1
1
s 0 7
5
2
2
5
4
2
9
7
0
Init:
1
2
3
4
5
6
7
8
9
10
9 1 7
4
3
C*(n-1)
s
1
2
3
4
v*=s
26.04.2005
1
2
3
(c) W. Conen, FH GE, GIN2
4
14
Kosten für Dijkstra mit PQueues
5
C = 9, maximales Bogengewicht
C*(n-1) = 35, maximale kürzeste
Weglänge
1
1
1
s 0 7
5
2
2
5
3
4
2
9
7
0
1
2
3
4
11
v*=4
v*=2
v*=1
v*=3
26.04.2005
5
6
3
8
9
3
2
2
7
43
4
9
8 1 5
7
6
7
6
3
4
C*(n-1)
10
4
4
(c) W. Conen, FH GE, GIN2
D(1) = 1
D(2) = 3
D(3) = 5
D(4) = 6
15
Kosten von Dials Variante
„
„
„
„
„
Im schlimmsten Fall erstreckt sich die Suche über alle Buckets, also
von 1 bis C*(n-1) = O(nC).
Das ist linear: C ist eine Konstante!
Aber stören kann es schon...z.B. wenn für ihre Anwendungen n im
Vergleich zu C eher klein ist ...
Zudem führt eine direkte, naive Implementierung natürlich zu
zusätzlichem Speicherbedarf von O(nC).
Gesamte Kosten:
‰ INSERT (gesamt): O(n)
‰ DELETE MIN (gesamt): maximal O(nC)
‰ DECREASE KEY: Finden (mittels Hilfsarray): O(1),
Löschen/Einfügen in Bucket: O(1)
‰ Insgesamt also: O(n) + O(nC) + O(m) = O(m + nC)
26.04.2005
(c) W. Conen, FH GE, GIN2
16
Verbesserungen zu Dial?
„
„
„
„
„
Nur ¼ des Bucket-Arrays wird im Beispiel genutzt!
Wenn alle Knoten von s erreichbar sind:
‰ dann kann der nächste Knoten minimaler Distanz
höchstens C Felder entfernt sein
‰ Wenn eine Distanz kleiner als C ist, z.B. C-x, und das
momentane Array-Ende bei Ende liegt, dann werden
die Felder [Ende-x+1..Ende] niemals besucht
Ideal wäre die Suche, wenn man nur gefüllte Buckets besucht, diese
nur Knoten mit gleicher Distanz enthalten und
Verwaltung/Anlage/Update der Buckets billig ist.
Das wird nicht gehen...aber wenig suchen und wenig und billig
sortieren/finden (bei Knoten ungleichen Werts in den Buckets) ist
schon ein gutes Ziel!
Das versuchen Radix-Heaps, s. Literatur (im letzten Jahr haben wir
die noch angeschaut, ist aber doch eine reichlich recht
Datenstruktur, deshalb sei sie hier nur erwähnt).
26.04.2005
(c) W. Conen, FH GE, GIN2
17
Literatur
„
Allgemein zur Algorithmik, nochmals erinnert sei an:
‰
„
Cormen, Leierson, Rivest, Stein: Introduction to Algorithms, MIT Press, 2nd Edition,
2001 (ein inhaltlich sehr gutes und optisch sehr schönes Buch, zum Nachschlagen für
den Schrank, zum Lernen nicht so doll, weil ohne Lösungshinweise; zum Verstehen
aber gut!)
Speziell zu Datenstrukturen für Dijkstra (only for the *very* brave ones):
‰
‰
Ahuja, Mehlhorn, Orlin, Tarjan: Faster Algorithms for the Shortest Path Problem.
Verwendet Radix-Heaps, Präsentation hierzu z.B. auf James Orlins Webseite am MIT.
Mikkel Thorup: Integer Priority Queues with Decrease Key in Constant Time and the
Single Source Shortest Paths Problem, Proc. STOC’03, ACM, 2003 (s. diesen Link)
„
„
„
„
‰
26.04.2005
eine abstrakt beschriebene deterministische Fibonacci-Heap-Variante als Priority-Queue für
Dijkstra mit insgesamt O(m + n log log C) für ganzzahlige Gewichte aus [0..C] – das löst ein
offenes Problem aus dem obigen Paper – und verbessert die bekannten Grenzen!
Ohne Gewichtsgrenze C erhält er O(m + n log log n), das größenordnungsmässig nur
verbessert werden kann, wenn die bisher beste bekannte Lösung für deterministisches
Sortieren von Han (eben mit O(n log log n) verbesserbar wäre.
Leider nicht so leicht praktisch umsetzbar...
Es nutzt die Idee, dass man nicht immer das Minimum auswählen muß, sondern nur einen
Knoten, der nicht mehr verbessert werden kann (darunter ist auch immer das Minimum)
...und eine große Anzahl weiterer Arbeiten, u.a. zu Fibonacci-Heaps etc. im
Zusammenhang mit Dikstra
(c) W. Conen, FH GE, GIN2
18
Algorithmik: K RUSKAL
Spannende Bäume, Teil 2
Zur Erinnnerung: Sei G = (V, E) ein ungerichteter Graph mit Kantenbewertung w.
• G heißt zusammenhängend (oder verbunden), wenn jeder Knoten von jedem
anderen Knoten erreichbar ist (bei gerichteten Graphen hieße das dann stark
zusammenhängend).
• Ein Graph ohne Kreise (oder Zyklen) heißt kreisfrei (oder azyklisch).
• Ein zusammenhängender Graph ohne Zyklen heißt auch freier Baum (frei, weil
es keine ausgezeichnete Wurzel gibt).
Anmerkung: Wenn der Graph nicht zusammenhängend, aber kreisfrei war, dann
kann man ihn auch als Wald von freien Bäumen ansehen.
Übrigens: Eine Komponente ist ein maximal zusammenhängender Teilgraph von G,
d.h. es gibt keine Kante in G, die einen Knoten, der nicht im Teilgraph ist, mit diesem
Teilgraph verbindet. Mit anderen Worten: andere Knoten, als die die im Teilgraph
enthalten sind, sind von Knoten dieses Teilgraphs aus nicht erreichbar.
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Version 0.9β , 1. Mai 2005, Seite 14
Algorithmik: K RUSKAL
Die einzelnen Komponenten eines Graphs induzieren eine Zerlegung der Knotenmenge (s. GIN1b-Folien). Wenn der Graph zusammenhängend ist, dann besteht er
natürlich nur aus einer Komponente.
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Version 0.9β , 1. Mai 2005, Seite 15
Algorithmik: K RUSKAL
Spannende Bäume, Teil 2
Freie Bäume haben z.B. die folgenden interessanten Eigenschaften:
(i) Ein freier Baum mit n ≥ 1 hat genau n − 1 Kanten.
(ii) Wenn man einem freien Baum eine beliebige Kante hinzufügt, entsteht ein Zyklus
Machen sie sich insbesondere die letzte Eigenschaft klar! Abstrakt ist es klar:
• zwischen jedem Paar von Knoten, z.B. x und y , aus V gibt es bereits einen Weg
(der Baum ist ja ein zusammenhängender Graph).
• Wenn sie jetzt einen Kante einfügen, die x und y miteinander verbindet (unter
der Annahme, dass es zwischen diesen beiden Knoten keine direkte Verbindung
gab, ein solches Paar gibt es in einem Baum immer, wenn n > 2 ist), dann gibt
es einen weiteren Weg von x nach y , also haben sie einen Kreis!
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Version 0.9β , 1. Mai 2005, Seite 16
Algorithmik: K RUSKAL
Spannende Bäume, Teil 2
Weiteres zur Erinnerung:
• Ein Spannbaum zu einem zusammenhängenden Graphen G = (V, E) mit
Kantenbewertungen bzw. Gewichten w ist ein freier Baum, der alle Knoten aus
V enthält und dessen Kanten eine Teilmenge von E bilden.
• Das Gewicht eines Spannbaum ist die Summe der Gewichte seiner Kanten.
• Spannbäume mit einem Gewicht, das im Vergleich zu allen Spannbäumen von G
minimal ist (d.h. es gibt keinen Spannbaum zu G mit kleinerem Gewicht) heißen
minimale Spannbäume (natürlich kann es mehrere Spannbäume mit dieser Eigenschaft geben).
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Version 0.9β , 1. Mai 2005, Seite 17
Algorithmik: K RUSKAL
Spannende Bäume, Teil 2
Wir kennen bereits den Algorithmus von P RIM, der einen minimalen Spannbaum,
ausgehend von einem ausgezeichneten Knoten s, findet.
• Der Ablauf entspricht dem D IJKSTRA-Algorithmus
• Als Distanzen werden aber die Gewichte von einzelnen Kanten mitgeführt, und
nicht die Gewichte von Wegen!
• Diese Gewichte für einen Knoten v aus V S sind die Gewichte der jeweils besten
Kante, die aus S heraus direkt zum Knoten v führt
[Beispiel in ihrem Mitschrieb]
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Version 0.9β , 1. Mai 2005, Seite 18
Algorithmik: K RUSKAL
Spannende Bäume, Teil 2
Der P RIM-Algorithmus konstruiert also nach und nach einen minimalen Spannbaum,
der in jedem Schritt um eine Kante erweitert wird.
Es geht aber auch anders . . .
Definition 2. [Cut] Ein Cut eines Graphen ist eine Zerlegung (alternativ: Partition)
der Knoten (zum Zerlegungsbegriff s. Übungsaufgabe 13 zu GIN1b) in zwei Mengen. Eine kreuzende Kante ist eine Kante, die einen Knoten der einen Menge mit
einem Knoten der anderen Menge verbindet.
Satz 3. [Cut Eigenschaft] Sei für G = (V, E) die Menge Z = {U, W } ein Cut
der Knotenmenge V . Sei uw eine kreuzende Kante in G mit minimalen Kosten unter
allen kreuzenden Kanten, also aus der Menge {u0w0|u0 ∈ U, w0 ∈ W }. Dann gibt
es mindestens einen minimalen Spannbaum zu G, der uw enthält (i) und jeder
minimal spannende Baum enthält eine minimale kreuzende Kante. (ii)
[Beweise s. Übung, dieser Beweis (ebenso wie der nächste) findet sich z.B. im Sedgewick, Part 5]
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Version 0.9β , 1. Mai 2005, Seite 19
Algorithmik: K RUSKAL
Spannende Bäume, Teil 2
Satz 4. [Cycle Eigenschaft] Gegeben sei ein Graph G und ein Graph G0 der aus
G durch Hinzufügen einer Kante e entsteht. Fügt man e zu einem minimalen spannenden Baum für G hinzu und löscht eine maximale Kante auf dem resultierenden
Kreis, dann erhält man einen minimalen spannenden Baum für G0.
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Version 0.9β , 1. Mai 2005, Seite 20
Algorithmik: K RUSKAL
Spannende Bäume, Teil 2
Dieser Satz liefert die Grundlage für den Algorithmus von K RUSKAL, der für G =
(V, E) (kleine) minimale Spannbäume nach und nach zusammenfügt, und zwar
wie folgt:
• Zu Beginn sei T ein Graph, der genau die Knoten V enthält, aber keine Kanten.
• Die Kanten aus E werden nach Gewicht sortiert.
• In jeder Runde eine Kante mit minimalem Gewicht ausgewählt und aus der Menge der noch nicht betrachteten Kanten entfernt.
• Wenn die Kante zwei bisher getrennte Komponenten miteinander verbindet,
dann wird sie in den Graphen eingefügt (und die Komponenten werden dadurch
verschmolzen).
• Ansonsten wird die Kante ignoriert (sie würde zu einem Kreis führen! Warum?)
• Fertig ist man, wenn T nur noch aus einer einzigen Komponente besteht.
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Version 0.9β , 1. Mai 2005, Seite 21
Algorithmik: K RUSKAL
Kruskal Implementierung
Nach und nach die Minima aus einer Menge zu entnehmen können wir schon! (PriorityQueue)
Aber wie können wir geschickt feststellen, ob eine ausgewählte Kante zu einem
Kreis führt oder zwei bisher getrennte Komponenten verbindet?
• Jede ungerichtete Kante kann man als zwei gerichtete Bögen darstellen.
• Die Bögen sind nichts anderes als geordnete Paare.
• Wir können diese geordneten Paare als Teil einer Verbunden-mit-Relation betrachten.
• Wir nehmen zudem an, dass jeder Knoten mit sich selbst verbunden ist.
• ... und ergänzen die Relation um Paare, die aus der Transitivität der Relation
entstehen.
Wir betrachten die Kanten ja nach und nach. Zu jedem Betrachtungszeitpunkt bestimmt die Kantenmenge eine Relation, die die Knoten V in Äquivalenzklassen
zerlegt.
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Version 0.9β , 1. Mai 2005, Seite 22
Algorithmik: K RUSKAL
Spannende Bäume, Teil 2
• Jede Prüfung, ob eine Kante uv zu einem Kreis führen würde, können wir nun
in die Frage übersetzen, ob u und v in der gleichen Äquivalenzklasse sind (also
schon verbunden sind).
• Wenn das nicht der Fall ist, dann fügen wir die Kante uv hinzu. Für unsere
“Verbunden-mit”-Relation bedeutet dies, dass wir (u, v) und (v, u) hinzufügen –
und alle Paare, die aus der Transitivität der Relation folgen.
• Das brauchen wir aber gar nicht wirklich zu tun, denn u ist in einer Äquivalenzklasse U und v in einer Äquivalenzklasse V mit U 6= V . Durch Schaffen einer
Verbindung zwischen U und V folgt mit der Transitivität, dass sich einfach eine
neue Äquivalenzklasse U ∪ V bildet, die u und v enthält.
• Dies entspricht genau dem Zusammenfügen zweier Komponten in T !
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Version 0.9β , 1. Mai 2005, Seite 23
Algorithmik: K RUSKAL
Spannende Bäume, Teil 2
Wir können das Problem also auch mit Hilfe von Äquivalenzklassen betrachten:
• Test, ob uv zu einem Kreis führt: sind u und v in der gleichen Äquivalenzklasse?
• Verschmelzen von Komponenten: Vereinigung von Äquivalenzklassen
Das kann man als Operationen auf Zerlegungen beschreiben (zur Erinnerung: eine
Zerlegung P einer Menge M besteht aus Mengen, die überschneidungsfrei und
nichtleer sind und vereinigt M ergeben)
• z = find(P ,k): Liefert das Element der Zerlegung P , in dem sich k befindet
• P = union(P ,z1,z2): Vereinigt z1 und z2 aus P und liefert das “neue” P zurück
Beachten Sie, dass P ein Mengensystem ist, Elemente aus P also Mengen sind.
Die folgende Implementierung verwendet allerdings Elemente dieser Mengen als
Repräsentanten für die Mengen (so, wie jedes Element einer Äquivalenzklasse als
Repräsentant der ganzen Klasse gewählt werden kann).
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Version 0.9β , 1. Mai 2005, Seite 24
Algorithmik: K RUSKAL
Union-Find
Init(P)
For i ← 0 to n do P[i] ← i
Union(P,i,j)
Random z in [0,1]
if z = 0 then P[i] ← j else P[j ] ← i
Find(P,i)
if i =P[i] then return i
else j ← Find(P,P[i])
P[i] ← j
return j
[s. auch die Informationen zu TARJAN]
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Version 0.9β , 1. Mai 2005, Seite 25
Algorithmik: K RUSKAL
Eine Verbesserung
• Die obige Implementierung verwendet bereits die sogenannte Pfadkompression
(s. Mitschrieb)2
• Sie wählt allerdings zufällig aus, ob zwei Komponenten i und j durch j oder
durch i repräsentiert werden
• Diese Wahl kann man auch bewußt treffen:
– In einem weiteren Array wird gespeichert, wieviele Elemente sich hinter einem
repräsentierenden Element verbergen
– Dann wird die kleinere Menge zur größeren hinzugefügt (d.h. jedes Element
zeigt dann auf den Repräsentanten der größeren Menge)
– Natürlich muß man dann auch noch die Information zur Elementzahl updaten
– Noch simpler (aber fast genau so gut und leichter zu analysieren) ist ein Ranking: für jeden Knoten gibt der Rank ein obere Grenze für die Höhe des Knotens an
2 die Elemente einer Äquivalenzklasse zeigen also direkt auf den Repräsentanten der Klasse (sonst könnte aus dem
nach und nach erfolgenden Verschmelzen von Klassen eine “tiefe” Baumstruktur folgen, die es teurer macht, die Frage
nach dem Repräsentanten der Klasse, in der ein gegebenes Element ist, zu beantworten, s. Übung.)
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Version 0.9β , 1. Mai 2005, Seite 26
Algorithmik: K RUSKAL
Init(P)
For i ← 0 to n do P[i] ← i; rank[x] ← 0
Union(P,i,j)
if rank[i] < rank[j ] then P[i] ← j else
P[j ] ← i
if rank[i] = rank[j ] then rank[i] ← rank[i] + 1.
Find-Worst-Case: O(log n), Amortisationsanalyse für eine beliebige Folge von
O(n) Union und Find Operationen führt zu O(n log∗ n) (das ist praktisch linear
– s. Mitschrieb, Details s. z.B. Cormen et. al)
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Version 0.9β , 1. Mai 2005, Seite 27
Algorithmik: K RUSKAL
Spannende Bäume, Teil 2
Algorithmus K RUSKAL:
Input: Zusammenhängender, ungerichteter Graph G = (V, E)
mit Gewichten w und n Knoten
Output: Minimaler Spannbaum T zu G
Init(P );
Sei T ← (V, ∅); Füge alle Kanten aus E in die PQueue Q ein;
kantenanzahl ← 0;
while kantenanzahl < n − 1 do
vw ← deleteMin(Q);
a ← find(P ,v ); b ← find(P ,w);
if a 6= b then
insert(T ,vw);
P ← union(P ,a,b);
kantenanzahl ← kantenanzahl+1;
end if
end while
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Version 0.9β , 1. Mai 2005, Seite 28
Algorithmik: K RUSKAL
Spannende Bäume, Teil 2
Analyse des K RUSKAL-Algorithmus (n = Knotenanzahl, m = Kantenanzahl):
• Initialisierung von P und T :
O(n)
• Initialisierung von Q:
O(m log m) (geht natürlich auch in O(m))
• Schleife:
– maximal m deleteMin
O(m log m)
– maximal m find und maximal n union
Je nach Implementierung O(n log n + m) oder
O(m log n)
Da wir angenommen haben, dass G zusammenhängend ist, gilt m ≥ n − 1, insgesamt folgt also ein Aufwand von O(m log m).
c 2005, Dr. W. Conen — Nutzung nur an der FH Gelsenkirchen
Version 0.9β , 1. Mai 2005, Seite 29
GIN2 Vorlesung SS05
Prof. Dr. W. Conen,
2. Mai 2005, FH Gelsenkirchen
-Minimal
spannende Bäume mit dem
Boruvka-Algorithmus
V1.0b
Es fehlt noch die älteste Variante...
„
Laut Tarjan von Boruvka, 1926!
„
Idee wie folgt:
‰ In jeder Runde werden Teilbäume (eines MST) mit ihren
nächsten Nachbarn verschmolzen (wir nennen die Teilbäume im
folgenden Komponenten)
‰ Die Kanten, die zu Verschmelzungen führen, werden in den MST
aufgenommen
‰ Durch die Verschmelzungen werden viele Kanten irrelevant
„
‰
Das sind die Kanten, die Knoten innerhalb einer bereits
verbundenen Komponente verbinden (also in der Komponente zu
Kreisen führen würden)
Zu Beginn steht jeder Knoten für eine Komponente, die nur ihn
enthält
V1.0b
Boruvkas Algorithmus
„
„
Annahme: wir haben n Knoten, m Kanten, indiziert von [0..n-1], der
(ungerichtete) Graph G = (V,E) ist zusammenhängend, Ausgabe: MST
Hier der Ablauf des noch folgenden Algorithmus
‰
Solange es noch mehr als eine Komponente gibt (Runde/Iteration)
„
Setze für jede Komponente i die kürzeste in dieser Runde bisher
gefundene Kante aus i heraus auf null mit einer Länge unendlich
„
Laufe über alle Kanten xy, die noch in E sind (Phase 1)
‰ Finde die Repräsentanten der Komponenten, in denen x und y sich
befinden, i für x, j für y.
‰ Entferne xy aus E, falls i = j, also x und y in einer Komponente sind
‰ Sonst prüfe, ob xy eine neue kürzeste Kante aus i bzw. j heraus ist,
und, falls ja, dann vermerke dies
„
Laufe über alle Komponenten i (Phase 2)
‰ Sei xy die kürzeste Kante, die aus i herausführt mit eine Länge D[i]
‰ Wenn x und y nicht mittlerweile in der gleichen Komponente liegen,
dann nimm xy in den MST auf und vereine die Komponenten
‰ Entferne xy aus E
V1.0b
Boruvka
3
b
4
d
3
1
a
4
c
3
b
1
4
1 2
e
d
3
1
a
4
c
3
b
1
4
1 2
e
d
3
1
a
4
2
f
3
2
f
3
2
f
2
c
1
e
Der Ausgangsgraph, es werden alle Kanten
nach und nach betrachtet. Der „naive“
Algorithmus schreibt keine Reihenfolge vor.
3
Runde 1, Ergebnis Phase 1:
Jeder Knoten steht noch für seine
eigene Komponente. Die roten Kanten sind
die gefundenen minimalen Kanten (die Wahl,
z.B. für c ist nicht eindeutig), die aus den
Komponenten hinausführen.
Runde 1, Ergebnis Phase 2:
Nach Betrachtung von b und c wird Kante
eb überflüssig. Die grünen Kanten werden
in den MST übernommen und aus E entfernt.
In E sind noch die schwarzen Kanten.
Es gibt jetzt nur noch ZWEI Komponenten.
V1.0b
Boruvka
3
b
4
d
3
1
a
b
1
4
e
d
3
a
f
2
c
3
2
3
2
2
1
c
1
e
f
3
Runde 2, Ergebnis der Phase 1:
Jetzt ist das Ergebnis übrigens eindeutig.
Denken Sie daran: im Prinzip wird jede
schwarze Kante „angepackt“, die Kante
ac wird entfernt, weil sie Knoten aus der
gleichen Komponente verbindet
Runde 2, Ergebnis der Phase 2:
In E sind noch die schwarzen Kanten
enthalten.
Wir haben n-1 Kanten aufgenommen (die
grünen), der MST ist komplett, es gibt nur
noch eine Komponente.
V1.0b
Boruvkas Algorithmus: Implementierung
„
Um die zusammenwachsenden Komponenten zu verwalten, verwenden wir
Union-Find auf Arraybasis.
‰
‰
„
In jeder „Solange“-Runde verwenden wir zwei Arrays, um die Informationen zu
den kürzesten Kanten, die aus den Komponenten hinausführen, abzulegen:
‰
„
D[i] gibt die Länge der bisher gefundenen kürzesten Kante min_edge[i] an; zu Beginn
jeder Runde werden alle auf ∞ bzw. null initialisiert
In der Phase 1 der „Solange“-Runde werden alle Kanten, die noch in E
verblieben sind, angeschaut:
‰
‰
„
Die Daten werden in einem Array P mit n Einträgen abgelegt, nummeriert von 0..n-1.
Zu einem gegebenen Knoten x findet Find(P,x) den Repräsentanten der Komponente,
in der x liegt, ebenso verwenden wir wieder Union und Init.
Falls die Kante innerhalb einer bereits gefundenen Komponente liegt, wird sie aus E
entfernt
Sonst wird geschaut, ob sie vielleicht kürzer ist, als die bisher gefundenen „kürzesten“
Kanten, die aus den beiden beteiligten Komponenten hinausführen – wenn ja, dann
werden die Informationen in D und min_edge upgedated, und zwar für den Knoten, der
die jeweilge Komponente repräsentiert, in der der jeweilie Endpunkt der betrachteten
Kante liegt (wenn nein, dann geschieht nichts)
In der Phase 2 der „Solange“-Runde laufen wir über alle Knoten. Wenn diese
Komponenten repräsentieren (also P[i] = i ist), dann nehmen wir die min_edge
zu dieser Komponente in den MST auf und vereinen die beiden Komponenten,
falls die beiden Knoten, die sie verbindet, nicht bereits (durch andere in dieser
Phase aufgenommene Kanten) in einer Komponente liegen.
‰
Zuletzt entfernen wir noch die Kante aus E
V1.0b
Boruvkas Algorithmus
„
Boruvka(G):
‰ int P[n], Init(P), MST ← {}, int D[n], Edge min_edge[n];
‰ while Anzahl(P) > 1 do
‰ . D[i] ← ∞, min_edge[i] ← null, jeweils für alle i
‰ . for each xy ∈ E do
‰ .
i ← Find(P,x), j ← Find(P,y)
‰ .
if (i = j) then E ← E - {ij}
‰ .
else if w(ij) < D[i] then D[i] ← w(ij), min_edge[i] ← xy
‰ .
if w(ij) < D[j] then D[j] ← w(ij), min_edge[j] ← xy
‰ . for each i in [0..n-1] do
‰ .
if P[i] = i then xy ← min_edge[i]
‰ .
if Find(P,x) != Find(P,y) then
‰ .
MST ← MST ∪ {xy}, Union(P,Find(x),Find(y))
‰
E ← E – {xy}
V1.0b
Boruvkas Algorithmus
„
Kosten:
‰ Die Union-Find-Operation sind annähernd linear
‰ Die Anzahl von Komponenten (also bisher unverbundenen Teilen
des MST) verringert sich in jeder Runde mind. um den Faktor 2
‰ Eine nennenswerte Zahl von Kanten wird in jeder Runde entfernt
„
Eine nicht sehr präzise, „konservative“ Abschätzung (ohne
Einsparungen durch Kantenentfernungen):
‰ O(|E| log |V| log* |E|) = O (m log n log* m)
„ Anzahl der Runden höchsten log |V|, pro Runde höchstens m
Find, mit Kosten weniger als m log* m.
„ mit Modifikationen O(m log n) erreichbar,
„ In empirischen Tests (s. Sedgewick) Kruskal für spärlich
besetzte Graphen und Prim-Varianten für stärker bis stark
gefüllte Graphen unterlegen
V1.0b
Boruvkas Algorithmus
„
„
„
Trotzdem ist der Algorithmus interessant, z.B. für Parallelisierungen
– aber auch für Randomisierung („Verzufallisierung“)
Dort werden Kanten entfernt, die sicher nicht im MST sein können...
Zunächst 3 modifizierte Boruvka-Iterationen:
‰
‰
‰
„
„
Die Knoten der Teil-MST werden je Teil-MST zu einem einzigen
Superknoten verschmolzen
die Kanten werden entsprechend umnummeriert, bei parallelen wird nur
die kürzeste vermerkt, Schleifen werden entfernt („Kontraktion“)
Das kann man übrigens auch in den normalen Boruvka einbauen und
damit Aufwand vermeiden (effektivere Reduktion der Kantenzahl)
Weitere Kanten werden zufällig gewählt und geprüft, dies geschieht
eingebettet in einen bestimmten Ablauf, den wir nicht im Detail
anschauen werden, der aber zu zu erwartenden Kosten von
O(m+n) führt – also linear!
Wir schauen uns das heute immer wichtiger werdende Instrument
der Randomisierung von Algorithmen noch an einem anderen
Beispiel genauer an (haben wir leider nicht geschafft, ein nettes
Beispiel ist ein randomisierter Algo von Uwe Schöning zur
Erfüllbarkeitsprüfung von aussagenlogischen Formeln)
V1.0b
GIN2 – Vorlesung, SS04
Prof. Dr. Wolfram Conen
2.+3.5.2004
Inhalte:
- Greedy-Algorithmen
- Matroide
- Greedy-Beispiel Huffman-Codierung
05.05.2005
(c) W. Conen, FH GE, GIN2
1
Greedy-Optimierung
„
Nehmen Sie an, sie brechen nachts in eine Villa ein
und wollen
‰
‰
„
soviel an Werten mitnehmen, wie sie tragen können
so schnell wie möglich wieder raus
Es gelten folgende Nebenbedingungen
‰
‰
‰
‰
05.05.2005
Die Bewohner sind verreist, wir haben im Prinzip „reichlich
Zeit“
Wir kennen den Wert aller Wertgegenstände
Wir können nicht alles mitnehmen, nur ein bestimmtes
Gewicht (sonst wäre „Alles mitnehmen“ optimal)
Die Gewichte kennen wir auch (Handwaage ist immer
dabei!)
(c) W. Conen, FH GE, GIN2
2
Greedy-Optimierung
„
Dieses Problem ist (in krimineller Verkleidung) das
klassische Knapsack Problem (Rucksack-Problem),
formal:
‰
‰
Gegeben ist eine Menge M = {1,..,n} von Gegenständen
mit den Werten vi und den Gewichten wi. Gesucht ist M‘ ⊆
M mit
∑i ∈ M‘ wi · wmax
d.h. die Summe der Gewichte der Gegenstände in M‘
überschreitet ein vorgegebenes Maximalgewicht nicht.
Der Wert der Gegenstände soll so groß wie möglich sein:
MaxM‘ ⊆ M ← ∑i ∈ M‘ vi
05.05.2005
(c) W. Conen, FH GE, GIN2
3
Greedy-Optimierung
„
Lösungsidee:
‰
‰
„
„
Gegenstände nach dem Verhältnis von Wert zu Gewicht („Wert
pro Kilo“) absteigend sortieren (der mit dem größten Wert pro
Gewichtseinheit steht also vorne), z.B. in einer PQueue
Dann nehmen wir solange Gegenstände aus der PQueue, wie
wir sie noch tragen können
Diese Strategie ist „gierig“! (English: Greedy) – man
nimmt das „Vielversprechendste“ zuerst usw.
Ist das immer sinnvoll?
05.05.2005
(c) W. Conen, FH GE, GIN2
4
Greedy-Optimierung
„
„
Natürlich nicht (würde ich sonst so fragen...)
Wir können maximal 50kg tragen, es gibt die folgenden
Gegenstände
‰
‰
‰
„
„
Lehrbuch über Algorithmen und Datenstrukturen,
Gewicht w1 = 10kg, Wert v1 = 60 Euro, d.h. 6 Euro pro kg
Taschenrechner mit eingebautem Dijkstra,
Gewicht w2 = 20kg, Wert v2 = 100 Euro, d.h. 5 Euro pro kg
Allegorische Statue, die die ewige Schönheit von (bottom-up)
Heapsort symbolisiert,
Gewicht w3 = 30kg, Wert v3 = 120 Euro, d.h. 4 Euro pro kg
Was liefert die Greedy-Strategie als Resultat?
Wie sieht das optimale Resultat aus?
05.05.2005
(c) W. Conen, FH GE, GIN2
5
Greedy-Optimierung
„
„
Aber manchmal geht es auch...sogar immer, wenn die
Problemstellung entsprechend ist.
Andere Gegenstände:
‰
‰
‰
„
„
„
Ein Haufen Schnipsel mit Klausurlösungen,
Gewicht 10kg, Wert 60 Euro, 6 Euro pro kg
Ein Haufen Goldstaub,
Gewicht 20kg, Wert 100 Euro, 5 Euro pro kg
Ein Haufen Silberstaub,
Gewicht 30kg, Wert 120 Euro, 4 Euro pro kg
Jetzt können wir die Gegenstände beliebig teilen:
Also nehmen wir die Klausurschnipsel, den Goldstaub,
und füllen unseren Rucksack mit Silberstaub auf (zu
einem Gesamtwert von 60+100+80 = 240 Euro)
Besser geht es natürlich nicht!
05.05.2005
(c) W. Conen, FH GE, GIN2
6
Greedy-Optimierung
‰
Probleme des ersten Typs (mit unteilbaren Gegenständen)
heißen
0-1 Knapsack Probleme
‰
Probleme des zweiten Typs (mit beliebig teilbaren
Gegenständen) heißen
Fractional Knapsack
‰
‰
05.05.2005
Fractional Knapsack Probleme lassen sich immer „greedy“ lösen!
0-1 Knapsacks nur ab und an „zufällig“!
(c) W. Conen, FH GE, GIN2
7
Greedy-Optimierung
‰
Unser „kürzeste-Wege-Problem“ lässt sich
auch „Greedy“ lösen – und genau das tut der
Dijkstra auch!
‰
Das gleiche gilt für das „Minimum Spanning
Tree“-Problem, und auch Kruskal bzw. Prim
lösen das Problem „greedy“
‰
Ein guter Hinweis darauf ist immer die
Verwendung einer PQueue...
05.05.2005
(c) W. Conen, FH GE, GIN2
8
Arbeitsweise von Greedy-Algorithmen
‰
Wir haben folgendes zur Verfügung
„
„
„
„
„
„
05.05.2005
Eine Menge von Kandidaten C, aus denen wir die
Lösung konstruieren wollen (z.B. Kanten beim MST)
Eine Teilmenge S ⊆ C, die bereits ausgewählt wurde
Boole‘sche Funktion solution, die sagt, ob eine Menge
von Kandidaten eine legale Lösung des Problems
darstellt (unabhängig davon, ob die Lösung optimal ist)
Eine Testfunktion feasible, die sagt, ob eine Teillösung
unter Umständen zu einer kompletten legalen Lösung
erweitert werden kann
Eine Auswahlfunktion select, die den nächsten
„vielversprechendsten“ Kandidaten liefert
Eine Zielfunktion value, die uns den Wert einer Lösung
angibt
(c) W. Conen, FH GE, GIN2
9
Arbeitsweise von Greedy-Algorithmen
Function greedy(c)
S←∅
while not solution(S) and C ≠ ∅ do
x ← select(C)
C ← C - {x}
if feasible(S ∪ {x}) then
S ← S ∪ {x}
if solution(S) then
return S
else return „There_is_no_solution“
Mit der Funktion value(S) kann man am Ende den Wert
der gefundenen Lösung bestimmen (falls es eine
gab...)
05.05.2005
(c) W. Conen, FH GE, GIN2
10
Welche Probleme sind „greedy“ lösbar?
„
Wenn das Problem sich als Matroid modellieren
läßt, dann kann man es „Greedy“ lösen!
„
Aber was ist ein Matroid? [s. Übung]
„
Es gilt sogar: ein Problem lässt sich genau dann
„greedy“ lösen (allgemein für jede
Gewichtungsfunktion der Elemente in die positiven
reellen Zahlen), wenn es eine Matroid-Struktur
aufweist (s. auch „Das Geheimnis des kürzesten
Weges“, Literaturhinweis zu Gin1b)
05.05.2005
(c) W. Conen, FH GE, GIN2
11
Noch ein wichtiges Problem, das man
„greedy“ angehen kann: Datenkompression
„
„
„
„
Ist ihre Festplatte ständig zu klein?
...oder ihre Internet-Anbindung zu langsam?
Dann ist Datenkompression ein Thema für Sie!
Ziele:
‰
‰
möglichst platzsparende Datenspeicherung bzw.
Übertragung
entpacken möglich, ohne Fehler in den Daten zu
hinterlassen
05.05.2005
(c) W. Conen, FH GE, GIN2
12
Datenkompression
„
Zwei Teilprozesse:
‰
‰
„
Beispielfälle für „Original“-Daten:
‰
‰
„
Kompression: Ein Prozess, der Daten in einer
komprimierte, also „kleinere“, Form überführt
Expansion: Ein Prozess, der aus der komprimierten Form
die Ausgangsdaten rekonstruiert
Ein Text aus 256000 Zeichen, jedes der 256 möglichen
(ASCII-)Zeichen tritt genau 1000-mal auf
Der Text besteht aus 256000-mal dem Zeichen ‚a‘
Beide Texte nehmen 256000*8 = 2.048.000 Bit
Plattenplatz ein.
05.05.2005
(c) W. Conen, FH GE, GIN2
13
Datenkompression
‰
Betrachten wir einmal die Wahrscheinlichkeit,
dass an einer bestimmten Stelle der Files ein
bestimmtes Zeichen auftaucht:
„
Im ersten File ist die Wahrscheinlichkeit für das
Auftauchen jedes Zeichens an der ersten betrachteten
Position gleich, nämlich 1/256, z.B. für ‚a‘
‰
„
05.05.2005
An später betrachteten Positionen verschieben sich die
Wahrscheinlichkeiten abhängig von den vorher bereits
betrachteten Zeichen, aber „ungefähr“ bleibt es bei der
Wahrscheinlichkeit 1/256 auch an den anderen Positionen
Im zweiten Fall ist die Wahrscheinlichkeit für das
Auftauchen von ‚a‘ an jeder Position 1.
(c) W. Conen, FH GE, GIN2
14
Datenkompression
„
Im ersten Fall besteht eine hohe Unsicherheit
darüber, welches Zeichen an der betrachteten
Position auftritt.
„
Um zwischen den verschiedenen möglichen
„Ereignissen“ (das Auftreten eines bestimmten der
256 Zeichen) zu unterscheiden, müssen wir den
aufgetretenen Fall genau angeben
„
Um 256 Ereignisse zu unterscheiden, brauchen wir
(log2 256) Bit (also 8 ;-)
05.05.2005
(c) W. Conen, FH GE, GIN2
15
Datenkompression
„
„
„
„
„
Im zweiten Fall wissen wir mit Sicherheit, welches
Zeichen an der betrachteten Position auftritt
(nämlich „a“)
Um zwischen den verschiedenen möglichen
„Ereignissen“ zu unterscheiden, brauchen wir gar
keine Information
Wir müssen nur wissen, wieviele „a“ auftreten
Insgesamt können wir das File durch „Jetzt kommen
256.000 ‚a‘“ vollständig beschreiben (also mit
ungefähr 150 Bit)
Intuitiv ist klar, dass man im ersten Fall nicht sehr
viel komprimieren kann, im zweiten aber schon!
05.05.2005
(c) W. Conen, FH GE, GIN2
16
Datenkompression
„
„
„
„
Zum Komprimieren muss man den „Eingabetext“
sinnvoll kodieren.
Elementare Idee:
Zeichen, die häufig vorkommen, erhalten
vergleichsweise kurze Codes
Das nennt man „variable Kodierung“
Sie soll den folgenden Ausdruck minimieren
∑ l(ci)*f(ci), 1 · i · n
‰
‰
‰
05.05.2005
n = Anzahl der Zeichen
l(ci) = Länge der Codierung des Zeichens ci
f(ci) = Häufigkeit von ci im Text
(c) W. Conen, FH GE, GIN2
17
Datenkompression
a
Häufigkeit 45
b
c
d
e
f
13
12
16
9
5
ASCII
01100001 01100010 01100011 01100100 01100101 01100110
Code 1
0
011
100
101
0011
1100
Code 2
0
101
100
111
1101
1100
Was ist schlecht am Code 1? Probieren Sie mal, 001100101 zu expandieren!
Man könnte natürlich die Länge des nächsten Codes abspeichern, aber so
recht macht das keinen Sinn...bei Code 2 geht das auch so!
05.05.2005
(c) W. Conen, FH GE, GIN2
18
Datenkompression
„
„
Definition: Eine Codierung heißt präfix-frei, wenn
kein Code Anfangsstück (=Präfix) eines anderen
Codes ist
Einen solchen Code kann man mit Hilfe von binären
Entscheidungsbäumen finden:
‰
‰
‰
‰
05.05.2005
Die Blätter sind die zu kodierenden Zeichen
Die Codes ergeben sich aus den Wegen im Baum, die von
der Wurzel zu den Zeichen führen
Eine Zweig nach links steht für ein 0, ein Zweig nach rechts
für eine 1 [s. Mitschrieb]
Eine solche Kodierung ist immer präfix-frei (warum?)
(c) W. Conen, FH GE, GIN2
19
Datenkompression
„
Unsere „neue“ Aufgabe ist also:
‰
‰
„
Gegeben ist eine Datei mit zu komprimierenden
Daten
Man konstruiere einen binären
Entscheidungsbaum, der zu einer optimalen
präfix-freien Codierung des Dateiinhalts führt.
Eine sehr bekannte Lösung dieser Aufgabe
ist die so genannte Huffman-Codierung
05.05.2005
(c) W. Conen, FH GE, GIN2
20
Huffman-Codierung
„
Baum zur Kodierung auf Basis der
Zeichenhäufigkeit optimal aufbauen
„
Infos über die gewählte Codierung in der erzeugten
Datei mit der Komprimierung speichern
„
Führt je nach Daten zu Komprimierungen ca.
zwischen 20%-70% (im worst-case spart man
nix...im Gegenteil, die Codetabelle kostet ja auch
etwas)
05.05.2005
(c) W. Conen, FH GE, GIN2
21
Huffman-Codierung: Ablauf
1.
2.
3.
4.
5.
Ein Durchlauf zur Bestimmung der vorkommenden
Zeichen und zur Ermittlung ihrer
Vorkommenshäufigkeit
Aufbau des optimalen Codebaums
Ableiten der Codes und Codelängen aus dem
Baum
Abspeichern der Codeinformationen in der
Ausgabedatei
Zweiter Durchlauf, um die Zeichen zu codieren,
nebst Ablage in der Ausgabedatei
05.05.2005
(c) W. Conen, FH GE, GIN2
22
Huffman-Codierung: Ablauf
Häufigkeitsermittlung ist klar
Aufbau des Codebaums:
„
„
‰
05.05.2005
„Gierige“ Suche nach einem optimalen Baum
(Gierig, weil die Zeichen in der Reihenfolge
„absteigende Häufigkeit“ genau einmal
angepackt werden)
(c) W. Conen, FH GE, GIN2
23
Huffman-Codierung: Baumaufbau
STUDENTEN SCHLAFEN NIE (na ja, ungefähr...)
Zeichen
Häufigkeit
blank ‚_‚
D
E
F
N
O
P
S
T
U
2
1
4
1
5
1
1
1
3
1
Erstes Ziel: die vorkommenden Zeichen in zwei
Gruppen zerlegen, die möglichst gleich häufig sind
„
‰
‰
‰
‰
05.05.2005
Warum? Mit einem Bit können sie perfekt eine „SchwarzWeiss“-Entscheidung wiedergeben.
Wenn die beiden Ereignisse „Schwarz“ und „Weiss“
gleichwahrscheinlich sind, dann brauchen sie tatsächlich
ein ganzes Bit zu ihrer Unterscheidung
Wenn sie ungleich verteilt sind, dann kämen sie „auf
lange Sicht“ auch mit weniger aus (warum?)
Wir möchten den möglichen Informationsgehalt in einem
Bit optimal ausschöpfen (und nichts verschenken!)
(c) W. Conen, FH GE, GIN2
24
Huffman-Codierung: Baumaufbau
Der Text ist 20 Zeichen lang, wir haben 10 verschiedene Zeichen.
„
„
„
05.05.2005
Gruppe 1
EDFOP
8
Gruppe 2
N T ‚_‚ S U
12
Das führt zur ersten Ebene des
„Konstruktions“-Baumes (s. Mitschrieb)
Zerlegt man die Gruppe 1 weiter, erhält man
bereits ein einzelnes Zeichen als Blatt (s.
Mitschrieb)
Insgesamt ergibt sich der Ergebnisbaum des
Mitschriebs
(c) W. Conen, FH GE, GIN2
25
Huffman-Codierung: Codierung
Das führt zu folgender Codierung (Codetabelle):
Zeichen
blank ‚_‚
D
E
F
N
O
P
S
T
U
2
1
4
1
5
1
1
1
3
1
Code
1110
0100
00
0101
0111
11110
110
11111
Länge
4
4
2
4
4
5
3
5
Häufigkeit
„
„
„
„
05.05.2005
10 0110
2
4
Unsere Eingabe war: STUDENTEN PENNEN OFT (sorry!)
Wie sieht die Kodierung aus?
11110110111110100001011000101110011100101000101
11001100101110
Diese Bitfolge kann man natürlich bei gegebener
Codetabelle in eindeutiger Weise wieder expandieren –
probieren sie es!
(c) W. Conen, FH GE, GIN2
26
Huffman-Codierung: Implementierung
Implementieren kann man das leichter „von unten“:
„
1.
2.
3.
4.
5.
05.05.2005
Starten mit den Blättern (eines je vorkommendem
Zeichen) und ihren Häufigkeiten
Suchen der beiden Blätter mit den niedrigsten
Häufigkeiten, z.B. S und U
Konstruktion eines Knoten, dessen linker Nachfolger der
eine (hier: S) und dessen rechter Nachfolger der andere
Knoten (hier: U) ist, die zugehörige Häufigkeit ergibt sich
als Summe der Häufigkeiten der Kindknoten
Entfernen der beiden „alten“ Knoten aus der Menge
„vaterloser“ Knoten, fügen den neuen Knotens hinzu
Wiederhole 2-4, bis nur noch ein Knoten übrig ist
(c) W. Conen, FH GE, GIN2
27
Huffman-Codierung: Implementierung
Die Implementierung mit einer PQueue ist
„straightforward“ (Strickmuster: 2 raus, einen rein)
Aus dem entstandenen Baum läßt sich die Codetabelle,
wie bereits beschrieben, unmittelbar generieren (wie?)
Komplexität des PQueue-Handlings ist für k verschiedene
Zeichen wie gehabt O(k log k)
‰
‰
‰
„
„
‰
05.05.2005
Initial k Knoten einfügen
dann jeweils 2 entnehmen und einen Hinzufügen, insgesamt
also n-1 neue Inserts (mit steigenden Häufigkeiten)
Für „längere“ Texte mit n Zeichen(vorkommen), n >> k,
dominiert der (zu n lineare) Aufwand für das Einsammeln
der Häufigkeiten
(c) W. Conen, FH GE, GIN2
28
Huffman-Codierung: Expansion
Die Codetabelle wird in der Ausgabedatei abgespeichert
Aus der Codetabelle kann man direkt den Baum
rekonstruieren
‰
‰
„
Die Expansion läßt dann den binären „Codestring“ durch
den Baum rieseln:
‰
1.
2.
3.
05.05.2005
0 = links, 1 = rechts, die Tiefe eines Zeichens entspricht der
Länge des Codes für ein Zeichen
Beginn mit dem ersten Zeichen des Codestrings
Wegwahl entsprechend der binären Ziffern entlang des
Baumes, beginnend mit der Wurzel, bis ein Blatt erreicht wird
Zum Blatt gehöriges Zeichen ausgeben und auf die Wurzel
zurückgehen, wenn noch nicht alles expandiert ist, zum
Schritt 2 zurückkehren
(c) W. Conen, FH GE, GIN2
29
Literatur
„
Zum generellen Greedy-Ablauf und zur HuffmanCodierung:
‰
‰
„
B. Owsnicki-Klewe: Algorithmen und Datenstrukturen, 4.
Auflage, Wißner-Verlag, Augsburg, 2002 (gut lesbares
Buch, launig-nett geschrieben, kann beim Verstehen sicher
helfen, kaum/keine Beweise, wenig Aufgaben, keine
Lösungen, aber dafür nicht sehr teuer, ca. 15 Euro, und
berührt viele unserer Themen)
Ansonsten finden sie praktisch in allen genannten Büchern
Informationen zu Greedy-Algorithmen und Codierungen
Zu Matroiden: In allen guten Büchern zu
Optimierung (z.B. dem von Korte, Nguyen, das sie
schon aus GIN1b kennen) oder auch in Cormen,
Rivest, Leierson oder bei Schöning (s. frühere
Literaturangaben).
05.05.2005
(c) W. Conen, FH GE, GIN2
30
GIN 2 – Vorlesung zu
Hashing, 31. Mai 2005
Prof. Dr. W. Conen
FH Gelsenkirchen
SS 2005
Hashing - Ausgangssituation
„
„
„
[Einstieg: s. auch ihr Mitschrieb aus der letzten Woche]
Datenstruktur „Dictionary“: Insert, Delete, Member/Search
Bisher: Der Schlüssel selbst konnte „unmittelbar“ als Index in
einem Array zur Speicherung der Daten verwendet werden
T
U
4
0
6
7
K
2K
5
Nutzlasten
1
1
9
0
8
3
2
3
4
5
6
7
8
9
U = Universum möglicher Keys (=Schlüssel);
K = Tatsächlich auftretende Keys; T = Direkt-addressierte Tabelle
Hashing - Ausgangssituation
„
„
„
„
Datenstruktur „Dictionary“: Insert, Delete, Member/Search
Jetzt: U ist sehr groß (z.B. Strings!) und |K| << |U|, d.h. die Anzahl
tatsächlich genutzter Schlüssel ist eher klein.
Selbst, wenn unser T genug Platz für die möglichen Schlüssel hätte,
wäre das unschön: es würde sehr viel Platz nicht verwendet!
Also Annahme: |U| >> |T| ≥ |K|
T
0
1
U
2 = h(k1)
3
K
kK
1
k3
k2
k4
4
5 = h(k2) = h(k3)
6
7 = h(k4)
8
h = Hashfunktion: U → {0,...,m-1}
m-1
Kollision!
Hashing - Ausgangssituation
„
Annahme: |U| >> |T| ≥ |K|
‰ K kann auch größer, als T werden, dann gibt es aber natürlich auf
jeden Fall gewisse Probleme...
„
Lösung: Wir bilden mit einer Hashfunktion h : U → {0,...,m-1} die
(möglichen und tatsächlichen) Schlüssel auf die Einträge in T ab
„
Hauptproblem: es können Kollisionen auftreten
„ Worst-Case: bei „ungünstiger“ Hashfunktion können alle
tatsächlichen Werte auf einen Index abgebildet werden
„ Und das selbst dann, wenn die Hashfunktion „im Prinzip“, also
gemessen an U, z.B. bei Annahme einer Gleichverteilung der
Auftretenswahrscheinlichkeit, „gut“ zu sein scheint!
Hashing
„
Was hätten wir gern?
‰ Eine Hashfunktion, die wenigstens im Prinzip alle Werte in
[0,...,m-1] treffen kann (also surjektiv ist)
‰ Eine Hashfunktion, die die tatsächlich auftretenden Werte (also K)
möglichst gut über T „streut“
„
‰
also Kollisionen so weit wie möglich vermeidet
Bei der Konstruktion von h weiß man eventuell bereits etwas über
die Auftretenswahrscheinlichkeit der möglichen Schlüssel
„ Wenn es um z.B. um Namen geht, dann sind manche
Buchstabenkombination häufiger („Schmidt“, „Weber“),
manche eher selten („Xyzmick“)
‰ Die Hashfunktion sollte „Schmidt“ und „Weber“ möglichst
auf verschiedene Indices abbilden
‰ Wo „Xyzmick“ landet, ist eher nicht so wichtig...
Hashing
„
Ursache von Kollisionen:
‰
„Unvermeidbar 1“: Wenn Keywerte mehrfach auftreten können
„ z.B. mehrere Personen mit Namen „Weber“
„ Randbemerkung: manchmal hilft dann natürlich, die Schlüssel
zu „vergrößern“, z.B. „Vorname Nachname“ zu verwenden
‰
„Unvermeidbar 2“: Wenn K größer als T ist (aber selbst dann ist
eine Minimierung von Kollisionen hilfreich!)
‰
„Vermeidbar“: Wenn |K| <= |T|, dann könnte h | K ( also „h
eingeschränkt auf K“, s. GIN1b) im Prinzip injektiv sein...
„ wenn es aber dann nicht injektiv ist, dann gibt es „unnötige“
Kollisionen!
‰
Für uns ist vor allem der „vermeidbare“ Fall interessant!
Hashing
„
Wichtige Fragen:
‰ Frage 1:
Wie „designed“ man eine Hashfunktion so, dass sie „möglichst“
injektiv ist, also „vermeidbare“ Kollisionen vermeidet?
‰
Frage 2:
Wie geht man mit Kollisionen um, wenn sie denn auftreten?
‰
Wir schauen uns zunächst Frage 2 an.
Antworten zur Frage 1 finden Sie in ihrem Mitschrieb.
Hashing: Umgang mit Kollisionen (1)
„
„
„
Kollisionen treten auf
Die Daten jeder „Kollisionsklasse“ werden in einer Liste hinter dem
berechneten Indexwert für ihren Schlüssel abgelegt.
Das nennt man „Chaining“, also Verkettung
T
0
U
k1
K
k1K
k3
k2
k2
k4
k4
h
m-1
k3
Hashing: Umgang mit Kollisionen (2)
„
„
„
„
Kollisionen treten auf
Für jeden Datensatz wird ein Platz direkt in T gesucht
Verschiedene Strategien möglich (z.B. Re-Hashing, Sondieren)
Nebenproblem: Was passiert, wenn T überläuft
T
0
Nutzlasten
U
k1
K
kK
1
k3
„Sondieren“ in der
Nachbarschaft oder
zweites (drittes, ...)
Hashing
k3
k2
k2
k4
k4
h
[s. auch Übungsaufgaben zu Hashing]
m-1
Hashing: Kollisionsbehandlung (3)
„
„
„Chaining“
Insert(d), Datensatz d hat
den Schlüssel k
‰
„
„
Kosten: O(1) (+ Kosten für
die Berechnung von h(k) –
h sollte also möglichst
effizient berechenbar sein!
Wir nehmen O(1) an)
Delete(d): O(1)
Member(d):
‰
‰
‰
Best Case: O(1)
Worst Case: O(|K|)...Aua!
Mittlerer Fall, s. Mitschrieb
„
„
„Platz in T suchen“
Insert(d):
‰
‰
‰
„
„
„
Kosten je nach
Kollisionsbehandlung
Best Case: O(1)
Worst Case: O(min(|T|,|K|))
Delete(d): wie Insert
Member(d): wie Insert
weitere Details s. Mitschrieb
Unsere Übungsaufgabe...
„
stammt aus Knuth: The Art of Computer Programming, Volume 3
„
Wir wollen 31 Strings auf den Bereich [0..40] bijektiv abbilden (im
Original auf [-10..30])
„
Es gibt 4131, ungefähr also 1050 Funktionen, die 31 Werte auf 41
Werte verteilen
„
Es gibt 41*40*...*11 = 41!/10!, ungefähr also 1043 injektive Funktion
„
Das sieht nach vielen aus...aber es ist nur 1 aus ungefähr 10
Millionen der „möglichen“ Funktionen!
„
Aber sie sehen: sie hatten eine große Auswahl bei der Lösung der
Übungsaufgabe! ;-)
Knuths Lösung
„
„
„
„
„
„
„
„
„
„
„
„
„
„
„
„
„
„
„
Ein MIX-Programm
LD1N
K(1:1)
LD2
K(2:2)
INC1
-8,2
J1P
*+2
INC1
16,2
LD2
K(3:3)
J2Z
9F
INC1
-28,2
J1P
9F
INC1
11,2
LDA
K(4:4)
JAZ
9F
DEC1 -5,2
J1N
9F
INC1
10
9F: LDA
K
CMPA
TABLE,1
JNE
FAILURE
Beispiel-Ablauf für BE
-2 (in rI1)
5 (in rI2)
-2-8+5 = -5
Kommentar
Wert(B)=2,Position(B)=1
Sprung, wenn 1 positiv
-5+16+5 = 16
Sprung, wenn 2 leer/null
Sprung, wenn Accu leer
(Sprungziel eigentlich 9H)
(versteht aber niemand... ;)
Wenn Sie noch kein h gefunden haben, dann suchen sie noch ein wenig weiter!
Literatur
„
Hashing z.B. in
‰
‰
„
Cormen et al. „Introduction to Algorithms“ oder
Owsnicki-Klewe „Algorithmen und Datenstrukturen“ (s. auch frühere
Literaturempfehlungen)
Zum MIX-Computer von Knuth können sie direkt bei Knuth
schauen („The Art of Computer Programming“) oder einen der
Emulatoren ausprobieren:
‰ http://www.recreationalmath.com/mixal/ (Emulator in HTML mit
Javaskript realisiert)
‰ http://www.gnu.org/software/mdk/mdk.html, der GNU-MIXDevelopment-Kit (mit Doku:
http://www.gnu.org/software/mdk/manual/mdk.html)
GIN2 SS05
Prof. Dr. W. Conen, 10.5.05
- Nullsummen-Spiele
- Min-Max-Suche
- Alpha-Beta-Pruning (späterer Termin)
„Abwechselnde“ Suche
„
„
Heute spielen wir
TicTacToe...
Bei TicTacToe gibt es drei
mögliche Resultate:
‰
‰
‰
„
X gewinnt (O verliert)
O gewinnt (X verliert)
- unentschieden
Es ist ein Null-SummenSpiel: was der eine
gewinnt, verliert der andere
(wenn man Sieg und
Niederlage mit individuellem
Nutzen bewerten würde).
XOX
OXO
OOX
„Abwechselnde“ Suche
Am Zug ist O:
Eine Spielsituation...
O ist am Zug
O kann „im Prinzip“ auf 4
Positionen setzen usw.
„
„
„
O
O
X:
X
X
X
X
O
X
O
O
X
O
O
X
X
O
X
X
X
O
O
O:
X
X
O
O
O
X
O
O
X
X
X
X
X
O
X
O
O
X
X
X:
X
O
O
X
O
X
X
O
X
O
X
O
O
X
O
O
X
O
O
X
O
X
X
X
O
X
X
O
X
X
O
X
O
X
X
X
X
O
O
X
O
O
X
X
O
X
X
O
X
O
X
O
O
X
O
O
X
O
O
X
O
O
X
O
X
X
O
X
X
O
X
X
O
X
X
O
X
O
X
X
O
X
O
O
X
X
O
O
X
O
O
X
O
O
O
O
X
O
O
X
O
X
X
X
X
X
X
X
O
X
O
X
O
O
O
X
X
O
X
X
X
O
O
O
O
O
X
O
X
X
O
--
„Abwechselnde“ Suche
o
x ox x
ox o ooo
ox ox o - oo
x x O
Am Zug ist O:
‰
Wenn O und X optimal
spielen, dann gewinnt O
auf jeden Fall!
O
X:
X
X
X
X
O
X
O
O
X
O
O
X
X
O
X
X
X
O
O
O:
X
X
O
O
O
X
O
O
X
X
X
X
X
O
X
O
O
X
X
X:
X
O
O
X
O
X
X
O
X
O
X
O
O
X
O
O
X
O
O
X
O
X
X
X
O
X
X
O
X
X
O
X
O
X
X
X
X
O
O
X
O
O
X
X
O
X
X
O
X
O
X
O
O
X
O
O
X
O
O
X
O
O
X
O
X
X
O
X
X
O
X
X
O
X
X
O
X
O
X
X
O
X
O
O
X
X
O
O
X
O
O
X
O
O
O
O
X
O
O
X
O
X
X
X
X
X
X
X
O
X
O
X
O
O
O
X
X
O
X
X
X
O
O
O
O
O
X
O
X
X
O
--
„Ausrechnen“ eines Spiels
„
„
„Im Prinzip“ kann man jedes deterministische Spiel so „ausrechnen“
‰ Man bestimmt den Baum bis zu den Blättern, die für entschiedene
Spiele (also „Endzustände“ stehen)
‰ Das Resultat gibt man nach oben weiter
‰ Bei inneren Knoten gibt man dann von unten nach oben das für den
jeweiligen Spieler beste erreichbare Resultat nach weiter
3x3-TicTacToe ist immer unentschieden, wenn beide Spieler optimal
spielen:
‰ Der „Max“-Spieler will das Ergebnis des anderen Spielers minimieren
bzw. sein Ergebnis maximieren
‰ Der „Min“-Spieler verhält sich analog
‰ Wenn man einen Sieg des Max-Spielers mit 1 bewertet und einen Sieg
des Min-Spielers mit -1, dann wählt der Max-Spieler immer den
maximal bewerteten Zug, der Min-Spieler immer den minimal
bewerteten Zug
‰ Deshalb spricht man auch von MinMax-Bewertung bzw. MinMax-Algo
„Ausrechnen“ eines Spiels
„
„
„
„
„
Grob kalkuliert muß man sich
1 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1 = 362880 Knoten
anschauen und diese bewerten
‰ früheres Erreichen von Endzuständen wird hier vernachlässigt
Wenn n die (maximale) Anzahl der Zugmöglichkeiten ist, dann
entspricht das n!
Es gilt: n! ∈ O(2n) (Abschätzung hierzu in der nächsten Vorlesung)
Zeitaufwändig, naiv implementiert auch speicheraufwändig (geht aber
gut mit Tiefensuche, dann „nur“ Zeitproblem)
Übrigens: es gibt genau 39 = 19683 verschiedene Brettstellungen.
‰ Wenn man festlegt, dass immer X beginnt, dann sind damit auch die
Spielsituationen eindeutig bestimmt.
„
‰
nicht alle sind erreichbar (weil vorher Ende wäre)
Wie kommt es zur Differenz?
„
Weil es zu praktisch jeder Stellung viele Wege gibt!
„Ausrechnen“ eines Spiels
„
„
„
„
„
„
„
„
„
Ein Computer könnte ein „ausgerechnetes“ Spiel perfekt spielen – er
wüßte immer die bestmögliche Antwort!
Im allgemeinen ist das „Ausrechnen“ aber viel zu teuer!
Für fortgeschrittene Spielen „im Endspiel“ geht es allerdings oft auch bei
„komplexeren“ Spielen (wie z.B. Dame)
Für ein 6x6-TicTacToe mit 4-gewinnt Regel gibt es z.B. 336 h 1.5*1017
Stellungen
... und 36! h 3,72*1041 Knoten (1080 ist die geschätzte Zahl der Atome im
sichtbaren Universium) (auch wieder ohne frühe Endzustände)
Das ist ein bisschen viel für 220k-Speicher...
Wie man aber dennoch erfolgreich ein Programm zum Spielen von 6x6/4TicTacToe schreiben kann – sogar für Handys –, erzählen uns jetzt 3 ihrer
Kommilitonen: die Herren Cevani, Schramma und Wengler
Legen Sie los! ;-)
[Alpha-Beta haben wir in einer späteren Veranstaltung auf Overhead
geschrieben]
GIN2 – Vorlesung, SS05
Prof. Dr. Wolfram Conen
7. Mai 2005
Inhalte:
- Repräsentation von Problemen
- Problemlösung durch Suche
SS - V1.0
(c) W. Conen, FH GE, GIN 2
1
Künstliche Intelligenz (KI)
„
„KI: Teilgebiet der Informatik, welches
versucht, menschliche Vorgehensweisen der
Problemlösung auf Computern nachzubilden,
um auf diesem Wege neue oder effizientere
Aufgabenlösungen zu erreichen“,
aus: Lämmel, Cleve: Künstliche Intelligenz, 2. Aufl. 2004,
Hanser Verlag
SS - V1.0
(c) W. Conen, FH GE, GIN 2
2
Problem 1 – Missionare und Kannibalen
„
Drei Missionare und drei Kannibalen sind auf der selben
Seite eines Flusses. Es gibt auf dieser Seite auch ein Boot, das
ein oder zwei Leute aufnehmen kann.
„
Problem: Finden Sie nun einen Weg, alle so auf die andere
Seite zu bekommen, dass die Zahl der Kannibalen die Zahl der
Missionare auf irgendeiner Seite des Flusses niemals
überschreitet (dann würden die Missionare nämlich gefressen...)
SS - V1.0
(c) W. Conen, FH GE, GIN 2
3
Wie löst man solch ein Problem?
„
„
Man „sucht“ nach einer Lösung
Aber zunächst mal muß man sich klar machen, WAS genau die
Aufgabe ist
‰
‰
‰
‰
„
Dann beginnt die Suche nach einer Lösungsmethode.
‰
„
Man sucht nach einer günstigen Repäsentation des Problems
„Günstig“ ist sie, wenn man mit dieser Repräsentation „leicht“ eine Lösung
finden kann (für das „umformulierte“, repräsentierte Problem)
... und diese Lösung sich zurück übertragen lässt auf die ursprüngliche
Problemstellung – also das „tatsächliche“ Problem löst!
Und natürlich sollte die Repräsentation „beherrschbar“ sein, also möglichst
klein, verständlich („wartbar“), präzise, usw.
Oft hat man bereits eine Methode im Hinterkopf und schaut, ob man das
Problem passend repräsentieren kann (z.B. Graph-basierte Suche,
Constraint Optimization, Genetic Algorithms, Neuronale Netze, sehen sie,
wenn sie mögen, in INT im Master)
Wenn man die Methode (ev. auch mehrere) ausgewählt hat, dann
beginnt die tatsächliche Suche nach einer Lösung, und zwar „auf“
der gewählten Problemrepräsentation
SS - V1.0
(c) W. Conen, FH GE, GIN 2
4
Kannibalen haben Hunger...
„
„
„
„
„
„
„
„
Repräsentation des „Zustandes“ als Vektor
(m,k,b)
„
m = Anzahl Missionare
3,3,1
„
k = Anzahl Kannibalen
„
b = Position des Bootes
Wir brauchen nur eine Seite des Flußes
darstellen, wir nehmen die linke Seite (b = 1)
3,2,1
Es gilt immer: Missionare rechts = 3-m,
Kannibalen rechts = 3-k
Ev. mögliche Folgezustände (naiv) zu (m,k,1):
1m,1k
‰
(m-1,k-1,0), (m-2,k,0), (m,k-2,0),(m1,k,0),(m,k-1,0)
Ev. mögliche Folgezustände (naiv) zu (m,k,0):
1k
2,2,1
‰
(m+1,k+1,1), (m+2,k,1), (m,k+2,1),
(m+1,k,1),(m,k+1,1)
Startzustand: (3,3,1), Zielzustand: (0,0,0)
Suche nach eine Zustandsfolge vom Start
zum Ziel!
Problem: wir merken noch nicht, dass wir
ungültige Zustände verwenden...
SS - V1.0
(c) W. Conen, FH GE, GIN 2
1m,1k im Boot
2,2,0
1m
2,1,0
1m, 2k rechts...AUA!
5
Kannibalen haben Hunger...
„
Ungültige Zustände feststellen...z.B. durch
Aufzählen:
‰
‰
‰
‰
„
„
„
„
„
2,2,0
(m,k,b) mit m < k ∧ m > 0 oder k < m ∧ k < 3 ist
ungültig
Zugfolgen sind nur legal, wenn sie nicht über
ungültige Zustände führen
Wenn wir eine Funktion haben, die zu einem
Zustnd die gültige Folgezustände ausspuckt,
dann können wir direkt die Lösung finden!
Wenn wir eine haben, die nur alle „möglichen“
Zustände ausspuckt, dann müssen wir noch
die Gültigkeit prüfen
Beides läßt sich als Graph visualisieren!
(nächste Folie)
SS - V1.0
1m,1k im Boot
3,3,1
Möglicherweise sind nicht alle hiervon
erreichbar (überhaupt oder nur über gültige
Zustände)
Man könnte das auch abstrakt angeben:
‰
„
(2,3,1),(1,3,1),(1,2,1)
(2,1,0),(2,0,0),(1,0,0)
(1,0,1),(2,0,1),(2,1,1)
(2,3,0),(1,3,0),(1,2,0)
3,2,1
1m
1m,1k
2,1,0
1k
1m, 2k rechts...AUA!
2,2,1
(c) W. Conen, FH GE, GIN 2
6
Die Rettung der Missionare (1)
3,3,1
2,2,0
2,3,0
2,3,1
3,2,0
1,3,0
3,1,0
3,2,1
1,2,0
2,1,0
3,0,0
3,1,1
1,1,0
SS - V1.0
(c) W. Conen, FH GE, GIN 2
7
Rettung der Missionare (2)
(3,1,1) ist der Vorgänger
1,1,0
2m, 2k rechts mit Boot
2,1,1
2,2,1
1,2,1
1,3,1
2,1,0
2,0,0
1,2,0
0,2,0
(gab‘s bereits)
0,3,1
0,1,0
0,2,1
1,1,1
2,1,1
1,2,1
(gab‘s bereits)
SS - V1.0
(c) W. Conen, FH GE, GIN 2
8
Rettung der Missionare (3)
(0,1,0) ist Vorgänger
SS - V1.0
0,2,1
1,1,1
0,0,0
1,0,0
(c) W. Conen, FH GE, GIN 2
9
Rettung...Kontrolle (1)
„
„
3,3,1
2,2,0
2,3,0
3,2,0
1,3,0
3,1,0
„
2,3,1
3,2,1
1,2,0
2,1,0
3,0,0
3,1,1
„
„
„
1,1,0
SS - V1.0
(c) W. Conen, FH GE, GIN 2
Gefundener Weg:
2 Kannibalen nach
rechts: (3,1,-,0,2,B)
oder 1M+1K nach
rechts: (2,2,-,1,1,B)
1 Kannibale zurück
oder 1M zurück, je
nach Wahl oben
(3,2,B,0,1,-)
2 K nach rechts:
(3,0,-,0,3,B)
1 K zurück:
(3,1,B,0,2,-)
2M nach rechts:
(1,1,-,2,2,B)
10
Rettung...Kontrolle (2)
1,1,0
„
„
2,1,1
2,2,1
1,2,1
1,3,1
„
2,1,0
2,0,0
1,2,0
0,2,0
„
0,3,1
„
0,1,0
„
0,2,1
SS - V1.0
1,1,1
2,1,1
(c) W. Conen, FH GE, GIN 2
1,2,1
Gefundener Weg:
1M+1K nach
links:
(2,2,B,1,1,-)
2 M nach rechts:
(0,2,-,3,1,B)
1 K nach links:
(0,3,B,3,0,-)
2 K nach rechts:
(0,1,-,3,2,B)
1K nach links:
(0,2,B,3,1,-) oder
1M nach links:
(1,1,B,2,2,-)
11
Rettung...Kontrolle (3)
„
0,2,1
1,1,1
0,0,0
1,0,0
„
Gefundener Weg:
2K nach rechts
oder
1M+1K nach rechts:
(0,0,-,3,3,B)
Lösungen:
(1) 2K, 1K, 2K, 1K, 2M, 1M+1K, 2M, 1K, 2K, 1K, 2K
(2) 2K, 1K, 2K, 1K, 2M, 1M+1K, 2M, 1K, 2K, 1M, 1M+1K
(3) 1M+1K, 1M, 2K, 1K, 2M, 1M+1K, 2M, 1K, 2K, 1K, 2K
(4) 1M+1K, 1M, 2K, 1K, 2M, 1M+1K, 2M, 1K, 2K, 1M, 1M+1K
SS - V1.0
(c) W. Conen, FH GE, GIN 2
12
Welche Probleme können auftreten?
„
Man findet keine „griffige“ Repräsentation, weil z.B.
‰ Informationen fehlen oder „unscharf“ sind
‰ Informationen unsicher/unglaubhaft sind
„
Man hat ein Problem vor sich, dass
‰ im allgemeinen unlösbar ist (Halteproblem)
‰ im allgemeinen „hart“ zu lösen ist (NP-komplett, EXP)
„
Man kennt keine sinnvolle Problemlösungsmethode für die
gefundene Repräsentation
‰ Im Master werden Sie einige Methoden für verschiedene
Repräsentationen kennenlernen ...
‰ ... und wenn die richtige nicht dabei ist, dann können Sie mit
ihrem Wissen und ihrer Cleverness vielleicht einen (Er-)Finden!
SS - V1.0
(c) W. Conen, FH GE, GIN 2
13
Suche...nochmal generell
„
„
„
Angenommen, sie wollen ein Problem lösen ...
... dann suchen sie also nach einer Lösung
Viele Probleme lassen sich als Graph-Probleme modellieren
‰ manchmal ist das unmittelbar klar (MST, kürzeste Wege, TSP)
‰ manchmal braucht man einen „abstrakten“ Umweg:
„
„
„
„
SS - V1.0
Das Problem spielt sich in einem bestimmten „Realwelt“-Ausschnitt
ab, den man durch eine Menge von „Dingen“ und (regelhaften)
Beziehungen zwischen diesen Dingen beschreiben kann
Diese Dinge (und damit der Ausschnitt) befinden sich zu jedem
Betrachtungszeitpunkt in einem bestimmten Zustand
Modellieren kann man das z.B. durch Parameter/Variablen, denen
Wertebereiche zugeordnet sind und zwischen denen Relationen
bestehen.
Ein Zustand entspricht dann einer konkreten Belegung der
Parameter mit Werten
(c) W. Conen, FH GE, GIN 2
14
Suche (Forts. Problemlösen als Suche)
„
Aus den Wertebereichen und Beziehungen/Regeln ergeben sich die
möglichen Zustände des Realweltausschnitts
„
Es steht eine Menge an Operatoren zur Verfügung, um einen
Zustand in einen Folgezustand zu überführen
„
Ein Problem sieht dann wie folgt aus:
‰ Gesucht ist eine clevere Sequenz von Operatoranwendungen,
die uns von einem gegebenen Ausgangszustand in einen
gewünschten Zielzustand führt.
‰ Regelmäßig wollen wir zudem eine besonders „gute“
Operatorsequenz finden (z.B. eine kostengünstige, wenn wir
Kosteninformationen zu den Operatoren haben)
SS - V1.0
(c) W. Conen, FH GE, GIN 2
15
Suche (Forts. Problemlösen als Suche)
o1
z1
o2
o3
„
„
z2
z5
z8
z3
z6
z9
z4
z7
z10
z11
Ausgangszustand z1, Zielzustand z11
Es gibt viele mögliche Pfade inkl. Sackgassen
(z7,z8) und unerreichbare Zustände (z10)
SS - V1.0
(c) W. Conen, FH GE, GIN 2
16
Suche (Forts. Problemlösen als Suche)
o1
z1
o2
o3
„
„
„
z2
z5
z8
z3
z6
z9
z4
z7
z10
z11
Schauen wir uns noch eine der Sackgassen an
Um einen Weg zum Ziel zu finden, müssen wir einfach eine
Entscheidung für einen Operator zurücknehmen und ändern
Das nennt man „Backtracking“!
SS - V1.0
(c) W. Conen, FH GE, GIN 2
17
Suche (Forts. Problemlösen als Suche)
o1
z2
z5
z8
z3
z6
z11
z9
z4
z7
z10
o2
z1
o3
„
„
„
z11
Es gibt viele verschiedene Wege in diesem Zustandsgraphen
(wieviele?), manche dieser Wege führen zum Ziel, andere nicht
Um garantieren zu können, dass wir das Ziel erreichen (oder
sicher sein können, dass es nicht erreichbar ist), müssen wir
ggfs. alle von z1 aus begehbaren Wege anschauen
Wie können wir das systematisch tun?
SS - V1.0
(c) W. Conen, FH GE, GIN 2
18
Suche (Forts. Problemlösen als Suche)
o1
z1
o2
o3
z2
z5
z8
z8
z3
z4
z6
z9
z9
z7
z11
z9
z5
z8
z6
z9
z2
z5
z8
z3
z6
z10
Tiefensuche
z1
z4
z7
z9
z11
z11
z11
z8
z9
z11
z7
SS - V1.0
(c) W. Conen, FH GE, GIN 2
19
Suche (Forts. Problemlösen als Suche)
1
2
o1
z1
o2
o3
z2
z5
1
4
3
2
z8
1
2
z3
z6
1
4
3
2
z9
1
4
3
2
z11
z7
z9
z5
z8
z6
z9
z2
z5
z8
z3
z6
1
2
z4
z10
Breitensuche
z1
z8
z4
z7
z9
z11
z11
z11
z8
z9
z11
z7
SS - V1.0
(c) W. Conen, FH GE, GIN 2
20
Tiefensuche für Zustandsbäume
„
„
Hilfsdatenstruktur: Knoten k im Zustandsgraph sind mit
einem Zustand beschriftet, erhältlich über k.zustand
Genereller Ablauf für Tiefensuche in einem
Zustandsgraphen mit Baumform:
‰
Algorithm tiefensuche(Knoten start)
„
for each k ∈ Kinder(start) do
‰
‰
„
„
„
if k.zustand = zielzustand then
ƒ print „Ziel gefunden!“; return true;
else if tiefensuche(k) then return true;
return false;
Liefert sicher eine Lösung, wenn es eine gibt!
Achtung: Die Reihenfolge der Kinderbesuche ist nicht
vorgeschrieben, sie können frei wählen!
SS - V1.0
(c) W. Conen, FH GE, GIN 2
21
Breitensuche für Zustandsbäume
„
„
Wir verwenden eine FIFO-Queue queue (also eine Liste, an die hinten angefügt und
vorne entnommen wird, FIFO steht für first-in-first-out)
Genereller Ablauf für Breitensuche in einem Zustandsgraphen mit Baumform:
‰
Algorithm breitensuche(Knoten start)
„
„
„
„
queue.append(start);
// queue leer vor Beginn
while (not queue.empty()) do
‰ k ← queue.deleteFirst();
// Knoten k besuchen
‰ if k.zustand = zielzustand then
ƒ
print „Ziel gefunden!“; return true;
‰ for each c ∈ Kinder(k) do // Knoten k expandieren
ƒ
queue.append(c);
print „Kein Ziel gefunden!“; return false;
Anmerkung: Man kann auch vor dem Einstellen der Kinder prüfen, ob ein Zielzustand
erreicht ist. Wir können zeigen, dass das generell Speicher und Tests spart. Trotzdem
verwenden wir aus Gründen der Einheitlich (s. BestFirst) für die Aufgaben diese Variante.
Größenordnungsmäßig macht es keinen Unterschied...exponentiellen Zeit- und
Speicheraufwand erfordern beide Algorithmen in Best- und Worstcase (DFS nur im Worst
Case!)
SS - V1.0
(c) W. Conen, FH GE, GIN 2
22
Breitensuche für Zustandsbäume
„
„
„
„
„
Liefert sicher eine Lösung, wenn es eine gibt!
Achtung: Die Reihenfolge der Kinderbesuche ist nicht
vorgeschrieben, sie können frei wählen!
Bisher haben wir Zustandsräume in Baumform betrachtet
Da funktionieren beide Verfahren gut: beide sind „komplett“, d.h. sie
finden einen Zielzustand, wenn er existiert und erreichbar ist
Wenn die maximale Tiefe des Baumes d ist und der „flachste“
Zielzustand sich auf der Ebene m befindet und wir einen
„gleichmäßigen“ Verzweigungsfaktor b unterstellen, dann
‰ Tiefensuche: best-case O(m), worst-case O(bd), average case:
hängt von der Verteilung der Zielzustände über die Tiefen
zwischen m und d ab
‰ Breitensuche: best-case = average case = worst-case O(bm), falls
eine Lösung vorhanden ist, sonst best-case = average case =
worst-case = O(bd)
SS - V1.0
(c) W. Conen, FH GE, GIN 2
23
Suche für allgemeine Zustandsgraphen
„
„
Problem: ein Graph, der kein Baum ist, enthält einen Kreis, d.h. gleiche
Zustände können bei der Reise durch den Graphen mehrfach auftreten!
(s. Missionare)
Was passiert, wenn wir unsere Algorithmen auf einen Graphen mit
Kreis loslassen?
‰ Die Tiefensuche läuft immer weiter „geradeaus“ und kann sich in
einer endlosen Schleife „aufhängen“
„ Wenn es eine Lösung gibt, findet die Tiefensuche sie dann nicht!
„ Wenn es keine Lösung gibt, merkt sie es nicht!
‰ Die Breitensuche expandiert gleiche Knoten mehrfach
„ Kein „prinzipielles“ Problem, wenn es eine Lösung gibt – dann
wird diese auch gefunden (und zwar weiterhin die „flachste“) –
die Breitensuche ist also auch im „Wiederholungsfall“ komplett!
„ Wenn es allerdings keine Lösung gibt, dann merkt unser
einfaches Verfahren zur Breitensuche das nicht!
SS - V1.0
(c) W. Conen, FH GE, GIN 2
24
Beispiel: Suche in Kreisen mit Tiefensuche
GE
Startzustand
OB
OB
E
GE
GE
MH
D
OB
E
E
DUI
GE
DUI
MH
D
Zielzustand
SS - V1.0
E
Unendliche Zweige können in dem Baum
entstehen, der die Wege durch den Zustandsgraphen darstellt (also die Suche beschreibt)!
(c) W. Conen, FH GE, GIN 2
25
Suche
„
„
Kann man beide Verfahren noch „retten“?
Idee: wir können kontrollieren, ob es zu Zustandswiederholungen kommt
‰
Knoten markieren bzw. die durch sie repräsentierten Zustände in einer
globalen „CLOSED“-Liste registrieren und nur einmal besuchen
‰
Erweiterung der Algorithmen ist einfach:
„
besuchte Zustände werden in eine CLOSED-LISTE aufgenommen
‰ Suchkosten: linear zur Anzahl der Zustände in der Liste
ƒ mit einem Bitfeld und nummerierten (oder „gut“ gehashten)
Zuständen kann man die (Zeit-)Kosten konstant und den
Speicher „erträglich“ halten (es sei denn, es gäbe sehr viele
Zustände)
‰
Dann zwei Alternativen (zunächst nur für unsere Breitensuche
relevant)
1.
Nur Knoten in queue einstellen, die nicht in CLOSED sind
2.
Nur Knoten besuchen/expandieren, die nicht in CLOSED sind
SS - V1.0
(c) W. Conen, FH GE, GIN 2
26
Suche
„
Übrigens kann das zweite Verfahren besser sein, wenn der Test deutlich
teurer ist, als ein Einstellen sein sollte...denken sie an folgendes:
‰
Nehmen Sie an, die Lösung auf Tiefe m wird dort als letzter Knoten
„angepackt“
‰
Dann wurden vorher bereits bm-1 Knoten expandiert, also bm+1-b
Knoten in die queue gestellt und, bei Variante 1, auch getestet
‰
Wenn Tests im Vergleich zum Einstellen teuer sind (wie in unserem
Fall), dann sollte man unnötige Tests vermeiden
‰
In Variante 2 werden die Kinder von Knoten der Tiefe m zwar
eingestellt, aber nicht mehr getestet, das spart einen Zeitaufwand von
O(bm)*O(n)! (O(n) bei naiver Suche in CLOSED)
‰
Allerdings kostet es mehr Speicher – und wenn man beim Grundablauf
die Kinder vor dem Einstellen auf die Zieleigenschaft testen würde,
sähe die Situation wieder anders aus...uns interessiert aber wieder
vorrangig die Größenordnung des Aufwands, und die kennen wir
bereits: exponentiell für Speicher und Zeit!
‰
Sie sollten beide Varianten beherrschen!
SS - V1.0
(c) W. Conen, FH GE, GIN 2
27
Suche
„
Problem mit dem Markieren von Zuständen im Zustandsgraphen: der ist
häufig gar nicht explizit gegeben (und muß dann auch nicht explizit
repräsentiert werden), sondern wird nur durch einen Startzustand und eine
Zustandsübergangsfunktion beschrieben (vor allem empfehlenswert bei
unendlichen Zustandsräumen)
„
Schwerwiegender: Speichereffizienzüberlegungen!
‰
es kann sehr viele (besuchte) Zustände geben, die muß man sich
dann ev. alle merken
„
„
„
„
SS - V1.0
in der Tiefensuche braucht man sonst nur alle Knoten entlang eines
Weges, also O(d)
bei der Breitensuche ohnehin jeweils komplette Ebenen, also max. O(bm)
bei Tiefensuche kann man sich manchmal auch durch „einfache“
Abbruchkriterien behelfen, um unendliche Zweige zu vermeiden, z.B.
wenn man weiß, dass es nur max. C Zustände gibt (dann macht man
immer noch Arbeit ggfs. doppelt, aber man braucht keine Liste)
Ähnliches geht auch mit Breitensuche. Wenn man sogar weiß, dass eine
Lösung existiert, dann kann man auch auf die Kontrolle von
Wiederholungen verzichten und ist dennoch komplett (macht aber ggfs.
mehr Aufwand, als erforderlich – abwägen: wie oft kommen
Wiederholungen vor?)
(c) W. Conen, FH GE, GIN 2
28
Breitensuche für Zustandsgraphen
„
„
Vermeiden von Wiederholungen für die Variante 2:
Genereller Ablauf für Breitensuche in einem Zustandsgraphen (queue und
closed zu Beginn leer), es wird nur ein Ziel gefunden (um alle zu finden,
schmeißen sie einfach das „return true“ raus und geben nur false zurück,
wenn sie gar keins finden, also zählen sie die gefundenen Ziele am besten
mit – so können sie natürlich auch die normale Breitensuche modifizieren)
‰ Algorithm breitensuche(Knoten start)
„
queue.append(start);
„
while (not queue.empty()) do
‰ k ← queue.deleteFirst();
// Knoten k besuchen
‰ if (not closed.in(k.zustand)) then
// Ist k in closed?
ƒ if k.zustand = zielzustand then
print „Ziel gefunden!“; return true;
closed.append(k.zustand);
for each c ∈ Kinder(k) do // Knoten k expandieren
ƒ
queue.append(c);
„
print „Ziel nicht gefunden“; return false;
SS - V1.0
(c) W. Conen, FH GE, GIN 2
29
Beispiel: Suche mit Pfadkosten
OB
10
20 Startzustand
„
Wir wollen weiterhin von
Gelsenkirchen nach D‘dorf
„
Aber jetzt wollen wir nicht nur
einen Weg finden, sondern
einen guten Weg!
„
Genauer: einen Weg durch
den Zustandsgraphen mit
minimalen Kosten (also einen
„kürzesten Weg“)
GE
DUI
13
15
12
MH
14
35
D
E
Zielzustand
SS - V1.0
(c) W. Conen, FH GE, GIN 2
30
Suche mit Pfadkosten
„
Was können wir tun?
‰
‰
‰
‰
SS - V1.0
Weiterhin Tiefen- oder Breitensuche verwenden und dort einfach nach
allen Lösungen suchen (Lösungen „enumerieren“) und die beste
auswählen! (ggfs. sehr teuer)
wir können auch mit Tiefensuche nur nach einer Lösung suchen und
dann hoffen, dass es die richtige ist...
manchmal wissen wir auch, dass die flachste Lösung die beste ist,
„
z.B. wenn alle Schrittkosten konstant und positiv sind
„
oder gleichmässig und einheitlich mit der Entfernung vom
Startzustand zunehmen (dann geht die normale Breitensuche, die
nur die flachste Lösung findet)
Wenn wir Wiederholungen vermeiden wollen, dann geht das nicht
ohne „Nachdenken“
„
wir müssen uns die bisher besten Kosten zu den Zuständen
merken
„
und im Wiederholungsfall die Erkundung eines Zweigs stoppen,
wenn die neuen Kosten zum wiederholten Zustand nicht kleiner sind
(c) W. Conen, FH GE, GIN 2
31
Suche mit Pfadkosten
„
Und sonst?
‰
‰
‰
SS - V1.0
Wir verwenden die Kosteninformationen, um nach und
nach die vielversprechendsten Wege zu erkunden
(Russell/Norvig nennen das „Uniform cost“-Suche, kein
sehr passender Name)
Im Grunde ist das ein klassischer „Best-First“-Ansatz: der
Knoten mit den niedrigsten aufgelaufenen Kosten wird
zuerst expandiert
Wenn man weiß, dass die Kosten mit der Entfernung vom
Startknoten nicht abnehmen, dann kann man mit der ersten
gefundenen Lösung aufhören – sie muß optimal sein!
(c) W. Conen, FH GE, GIN 2
32
Suche mit Pfadkosten – Best-First
„
Uniform Cost Ablauf für Zustandsgraphen mit Vermeidung von Wiederholungen
nach Variante 2 (bei Uniform Cost kann auch Variante 1 lohnenswert sein, je
nach Probleminstanz)
Die Min-PQueue pqueue und die Closed List sind leer zu Beginn:
‰
„
„
„
Algorithm bestFirst(Knoten start)
„
start.cost ← 0; pqueue.insert(start); closed.append(start);
„
while (not pqueue.empty()) do
‰ k ← pqueue.deleteMin();
// Knoten k besuchen
‰ if (not closed.in(k.zustand)) then
// Ist k in closed?
ƒ
if k.zustand = zielzustand then
print „Ziel gefunden!“; return true;
closed.append(k.zustand);
ƒ
for each c ∈ Kinder(k) do // Knoten k expandieren
c.cost ← k.cost + kante(k,c).cost;
pqueue.insert(c);
// Knoten c in PQueue ablegen
Der Wert der Knoten wird im Feld cost abgelegt.
Der Wert einer Kante wird ebenso abgelegt.
Ist fast genau Dijkstra, nur ein bisschen „blöder“, weil mehrfaches Einstellen
statt Update (kann zu spektakulär höherem Speicheraufwand führen!)
SS - V1.0
(c) W. Conen, FH GE, GIN 2
33
Uniform-Cost-Ablauf
OB
10
Startzustand
20
GE
pqueue (und closed in Klammern dahinter):
DUI
13
15
12
MH
14
35
D
SS - V1.0
E
GE/0
E/15, OB/20 (GE)
OB/20, MH/27, GE/30, D/50 (GE,E)
MH/27, GE/30, DUI/30, GE/40, D/50 (GE,E,OB)
GE/30, DUI/30, E/39, GE/40, DUI/40, D/50
(GE,E,OB,MH) GE in Closed!
DUI/30, E/39, GE/40, DUI/40, D/50 (GE,E,OB,MH)
E/39, GE/40, DUI/40, OB/40, MH/43, D/44, D/50
(GE,E,OB,MH,DUI) E,GE,DUI,OB, MH in Closed!
D/44, D/50, OB/50, MH/53 (GE,E,OB,MH,DUI)
D/44 gefunden!
(c) W. Conen, FH GE, GIN 2
34
Was geht noch „uninformiert“?
„
Simples „Greedy“:
‰
„
Wenn wir nicht auf Wiederholungen achten, kann das zu endlosem
Pendeln zwischen zwei Zuständen führen
‰
„
Verwende von deinem Knoten aus jeweils den günstigsten nächsten Schritt.
Im Beispiel würde er sich zwischen E und MH einpendeln
Also achten wir auf Wiederholungen (von Zuständen)
‰
Das gibt aber noch keine Garantie, dass wir auch einen Zielzustand finden
(wir enden ggfs. in einer Sackgasse, die auch erst entstanden sein kann,
weil wir die Nachbarn bereits besucht haben)
„
„
Im Beispiel würde er in OB hängen bleiben
Also verwenden wir Backtracking („Zurückspringen“) und führen eine
CLOSED-List bereits verwendeter Kanten!
‰
‰
SS - V1.0
Im Beispiel besuchen wir dann folgende Kanten (und damit die Knoten):
{GE,E}, {E,MH},{MH,DUI},{DUI,OB},Backtrack,{DUI,D}
Also finden wir in diesem Beispiel nicht die optimale Lösung (aber immerhin,
wir finden jetzt sicher eine Lösung – das kann auch mal die Beste sein!)
(c) W. Conen, FH GE, GIN 2
35
Was geht noch „uninformiert“?
„
„
„
Wir können auch noch mittels Tiefensuche (depth-first search
oder kurz: DFS) die Breitensuche simulieren (mit oder ohne
Schrittkosten)
‰ dann brauchen wir nicht auf Wiederholungen zu achten
‰
und haben trotzdem ein vollständiges Verfahren für endliche
Zustandsräume
Das geht, indem wir ein Tiefenlimit einführen
‰ Setze das Limit zu Beginn auf 0 (dann wird nur der Startzustand
angeschaut)
‰ Erhöhe das Limit in jeder Runde um eins und beginne immer
wieder oben mit Tiefensuche, wiederhole das solange bis die
erste Lösung gefunden wurde
Dieses Verfahren nennt sich Iterative Deepening und ist für den
Fall ohne Schrittkosten die sinnvollste Wahl
SS - V1.0
(c) W. Conen, FH GE, GIN 2
36
Iterative Deepening
„
„
„
„
„
Die Implementierung ist simpel: Wie der Algorithmus Tiefensuche, aber
mit Abbruch des Abstiegs, wenn das Tiefenlimit erreicht ist (also
einfach ein Limit vorgeben und beim Aufruf von Tiefensuche einen
Parameter Tiefe, der schrittweise erhöht wird, hinzufügen – bei
Erreichen des Limits nicht mehr expandieren!)
Das Verfahren ist besser, als DFS, weil es sich nicht in endlose
Zweige verlaufen kann
Im Vergleich zur Breitensuche wiederholt es zwar eine Menge, aber es
muss sich wesentlich weniger merken (linear zur Lösungstiefe) und es
expandiert vor allem die Knoten auf Tiefe m nicht mehr! (es wird ab
einer gewissen Tiefe dramatisch günstiger als Breitensuche)
Es findet allerdings die beste Lösung nur, wenn es die flachste ist (wie
Breitensuche).
Man kann das leicht zu einem optimalen Verfahren machen, wenn man
sich die Kosten der besten bisher gefundenen Lösung merkt:
‰
‰
SS - V1.0
Solange es auf der Limitebene noch Knoten mit niedrigeren Kosten gibt, wird
weiter iteriert ...
...und dabei Knoten nicht expandiert, wenn sie nicht günstiger als die beste
bisherige Lösung sind.
(c) W. Conen, FH GE, GIN 2
37
Und was ist „informierte“ Suche?
„
Wenn wir zu den Zuständen z noch heuristische Informationen
h haben, die es uns erlauben, die Entfernung zum
nächsten/besten Zielknoten zu schätzen, also h(z)
„
Für einen gegebenen Knoten k mit Zustand k.zustand = z
können wir die bisherigen tatsächlichen Kosten des Wegs zu k,
angegeben durch g(k) und die noch zu erwartenden Kosten, h(k)
= h(k.zustand) = h(z) addieren
„
Diese Summe f(k) = g(k)+h(k) verwenden wir dann als
„Distanzwert“ in unserer PQueue für den Best-First-Algorithmus
von vorn
„
Dieses berühmte Verfahren nennt sich A* (Beispiel nächste
Folie), gesprochen „ä-star“
SS - V1.0
(c) W. Conen, FH GE, GIN 2
38
A*-Ablauf (auf Wiederholungen wird nicht
geachtet)
OB
10
Startzustand
20
GE
DUI
13
15 pqueue:
12
MH
14
35
D
SS - V1.0
Heuristische Informationen:
h(GE) = 30, h(OB) = 24, h(DUI) = 14,
h(MH) = 16, h(E) = 20, h(D) = 0
E
GE/0+30
E/15+20, OB/20+24
MH/27+16,OB/20+24, D/50+0,GE/30+30
OB/20+24, D/50+0, DUI/40+14,E/39+20, GE/30+30
DUI/30+14,D/50+0, DUI/40+14,E/39+20, GE/30+30,
GE/40+30
D/44+0, D/50+0, DUI/40+14,E/39+20, MH/43+16,
GE/30+30, GE/40+30, MH/43+16 (fertig)
(c) W. Conen, FH GE, GIN 2
39
Informierte Suche mit A*
„
„
Wenn die verwendete Heuristik „admissible“ ist – das ist sie, wenn sie die
tatsächlichen Kosten unterschätzt, dann ist A* optimal für endliche
Zustandsräume (bei nicht-negativen Pfadkosten, wie wir generell annehmen)
A* ist außerdem auch noch optimal effizient relativ zur Klasse der Algortihmen,
die einen solchen Suchbaum explorieren. Das Argument ist einfach:
‰
‰
‰
„
„
„
A* untersucht alle Knoten mit niedrigeren tatsächlichen Kosten, als der optimale
Zielknoten
wenn ein anderer Algo einen dieser Knoten ausläßt, dann kann er nicht sicher sein,
das Optimum gefunden zu haben
Manchmal kann ein anderer Algo „zufällig mal“ besser sein, aber nicht immer! (A*
expandiert auch Knoten mit dem gleichen Gewicht wie der optimale Zielknoten, die
muss man aber nicht unbedingt anschauen!)
Das Vermeiden von Wiederholungen spielt auch wieder eine Rolle für die
Effizienz (und die Vollständigkeit des Algo) – hier helfen konsistente Heuristiken
(sie erfüllen die Dreiecksungleichung und sind admissible)
Natürlich können wir auch h verwenden, um „greedy“ loszulaufen (diesmal
stürzen wir uns nicht „greedy“ auf Kanten, sondern auf Nachfolger) – mit
ähnlichen Problemen und Resultaten, wie oben
...und einiges mehr (Praktisch relevant: Speicherbeschränkte Varianten von A*!)
SS - V1.0
(c) W. Conen, FH GE, GIN 2
40
Und sonst noch?
„
„
Man kann auch noch anders modellieren – man verwendet nur
komplette Lösungen und versucht dann durch Operatoren von einer
Lösung zur nächsten zu gelangen
‰ Kann z.B. beim TSP sinnvoll sein: Zustände sind dann komplette
Rundtouren, man sucht die beste.
‰ Man kann auch „partielle“ oder ungültige „Lösungsvorschläge“
zulassen und dann nach der besten gültigen Lösung in diesem
erweiterten Zustandsraum suchen
Und vieles mehr...wie wir noch sehen werden (aber leider erst im
Master)
SS - V1.0
(c) W. Conen, FH GE, GIN 2
41
Literatur zur Suche in Zustandsräumen
„
Russell, Norvig: Artificial Intelligence – the Intelligent Agent
Approach, Prentice-Hall, 2nd Edition (unbedingt die zweite
Auflage verwenden mit einem aktuellen Printing), International
Edition (billiger als das amerikanisch/kanadische Original), 2003
‰ Russell ist Professor in Berkeley, eine der öffentlichen Top-Unis
(eine/die andere öffentliche Top-Uni in Informatik ist die UMICH
in Ann Arbor)
‰ Norvig ist Director of Search Quality bei Google
‰ Das Buch ist das „Standardwerk“ zu KI (=künstlicher Intelligenz),
es hat ein paar kleine Schwächen, z.B. wenn es um Optimierung
geht oder wenn man sehr präzise Details braucht, es gibt aber
einen exzellenten Überblick über viele Teilgebiete der KI (und
fast alles spannende gehört da „irgendwie“ zu...zumindest sehen
das die KI‘ler so...stimmt natürlich nicht so ganz, oder doch... ;-)
SS - V1.0
(c) W. Conen, FH GE, GIN 2
42
Ein kurzer Ausflug in die Welt
„harter“ Probleme... [der 2. Teil zur
ersten Vorlesung in GIN1B]
Prof. Dr. Wolfram Conen
21. Juni 2005
Vorlesung zu GIN2
Alan Turing – Mini-Rückblick auf die
erste GIN1b-Vorlesung
„
„
„
„
23. Juni 1912, London
1936: On computable
numbers ... (es gibt keine
„definite“ Methode, die für
jede mathematischlogische Aussage
entscheiden kann, ob sie
beweisbar ist oder nicht.)
1939-40: Entwicklung der
Bombe (zur Entschlüsselung
der deutschen EnigmaCodes)
7. Juni 1954: Selbstmord
Von der Intuition zur Exaktheit
„
Hilbert suchte nach einem Verfahren, das für jede mathematischlogische Aussage einen Beweis oder eine Widerlegung liefert:
„Das Entscheidungsproblem ist gelöst, wenn man ein Verfahren kennt, das
bei einem vorgelegten logischen Ausdruck durch endlich viele
Operationen die Entscheidung über die Allgemeingültigkeit bzw.
Erfüllbarkeit erlaubt. (...) Das Entscheidungsproblem muss als das
Hauptproblem der mathematischen Logik bezeichnet werden“,
Hilbert, Ackermann, Grundzüge der theoretischen Logik, 1928
„
„
„
Um zu zeigen, dass es so ein Verfahren gibt, könnte man einfach
eines angeben.
Turing wollte zeigen, das es ein solches Verfahren nicht geben
kann!
Er mußte etwas finden, dass Hilberts Intuition eines Verfahrens
entsprach, diese präzisierte und wesentliche Eigenschaften aller
anderen „möglichen“ Verfahren beinhaltete und so als „Prototyp“
dienen konnte:
„
Wenn der Prototyp die gewünschte Antwort nicht geben kann, dann kann es
auch kein anderes Verfahren!
Die Turing-Maschine:
Ein Modell der Berechenbarkeit
...
0 0 0 0 0 0 1
Zustand: z1
„Programm“
...
Falls Zustand == z1 und
Zeichen unter Schreib/Lesekopf == 0,
dann schreibe 1,
gehe in den Zustand z1 und
bewege dich ein Feld nach rechts.
Kurz: (z1,0) Æ (1,z1,R)
(z1,1) Æ (0,z1,R)
(z1,_) Æ (_,ende,L)
Was macht dieses Programm?
Die Turing-Maschine:
Eine Berechnung
...
1 0 0 0 0 0 1
Zustand: z1
„Programm“
(z1,0) Æ (1,z1,R)
(z1,1) Æ (0,z1,R)
(z1,_) Æ (_,ende,L)
...
Die Turing-Maschine:
Eine Berechnung
...
1 1 1 1 1 1 1
(z1,0) Æ (1,z1,R)
(z1,1) Æ (0,z1,R)
(z1,_) Æ (_,ende,L)
Zustand: z1
„Programm“
...
Die Turing-Maschine:
Eine Berechnung
...
1 1 1 1 1 1 0 0
(z1,0) Æ (1,z1,R)
(z1,1) Æ (0,z1,R)
(z1,_) Æ (_,ende,L)
Zustand: z1
„Programm“
...
Die Turing-Maschine:
Eine Berechnung
...
1 1 1 1 1 1 0
(z1,0) Æ (1,z1,R)
(z1,1) Æ (0,z1,R)
(z1,_) Æ (_,ende,L)
...
Zustand: ende
„Programm“
Die Berechnung hält an!
Noch wichtig: Programm, Eingabe und Zeichensatz sind endlich.
Universelle TM (1)
„
Turing führt eine Turing-Maschine UTM ein, die
andere Turingmaschinen simulieren kann
‰
Bisher:
„
‰
Jetzt:
„
„
„
Eingabestring x → Turingmaschine TM → Ausgabestring y;
kurz: TM(x) = y
Kodieren des Anweisungen der Turingmaschine als String z
Eingabestring zx → Turingmaschine UTM simuliert TM und
wendet die Simulation auf x an → Ausgabe y;
kurz also: UTM(zx) = y bzw. UTM(TM,x) = y
Die UTM von Turing kann alle Turingmaschinen
simulieren...sie ist in einem bestimmten Sinn
universell
Universelle TM (2)
Eine „binäre“ UTM von Stephen V. Gunhouse mit 176 Zuständen
B0.UTM=10000001011101000000010101101000000100101101000010101101000001000101101010011101000001010101110100001110100001001
0101110100100110100001010010110101000110100010001010110101010011101001010101001011101000001110100000000011101000000101011
0100000100101101001000110100010101001011010010001000110100101010001110101000011101001010010010111010010001001011010100010
1001110100100000011101010100101110100100101001110101010101011101000000111010010101010101110100001001101000110100010100001
1010000010111010100101000101110100010101011101001000011010101001010010110101001011010010010101101010001010110100101001011
1010101000010011101001010101011101000101010011101010000101101010010001101010001010110100010000101110101001011010010010011
0101001010101101001001000101110101010001011101010101000110101010010101101001000101101010101001011001110100010010111010010
0000111010001000101110100100011010000010101101001001001001110100001001011010010011010000101010110101001001011101001101000
0000111010001001010110100100010101110100010100101110101001000101110100100001011101001010001010111010100100010111010010010
0101110100101000101110100100100011101001010000111010001000110100101001001110100100010011101000011101001010011101010010011
1010010100111010010101011101000000111010100010001011101010001010010111010100000101110100100000111010100001010110100010011
0101000100101110101001001110101001010111010010101001010111010100010010101110101010100010111010100100101011101000010101101
0101000101011001110101010101010010110100010101101010100001011101010100100101101010000001110100010000011101000100011101000
0111010101010010101110101000001001110100101000001110100001110101010010111010010000010111010000000011010010011010000101010
1011010100000010110100001010101101000100110100001000011010001010110100000100110101001110100000010101011101001011010000100
0101101000011101010100101010111010010011010101010101010110100100110100010010101101001001010111010101011101010001101010100
0101010110101000010010110101010010111010000000111010001010010101101001000010011101000101000101101000000011101000101001011
1010010001101000101010101011010010001101001000101010110100010000110101010101000111010101010100100111010010000100111010000
0010011010000010001101001000110100001001011010001010111010100100100111010100000111010111010101010101010101101001001010101
0110101000010011010001010011101010101000011101010101010000111010000100101110101000101110101000011101001010010101011101010
0001110100101010001011101010000111010100010010111010001000101110101010101110101010011101001010101010101110101010011101010
0001010101110100000000110100000001011010100001000110101000000011010001001000110100010010010110101010011101010100001011101
0000001110101001000010111010100000011101010010010101011101000110101000101010101110100011010100100010101110100000011101010
1000001011101000110101001001001011101000110101010101011101000101001110101001110100110101001010010101110100110101001010100
1011101001101010010101010101110100110100101010101110100101000111010010011010001001011010101000100101110101010010011010100
1010011010100011010101001000101101010001101010100100101011010000101110101001010101011010010000110101010010101010110100100
0011010101010000101101001000011010000000001110100001011101010101001001011101000010111010101010010101011101000010111010010
0100010111010100000101011101001010000101110110111001110101000010110011101010101010101101000010111010101010001010111010000
0101011010101101000100101010110100100101001011010100111010000010010101110100001010110101001010010110101001110100000101001
0111010101000101110100001
Was kann die Turing-Maschine?
„Alles Machbare!“
Genauer formuliert dies die Church-Turing-These:
„Genau das, was wir intuitiv für „effektiv berechenbar“ halten, kann
durch die Turing-Maschine tatsächlich berechnet werden.“
Wir nennen ab jetzt etwas berechenbar, wenn es eine Turingmaschine gibt
(geben kann), die mit dem richtigen Ergebnis anhält!
Es wurden noch viele andere Modelle der Berechenbarkeit „erfunden“
(generelle rekursive Funktionen, λ-Definierbarkeit, Kombinatorische
Systeme, Termersetzungssysteme, Registermaschinen, ...),
‰
‰
aber alle (zumindest alle physikalisch realisierbaren) erwiesen sich als
„äquivalent“, sie können die gleichen Probleme lösen!
Zeigen kann man dies, in dem man ein Modell durch ein anderes simuliert
(und vice versa).
Merke: Modelle der Berechenbarkeit präzisieren den Begriff Algorithmus /
Lösungsverfahren (F1)
Noch ein Beispiel...fleissige Bieber
„
„
Bieber bauen Dämme aus Baumstämmen...
Als Turingmaschine sieht das z.B. so aus:
_/1,R
1/1,R
z1
z2
z3
1/1,R
_/1,L
_/1,L
1/1,L
_
„Satz:
halt
_
1
_
1
_
1
_
1
_
1
_
1
_
_
_
_
Die Rado-Funktion kann nicht durch eine Turing-Maschine berechnet werden
Luftholen... [Ende des Rückblicks]
„
Wahre Titanen des Geistes (auch, wenn manche von Ihnen
ein unglückliches Ende gefunden haben...) legten die
Grundlagen.
„
Wir wissen jetzt: es gibt Probleme, die sich nicht lösen
lassen, obwohl sie „mathematisch genau“ beschrieben sind!
„
Modelle der Berechenbarkeit erlauben eine Analyse der
Berechenbarkeit/Lösbarkeit eines Problems! (F2)
„
Aber: wie schwer kann es werden, ein konkretes Problem
tatsächlich zu lösen?
Komplexität
„
„
Gegeben: ein berechenbares
Problem P und ein bestimmtes
Modell der Berechenbarkeit (z.B.
Turing-Maschinen oder RAMs)
Zentrale Frage: Erfordert die
Lösung des Problems einen Zeit
oder Speicheraufwand, der
polynomial oder
(möglicherweise) exponentiell
zur Größe der Eingabe ist?
Zur Größenordnung:
Annahme: Jede Operation benötigt eine
Mikrosekunde
Eingabegröße
n = 10 =100 =1000 =1000000
Zeitaufwand als Funktion von n:
n
10us 0.1ms 1ms 1s
n2
0.1ms 10ms 1s
2W
n5
0.1s 3h
30J 3*1016J
2n
•
1ms 3*1016 J 3*10288J groß
3*1016 J ist übrigens mehr als eine
Millionen mal die geschätzte
bisherige Dauer des Universums...
TSP - Traveling Salesman Problem
„
Gegeben ist eine Menge von „Städten“, so dass jede von jeder
direkt erreichbar ist (z.B. per Hubschrauber ;-)
„
Mit den Reisen sind Kosten verbunden (wir nehmen mal an, dass
die Dreiecksungleichung erfüllt ist, d.h. kein „Umweg“ ist kürzer, als
die direkte Verbindung)
„
Gesucht ist eine Rundtour durch alle Städte, die minimale Kosten
über alle möglichen Rundtouren hat (jede Stadt wird nur einmal
besucht)
„
Das ist das „metrische“ TSP
TSP
TSP
TSP
„
„
„
„
Wir können das TSP als
Graphproblem auffassen
n Knoten (die Städte), n*n-1
ungerichtete Kanten (die
Wege)
Der Graph ist also
vollständig (er kann natürlich
auch unvollständig sein...)
Wenn Hin- und Rückfahrt
zwischen je zwei Städten
gleich teuer sind, dann ist es
ein symmetrisches TSP
„
Naiver Lösungsalgo:
‰
‰
Suche zufällig einen
Startpunkt aus
Generiere nach und nach alle
möglichen Rundtouren
„
„
„
Vergleiche die Länge jeder
Rundtour mit der Länge der
bisher besten
Merke dir ggfs. die neue
„beste“ Tour
Aufwand?
‰
‰
n-1*...*2*1 = (n-1)!
im symmetrischen Fall (n-1)!/2
TSP
„
Für ein symmetrisches TSP gibt es also eine Menge
Lösungskandidaten:
‰
‰
‰
„
n = 5:
n = 10:
n = 20:
12 Touren
181.440 Touren
60.822.550.204.416.000 Touren
(~ 61 Billiarden Touren)
Das heißt: Die Anzahl von Lösungskandidaten explodiert
exponentiell mit steigendem n
TSP
„
Das geht natürlich besser!
„
Aber wenn wir wirklich das Optimum haben wollen, dann erfordert
jedes exakte Lösungsverfahren im worst case exponentiellen
Aufwand
‰ es sei denn, NP = P (was das ist, sehen wir gleich)
„
Man kann metrische TSPs aber zu polynomialen Kosten
approximieren (wie gut?)
Approximationen metrischer TSPs
„
Jetzt kommt unser alter MST zu Ehren:
‰
Gegeben: 8 Knoten, vollständiger, ungerichteter Graph
v
MST
„Doppelter“ MST: jede
Rundtour mit
Kante wird verdoppelt und die
„Abkürzungen“
Kanten eines jeden Paares werden
in gegensätzliche Richtungen orientiert
Approximationen metrischer TSPs
„
Algo APP_TSP
‰
‰
‰
‰
Bestimme einen MST T
Wähle zufällig einen Knoten
v und laufe komplett um den
Baum (=verdopple alle
Kanten und mache eine
Eulertour)
Vermerke dabei die Besuche
der Knoten in einer Liste L
Entferne alle bis auf das
jeweils erste Vorkommen der
Knoten, zurück bleibt der
Kreis C (oder kürze bei der
Tour gleich ab)
[C ist ein Preorder-Traversal
von T]
„
Kosten für Prim:
O(m + n log n) = O(n2) für
kompletten Graph
O(1) (Auswahl)
O(n) (n-1 Kanten * 2)
„
O(n) Speicher
„
O(n)
„
Dominiert von MST-Kosten,
insgesamt O(n2)
„
„
Approximationen metrischer TSPs
„
Satz: C ist eine 2-Approximation der kürzesten Tour (Beweis
gleich)
„
Aber: was ist eine 2-Approximation?
„
Gegeben ist ein Optimierungsproblem OPT mit der optimalen
Lösung C*
„
Gegeben ist außerdem ein Approximationsalgorithmus, der eine
Näherungslösung zu OPT bestimmt, seine Lösung hat den Wert C
Approximationen metrischer TSPs
„
C* = optimale Lösung, C = approximative Lösung
„
Approximationsfaktor für eine bestimmte Instanz bei einem
Minimierungsproblem: C/C*
„
Approximationsfaktor des Algorithmus:
p = sup C/C*
Hier wird das Supremum (also die Schranke nach oben) über alle
Instanzen bestimmt (also eine Schranke für die schlechteste
Approximation, die der Algo liefert)
„
p ist hier immer größer als 1 (klar, C kann ja nicht besser als C*
sein)
Approximationen metrischer TSPs
„
Relativer Fehler ε des Approximationsalgorithmus:
ε = sup |C-C*|/C*
„
Für Minimierungsprobleme äquivalent zu
C · (1 + ε) C*, d.h. p · (1 + ε)
„
Approximationsschema:
‰ Eingabe: eine Probleminstanz I und ein ε > 0
‰
Ausgabe: eine (1+ε)-Approximation des Optimums (für
Minimierungsprobleme)
‰
D.h. einem Approximationschema können sie sagen, welche
Qualität es liefern soll! (sie geben also den maximal erlaubten
relativen Fehler ε vor)
Approximationen metrischer TSPs
‰
2-Approximation: ε = 1,
„
„
‰
d.h der relative Fehler ist schlechtestenfalls 1, der
Approximationsfaktor also nicht schlechter als (1+ε)=2,
d.h. C ist höchsten doppelt so teuer, wie C*
Zurück zum Satz, dass APP_TSP eine 2-Approximation
liefert (zur Erinnerung: T ist unser MST, C unser Kreis)
„
„
„
„
Wenn man aus der optimalen Tour eine Kante entfernt, hat
meinen einen Spannbaum, d.h. C* ≥ Cost(T)
In L wird jede Kante aus T zweimal aufgenommen, also
Cost(L) = 2*Cost(T)
C entsteht aus L durch Hinzufügen von Abkürzungen. Weil die
Dreiecksungleichung gilt, kann C nicht länger als L sein, also
C · Cost(L)
Insgesamt also: Cost(T) · C* · C · Cost(L) = 2*Cost(T)
Approximationen metrischer TSPs
‰
Christofides hat 1976 einen verbesserten polynomiales 3/2Approximations-algorithmus gefunden (mittels MST und minimum
weight matching)
‰
Engebretsen hat 1999 gezeigt, dass kein Approximationsfaktor
5381/5380 - ε für ε > 0 möglich ist
‰
Spannend wird es, wenn man gute polynomiale
Approximationsschemata (AS) findet:
„ PTAS sind AS, deren Laufzeit polynomial zu n bei gegebenem ε ist
(PT = polynomial time)
„ FPTAS sind AS, deren Laufzeit polynomial zu n und 1/ε (FPT = fully
polynomial time)
„ O(n1/ε), O(n/ε): Beides PTAS, nur das zweite FPTAS
Approximationen metrischer TSPs
„
„
„
„
„
Mit einem solchen Schema können sie in der Praxis wie folgt arbeiten:
Sie haben 2 Wochen Zeit, bis sie ein möglichst gute Lösung brauchen:
‰ sie bestimmen mit der Abschätzung ε und lassen das PTAS loslaufen,
‰ nach ungefähr 2 Wochen wird es in der entsprechenden Güte antworten –
‰
dann brauchen sie aber natürlich eine Abschätzung, in der auch die
Konstanten enthalten sind und sie müssen wissen, wie lang elementare
Operation (im Mittel) wirklich brauchen.
Sie können also den Trade-Off zwischen Aufwand und Güte bewusst steuern
– was will man mehr!
‰ (wenn dann die zur Verfügung stehende Zeit auch noch für „gute“
Lösungen reicht, dann haben sie wirklich Glück ;-)
Für euklidische TSP (=metrisch und in der Ebene, also ohne Berg-auf, Bergab) hat Arora 1996 ein gutes PTAS gefunden (und ist damit berühmt
geworden)!
Uneingeschränktes TSP ist in der Klasse der schwierigsten
Optimierungsprobleme (NPO-complete), d.h. es gibt keine 2n^ε-Approximation
für kleine ε (Orponen, Mannila, 1987)
Wie schwer ist TSP „wirklich“?
„
TSP lässt sich mit einer nicht-deterministischen Turingmaschine, die
den richtigen Abarbeitungsweg „raten“ kann, in polynomialer Zeit
lösen
‰ Man sagt: TSP ist in der Komplexitätsklasse NP
„
Es ist aber keine deterministische Turingmaschine bekannt, die alle
TSPs in polynomialer Laufzeit lösen könnte
‰ Sonst wäre TSP in der Komplexitätsklasse P. Zu Problemen in P
(z.B. Kürzeste-Wege-mit-nicht-negativen-Kanten) sagt man auch:
Problem dieser Problemklasse sind effizient lösbar!
„
Offene Frage: Ist P=NP?
TSP und Komplexität
„
„
„
„
TSP gehört zu den „schwersten“ Problemen in NP: wenn man
dieses polynomiell lösen könnte, dann ginge das auch mit allen
anderen Problemen aus NP!
‰ TSP ist deshalb ein NP-vollständiges Problem
Als erstes NP-vollständiges Problem wurde 1971 von Cook das
SAT-Problem identifiziert: Erfüllbarkeit von aussagenlogischen
Formeln! (kennen sie aus GIN1b) – alle anderen Probleme in NP
lassen sich auf dieses Problem „reduzieren“.
Es gibt noch viele andere NP-harte Problem: Matching-Probleme
(„Stabile Heirat“), Graph-Färbe-Probleme, div.
Scheduling/Planungsprobleme (Stundenplan! ;-), ganzzahlige
Programmierung, usw. – einen Überblick finden sie auch im Web:
Es gibt auch noch sogenannte NP-harte-Probleme, diese sind
„mindestens so schwer, wie Probleme aus NP“ und ihre Lösung
würde zur Lösung aller Probleme aus NP führen (alle NP-Probleme
lassen sich auf sie reduzieren), aber es ist nicht sicher, ob sie
wirklich in NP liegen (oder aber in EXP, also sicher exponentiellen
Aufwand erfordern, auch im nichteterministischen Fall!)
Mögliche Auswege für den
Informatiker (1)
„
Nicht alle Probleminstanzen sind schwer!
Vielleicht gehören „unsere“ Probleminstanzen dazu?
‰ manchmal kann man das generell sagen (wenn das
Problem nicht „stark NP-schwer“ ist), manchmal helfen
Experimente!
‰
manchmal findet man auch „langsam wachsende“
exponentielle Algorithmen
Mögliche Auswege für den
Informatiker (2)
„
Alternativ kann man vielleicht approximieren!
‰
Man sucht nach polynomialen Algorithmen, die eine
bestimmte Güte der Lösung garantieren können (etwa immer
mindestens halb so gut wie das Optimum)
‰
Manche Probleme lassen sich nicht sinnvoll approximieren,
auch das kann man formal untersuchen!
Mögliche Auswege für den
Informatiker (3)
„
„
„
„
Oder man wendet eine Heuristik an, die versucht mit
Daumenregeln oder beispielsweise in Anlehnung an natürliche
Prozesse „schnell“ „gute“ Lösungen zu finden
‰
Das kann natürlich grandios schief gehen!
‰
Auch hier kann man einiges analysieren!
Bei allem hilft die Theorie zu Komplexität und Approximierbarkeit,
die aus den Modellen zur Berechenbarkeit hervorgegangen ist:
Die Analysen von Komplexität und Approximierbarkeit stecken den
Rahmen für die erreichbare Lösungsqualität ab! (F3)
Sie weisen den Weg zu verwandten Problemen und bereits
bekannten exakten, approximativen oder heuristischen
Problemlösungen! (F4)
Rückblick: Bedeutung für den
Informatiker
Zentrale Aufgaben: Problemanalyse und Problemlösung.
(F1) Wie lassen sich mein Problem und eine ev.
Lösung präzise beschreiben?
‰
Präzision ist eine wesentlich Voraussetzung für Analyse und
Lösung von Problemen (Übergang zur Logik)
‰
Modelle der Berechenbarkeit präzisieren den Begriff
Algorithmus/Lösungsvorschrift
(F2) Ist das Problem überhaupt lösbar?
‰
Modelle der Berechenbarkeit erlauben eine Analyse der
Berechenbarkeit/Lösbarkeit eines Problems!
Rückblick: Bedeutung für die
Anwendungsentwicklung (2)
(F3) Wenn ja, kann ich es auch mit vertretbarem
Aufwand lösen?
‰
‰
Die Komplexitätstheorie hilft uns, die Schwere des Problems
einzuschätzen!
Die Analysen von Komplexität und Approximierbarkeit stecken den
Rahmen für die erreichbare Lösungsqualität ab!
(F4) Wie kann ich das Problem lösen?
‰ Die Komplexitätstheorie hilft auch, verwandte Probleme zu
identifizieren!
‰ Die Analysen von Komplexität und Approximierbarkeit weisen den
Weg zu verwandten Problemen und bereits bekannten exakten,
approximativen oder heuristischen Problemlösungen! (Übergang
zur Algorithmik)
Rückblick: Bedeutung für die
Anwendungsentwicklung
„
„
Zentrale Aufgaben: Problemanalyse und Problemlösung
Was genau ist mein Problem? Ist das Problem überhaupt lösbar?
‰
‰
‰
„
Historische Bedeutung: Entwicklung von Formalismen zur Beschreibung von
Problemen und Lösungseigenschaften.
Entwicklung der Modelle der Berechenbarkeit zur Präzisierung des
Algorithmusbegriffe und zur Untersuchung der Lösbarkeit von Problemen
Speziell die Turingmachine hat die Entwicklung von „mechanischen“
Computern und Programmiersprachen beeinflußt und Intuitives mit Formalem
verbunden!
Wenn ja, kann ich es auch mit vertretbarem Aufwand lösen?
‰
‰
‰
Turingmachine diente als Grundlage zur Entwicklung der Komplexitätstheorie –
wie lange braucht eine deterministische bzw. eine nicht-deterministische
Turingmachine für diese Problem?
Komplexität und Approximierbarkeit sind wesentliche Problemeigenschaften –
nach welchem Typ von Algorithmus soll ich suchen (exakte Lösung,
approximative Lösung, heuristische Lösung)
Die moderne Algorithmik ist eng mit der Forschung zu Komplexität und
Approximierbarkeit verbunden - ganz wichtig für den Anwendungsentwickler:
Problemlösen durch Nachschlagen!
Herunterladen