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: ABDE
• 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
CE, 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