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