ppt

Werbung
Praktische Informatik 1
Prof. Dr. H. Schlingloff
4. 2. 2008
Sortieren
• Oft lohnt es sich, Daten zu sortieren
 einmalige Aktion, Abfragen häufig
 oft gleich beim Eintrag möglich
• Wie sortiert man eine unsortierte Reihung?
 selection sort: größtes Element an letzte Stelle,
zweitgrößtes an zweitletzte Stelle, usw.
- einfach aber ineffizient!
 insertion sort: In absteigender Reihenfolge: das
i-te Element in den sortierten Bereich [i+1, …, n]
einsortieren
- noch ineffizienter!
Beispiele
selectionSort
insertionSort
Bubblesort
• Ineffizienz vorher: „weites“ Vertauschen mit
•
Informationsverlust
Idee: Tauschen von Nachbarn
 Falls zwei Nachbarn in verkehrter Reihenfolge, vertausche
sie
 nach dem i-ten Durchlauf sind die obersten i Elemente
sortiert
static void bubbleSort (int[] a) {
for (int k=n-1; k>0; k--) {
for(int i=0; i<k; i++) {
if (a[i]>a[i+1]) swap(a,i,i+1); } }
Quicksort
• berühmter, schneller Sortieralgorithmus
• wähle ein „mittelgroßes“ Element w=a[k], alle
kleineren nach links, alle größeren nach rechts
• rekursiv linke und rechte Teile sortieren
public static void qSort(int[] a, int lo, int hi) {
if (lo<hi) {
int pivIndex = partition(a,lo,hi);
qSort(a,lo,pivIndex-1);
qSort(a, pivIndex+1, hi); }
}
public static void quickSort(int[] a) {
qSort(a,0,n-1); }
Partitionierung
private static int partition(int[]a, int lo, int hi) {
swap(a,(lo+hi)/2, hi);
int w = a[hi], k=lo;
for (int i=k; i<hi; i++)
if (a[i]<w) {swap(a,i,k); k++;}
swap(a,k,hi);
return k;}
• Idee: Pivotelement w irgendwo aus der Mitte wählen
•
(eigentlich egal), am rechten Rand (hi) ablegen
Dann 3 Bereiche bilden
 lo..k-1 : Elemente kleiner als w
 k..i-1: Elemente größer gleich w
 i..hi-1: unsortierte Elemente
9.3 Graphalgorithmen
• bereits behandelt: transitive Hüllenbildung
(Warshall-Algorithmus)
• Heute: Tiefensuche, Breitensuche
• Starke Verbandskomponenten
• minimale Spannbäume
aus: „Taschenbuch der Algorithmen“, erscheint demnächst (März 2008)
Hrsg.: Vöcking, Alt, Dietzfelbinger u.a. Springer eXamen.press 400 S.
Tiefensuche
Gegeben ist ein Graph sowie ein Anfangsknoten im Graphen,
gesucht ist irgendein Knoten mit einer bestimmten Eigenschaft.
• Besuche der Reihe nach alle Knoten,
Beispiel:
die über Kanten erreichbar sind
 Also: zunächst den ersten vom
Anfangsknoten erreichbaren Knoten
 Dann: den ersten von diesem
Knoten aus erreichbaren, und so
weiter
 Wenn kein Knoten mehr erreichbar
ist, kehre zum letzten Punkt zurück,
an dem es noch weitere Kanten gab
 Wenn vom Anfangsknoten alle
Kanten abgesucht wurden, ist der
gesamte erreichbare Teil des
Graphen abgesucht
1
2
3
4
8
6
5
7
9
10
11
Algorithmus
B
A
E
„Markieren“ kann zum Beispiel
dadurch geschehen, dass ein
Sternchen oder eine fortlaufende
Nummer dazugeschrieben wird.
rekursiv!
C
D
1 prozedur Tiefensuche (Knoten x)
2 falls Ausgang erreicht dann stopp
3 sonst falls x unmarkiert dann
4
markiere x;
5
für alle Nachfolgerknoten y von x tue Tiefensuche(y)
Ausführung
2.
B
1.
4.
A
E
5.
C
3.
D
• A Anfangsknoten; Kanten zu B und C
• B ist erster Nachfolger von A; Kante zu D
•
D ist Nachfolger von B, Kante zu E
•
E ist Nachfolger von D, Kante zu B
•
B wurde schon markiert, Rückkehr zu E
•
E hat keine weiteren Nachfolger, Rückkehr zu D
•
D hat keine weiteren Nachfolger, Rückkehr zu B
• B hat keine weiteren Nachfolger, Rückkehr zu A
• C ist zweiter Nachfolger von A, Kante zu D
•
D wurde schon markiert, Rückkehr zu C
• C hat keine weiteren Nachfolger, Rückkehr zu A
• A hat keine weiteren Nachfolger, Stopp
Tiefensuch-Baum
2.
1. A
oder
B
1.
4.
A
E
5.
2.
B
C
3.
5.
C
3.
D
D
Drei Arten von Kanten:
• Vorwärtskanten, z.B. von A nach C
• Querkanten, z.B. von C nach D
• Rückwärtskanten, z.B. von E nach B
E
4.
In gerichteten Graphen bilden nur Rückwärtskanten Zyklen!
Zyklensuche
• Rückwärtskanten verweisen auf einen Knoten, der noch
•
„in Bearbeitung“ ist
Erweiterte Markierung
 „noch nicht begonnen“ (oder „unmarkiert“, am Anfang alle Knoten)
 „in Bearbeitung“
 „abgeschlossen“
• Wenn eine Kante zu einem Knoten „in Bearbeitung“ führt,
wurde ein Zyklus gefunden
1 prozedur Zyklensuche (Knoten x)
2 falls Markierung(x) = „in Bearbeitung“ dann Zyklus gefunden
3 sonst falls Markierung(x) = „noch nicht begonnen“ dann
4
Markierung(x) := „in Bearbeitung“;
5
für alle Nachfolgerknoten y von x tue Zyklensuche(y);
6
Markierung(x) := „abgeschlossen“;
Aufrufbeispiel
Zyklensuche (A) // A noch nicht begonnen
A in Bearbeitung
Zyklensuche (B) // B noch nicht begonnen
B in Bearbeitung
Zyklensuche (D) // D noch nicht begonnen
D in Bearbeitung
Zyklensuche (E) // E noch nicht begonnen
E in Bearbeitung
in Bearbeitung
B
abgeschlossen
Zyklensuche (B) // B in Bearbeitung
Zyklus gefunden!
E abgeschlossen
D abgeschlossen
B abgeschlossen
in Bearbeitung
D
abgeschlossen
Zyklensuche (C) // C noch nicht begonnen
C in Bearbeitung
Zyklensuche (D) // D abgeschlossen
C abgeschlossen
abgeschlossen
E
in Bearbeitung
A abgeschlossen
Fertig!
A
abgeschlossen
in Bearbeitung
C
in Bearbeitung
abgeschlossen
Zyklen finden
• Was tun, wenn ein Zyklus entdeckt wird?
• Um alle Knoten auf dem Zyklus auszugeben, muss man sich „merken“,
welche gerade in Bearbeitung sind
 Zusätzliche Datenstruktur zum Speichern der Knoten „in Bearbeitung“ nötig
 Geeignet: Liste bzw. Stapel von Knoten
1 prozedur Zyklenfinden (Knoten x)
2 falls Markierung(x) = „in Bearbeitung“ dann
3
Zyklus gefunden;
alle Knoten auf dem aktuellen Pfad ab x liegen auf dem Zyklus
4 sonst falls Markierung(x) = „noch nicht begonnen“ dann
5
Markierung(x) := „in Bearbeitung“;
6
Verlängere den aktuellen Suchpfad um x;
7
für alle Nachfolgerknoten y von x tue Zyklenfinden(y);
8
Markierung(x) := „abgeschlossen“;
9
Entferne x (das letzte Element) aus dem aktuellen Pfad;
Ausgabe des Ergebnisses
• aktueller Pfad: ABDE
• Kante von E führt zu B

 B, D, E gehören zum Zyklus

• Programmieraufgabe: für ein
gegebene Liste L von Knoten und
eine Knotenbezeichnung k alle
Elemente bis zum ersten
Vorkommnis von k rückwärts
ausgeben.
• Beispiel: Eingabe „ZYKLENSUCH“
und „E“, Ausgabe „HCUSNE“
Starke Zusammenhangskomponenten
ein Beispielgraph:
• Starke Zusammenhangskomponente: Verbundene Zyklen (jeder
Knoten ist von jedem erreichbar)
• Alle Knoten in einer Zusammenhangskomponente sind sozusagen
„äquivalent“
• Quotientengraph: ersetze jede starke Zusammenhangskomponente
durch einen eigenen Knoten ( zyklenfrei!)
• Tarjan‘s Algorithmus zur Berechnung des Quotientengraphen ist eine
kleine Erweiterung von prozedur Zyklenfinden
Tarjan‘s Algorithmus
Vorbereitung
• Falls Zyklus gefunden, wird nichts direkt
•
unternommen
Tiefensuchnummer definiert wie vorher;
Komponentennummer wird die kleinste
Tiefensuchnummer eines Knotens der Komponente
Hauptteil
• rekursiver Aufruf für alle Kinder des Knotens
• Falls ein Kindknoten eine niedrigere
Komponentennummer bekommen hat, wird sie
übernommen
Nacharbeiten
• Beim rekursiven Aufstieg gilt: wenn sich die
•
Komponentennummer nicht geändert hat, hat der
Knoten die kleinste Tiefensuchnummer der
Komponente
Ausgabe der Komponenten: mit zusätzlichem Stapel
minimale Spannbäume
• Quotientengraph: Redundanz beseitigen durch
„Zusammenfassen“ von Knoten
 zyklenfrei, d.h. endliche Halbordnung
 wie bekommt man aus Graph Baumstruktur?
• Spannbäume: Redundanz beseitigen durch
„Löschen“ von Kanten
 gegeben: (ungerichteter, gewichteter) Graph
 gesucht: eine Teilmenge aller Kanten, die einen Baum
bilden (also zyklen- bzw. kreisfrei), so dass alle Knoten
erreichbar sind; (Summe der Kantengewichte minimal)
• Anwendung: z.B. Optimierung von Netzen
„lokaler“ Ansatz: Tiefen- oder Breitensuche
nicht geeignet zum Finden „optimaler“ Bäume
„globale“ Ansätze:
• Kanten wegnehmen, bis der Graph zerfällt
• Kanten hinzufügen, bis Zyklen entstehen
Konstruktion eines Spannbaumes
• gegeben zusammenhängender gerichteter Graph
•
G=(V,E), gesucht Spannbaum G=(V,C) mit
CE, G ist zusammenhängend, G ist Baum
Kreis = Rückwärts- oder Querkante
1 prozedur Spannbaum (Graph G)
2 C:= empty();
3 L := E
4 solange L nicht leer
5
wähle irgendeine Kante e aus L
6
L-= {e};
7
wenn e mit (V,C) keinen Kreis bildet
8
dann C += {e}
9 Rückgabe (V,C)
Algorithmus von Kruskal
• „Führe den folgenden Schritt so oft wie möglich aus: Wähle
unter den noch nicht ausgewählten Kanten von G (dem
Graphen) eine kürzeste Kante, die mit den schon gewählten
Kanten keinen Kreis bildet“
1 prozedur Kruskal (Graph G)
2 Sortiere die Kanten von G aufsteigend nach ihrem Kantengewicht.
3 C := empty();
4 L := E
5 solange L nicht leer
6
sei e Kante in L mit minimalem Kantengewicht
7
L-= {e};
8
wenn e mit (V,C) keinen Kreis bildet
9
dann C += {e}
10 Rückgabe M = (V,C) ist ein minimaler Spannbaum von G
Benötigte Routinen
• Wählen der Kante mit kleinstem Gewicht
 Vorsortieren der Kanten!
• Test auf Kreisfreiheit
 Union-Find-Struktur!
Demos unter
http://www.logn.de/mst/
http://links.math.rpi.edu/applets/appi
ndex/graphtheory.html
Union-Find-Struktur: Repräsentation von
Äquivalenzklassen
• Union: Vereinigt zwei Äquivalenzklassen
• Find: liefert zu beliebigem Element einen eindeutigen
Repräsentanten der Äquivalenzklasse
effiziente Implementierung mit Bäumen möglich!
Union-Find bei Kruskal
• Äquivalenzklassenpartition:
 alle Knoten die von einem gegebenen Knoten erreichbar
sind oder von denen der Knoten erreichbar ist, liegen in
der selben Partition (transitive Hülle der ungerichteten
Kanten-Relation)
 initial: feinste Partitionierung, d.h. jeder Knoten bildet eine
Partition
• Betrachtung der Kante k=(n1,n2):
 Wenn n1 und n2 in der selben Partition liegen (d.h. n1
erreicht n2 oder umgekehrt), entsteht durch k ein Kreis
 Wenn n1und n2 in verschiedenen Partitionen liegen,
müssen diese bei Hinzunahme von k vereinigt werden
Herunterladen