Problemlösungsmuster "Teile und herrsche" am Beispiel "Sortieren" Annahme bei allen folgenden Verfahren: die zu sortierende Menge hat genügend Platz im Hauptspeicher. Zum Vergleich: Einfachster Algorithmus ohne Anwendung von Teile und herrsche: Vergleiche je zwei Elemente: { für i=a.length-1 …1, für j=0..i-1 wenn a[j] < a[j+1] tausche a[j] und a[j+1]} Animationen findet man hier: http://www.tcs.ifi.lmu.de/~gruberh/lehre/sorting/sort.html hs / fub - alp2-12 76 15 3 10 22 7 1 5 3 15 10 22 7 1 5 3 10 15 22 7 1 5 3 10 15 7 22 1 5 3 10 15 7 1 22 5 3 10 15 7 1 5 22 3 10 7 15 1 5 22 3 10 7 1 15 5 22 3 10 7 1 5 15 22 3 7 10 1 5 15 22 3 7 1 10 5 15 22 3 7 1 5 10 15 22 3 1 7 5 10 15 22 3 1 5 7 10 15 22 1 3 5 7 10 15 22 Bubble Sort: INV: a[i]..a[n-1] sorted public void bubbleSort (double [ ] v){ // exchange the values in the vector v // so they appear in ascending order int n = v.length; // find the largest remaining value // and place into v[i] for (int i = n - 1; i > 0; i--) { // move large values to the top for (int j = 0; j < i; j++) { // if out of order if (v[j] > v[j+1]) { swapDouble(j,j+1,v) }} } } "Blasen" steigen auf, "schwere" Elemente sinken hs / fub - alp2-12 77 Teile und Herrsche und Sortieralgorithmen verbinden zerlegen und lösen > mergeSort [] = [] > mergeSort [x] = [x] > mergeSort xs = merge (mergeSort left) (mergeSort right) > where left = take n xs > right = drop n xs > n = (length xs) `div` 2 > merge xs [] = xs > merge [] ys = ys > merge (x:xrest) (y:yrest) > | x <= y = x:(merge xrest (y:yrest)) > | y < x = y:(merge (x:xrest) yrest) hs / fub - alp2-12 78 Beispiel Mischsortieren (Merge sort) 15 3 10 22 7 1 5 715 15 3 10 22 10 22 15 3 15 10 3 22 71 7 10 22 3 15 3 10 15 22 5 1 Teile bis Problem trivial Füge Lösungen zusammen 1 7 1 5 7 1 3 5 7 10 15 22 hs / fub - alp2-12 79 Mischsortieren //pre: double [] a, N=a.length > 0, ∀i, 0≤i ≤N-1: a[i] definiert double [] mergeSort(double[] a) { if (a.length == 1) { return a; } else { double[] firstHalf = mergeSort (firstHalf(a)); assert(for(int i=0; i<firstHalf.length-1;i++) firstHalf[i] <= firstHalf[i+1];) double[] secHalf = mergeSort (secHalf(a)); assert .. merge (firstHalf, secHalf); } } Nachteil der funktionalen Lösung: viel Kopieroperationen hs / fub - alp2-12 80 Variante mit einem temporären Hilfsspeicher (*) public void mergeSort( double[] a ) { double[] tmpArray = new double[ a.length ]; mergeSort( a, tmpArray, 0, a.length - 1 ); } private void mergeSort( double[ ] a, double[ ] tmpArray, int left, int right ){ // Sort a in range a[left]..a[right] if( left < right ){ int center = ( left + right ) / 2; mergeSort( a, tmpArray, left, center ); mergeSort( a, tmpArray, center + 1, right ); merge( a, tmpArray, left, center + 1, right ); } } (*) Ohne Hilfsspeicher schwieriger – Mischen in situ! hs / fub - alp2-12 81 Hauptarbeit: Mischen private void merge( double[ ] a, double[ ] tmpArray, int leftPos, int rightPos, int rightEnd ){ int leftEnd = rightPos - 1; int tmpPos = leftPos; int numElements = rightEnd - leftPos + 1; // Main loop while( leftPos <= leftEnd && rightPos <= rightEnd ) if( a[leftPos]<= a[ rightPos ] ) tmpArray[ tmpPos++ ] = a[ leftPos++ ]; else tmpArray[ tmpPos++ ] = a[ rightPos++ ]; // Copy rest of first half while( leftPos <= leftEnd ) tmpArray[ tmpPos++ ] = a[ leftPos++ ]; while( rightPos <= rightEnd ) // Copy rest of right half tmpArray[ tmpPos++ ] = a[ rightPos++ ]; // Copy tmpArray back for( int i = 0; i < numElements; i++, rightEnd-- ) a[ rightEnd ] = tmpArray[ rightEnd ]; } hs / fub - alp2-12 82 Teile und herrsche: Quicksort > qSort :: Ord a => [a] -> [a] > qSort [] = [] > qSort (x:xs) = qSort cmp [y | y <-xs, y <= x] > ++ [x] ++ qSort cmp [y | y <-xs, y > x ] Quicksort (typ[ ] a) 1. Pivotelement x wählen 2. Quicksort ( a'[ ]) mit a'[ ] enthält alle y <=x, y aus a[ ] 3. Quicksort (a''[ ]) mit a''[ ] enthält alle y >x 4. a[]sorted = a'''[] mit a'''[i]=a'[i] für i=0…a'.length-1 a'''[k] = x mit k= a'.length a'''[j] = a''[a'.length+1-j], j=a'.length+1.. a'length+a''length Beachte elegante funktionale Formulierung ! hs / fub - alp2-12 83 15 3 10 22 7 1 5 3 10 7 1 5 1 3 15 22 5,7,10 5 7,10 10 7 Beachte Wirkung des Pivotelements ⇒ hs / fub - alp2-12 84 15 3 10 22 7 1 5 315 1 3 15 10 22 7 5 15 10 22 Optimales Pivotelement x: Teilung in gleich große Hälften | {y: y<=x}| = |{y: y>x}| +/- 1 Überlegung zur Laufzeit: auf jeder Ebene müssen n-1 Elemente mit dem jeweiligen Pivotelement verglichen werden. Es gibt log2n Ebenen. O(n*log n) ist eine gute Vermutung für die Laufzeit im besten Fall, beweisen! Und im schlechtesten Fall? hs / fub - alp2-12 85 Idee der Aufteilung des Feldes 15 3 10 22 7 1 5 1 3 10 22 7 15 5 1 3 5 22 7 15 10 Rekursiv fortsetzen hs / fub - alp2-12 86 Quicksort rekursiv void qsort(double[] a) { qsortIdx( a,0, a.length-1); } private void qsortIdx(double[] a,int low,int high){ if(low < high) { int pivotIdx = pivotIdx(a,low,high); qsortIdx(a, low, pivotIdx-1); qsortIdx(a,pivotIdx+1,high); } } hs / fub - alp2-12 87 Quicksort: einfache Aufteilung private int pivotIdx(double[] a, int low, int high){ int l=low,r=high; double pivot = a[high]; while (l <r) { while (l <r && a[l] < pivot) {l++;} // linken Zeiger nach rechts while (l <r && a[r] >= pivot) {r--;} // rechten Zeiger nach links if (l < r) swapDouble(l,r,a); } swapDouble (l, high, a); //pivot -> correct pos return l; } Abhängig von der Wahl des Pivotelements (!) hs / fub - alp2-12 88 hs / fub - alp2-12 89 Versuch und Irrtum ( ~ Backtracking) 7 69 4 96 hs / fub - alp2-12 90 7 9 6 6 4 4 6 9 9 9 6 4 hs / fub - alp2-12 91 Problemlösungsmodell "Backtracking", eine Lösung. boolean solve (Configuration a){ if(legal(a)) { nextStep(a); if (finished(a)) return true; else for (all continuations b) if solve(b) return true; else backtrack(b); backtrack(a);} return(false); } hs / fub - alp2-12 92 Modell: Tiefensuche 1 2 8 3 7 4 5 6 9 10 12 11 13 Jedem inneren Knoten entspricht ein teilweise gelöstes Problem, jedem Blattknoten eine Lösung oder eine Sackgasse. Tiefensuche: löse nacheinander genau die Teilprobleme (1,2,3,4) die zu einem Blatt führen. Prüfe Lösung. Backtrack! Iteriere über Alternativen.Alternative hs / fub - alp2-12 93 n-Damen Problem (n=8) hs / fub - alp2-12 94 Modellierung des n-Damen-Problems boolean[][] board = new boolean[N+1][N+1];//use 1..8 legal() = !threat(row,column) boolean threat(int r, int c){ return vertHorizThreat(r,c) || DiagThread(r,c); } finished(column) = return column==N //ModelL: Alle Damen in unterschiedlichen Zeilen // und Spalten. Fülle in jedem Schritt eine // Spalte 1,2,.. ,N nextstep()= putQueen(row,column); hs / fub - alp2-12 95 boolean solve (int row, int column){ //1<=row,column<=N if (threat(row,column)){return false;} putQueen(row,column); if (finished(column)) return true; else{ for (int r=1; r<=N; r++){ if (solve(r,column+1)) return true; else {backtrack(row, column+1);} } backtrack(row, column); } return false; } hs / fub - alp2-12 96 Suche in einem Labyrinth 1 Modell: ungerichteter Graph 0 Verzweigungspunkte des Labyrinths: Knoten des Graphen, Verbindungen: Kanten 4 2 3 5 4 1 0 2 3 5 hs / fub - alp2-12 97 Repräsentation von Graphen durch Adjazenzliste (*) boolean[][] path = new boolean[knotenzahl][knotenzahl]; mit path[i][j] = true 0 0 1 2 3 4 5 1 true true true true ⇔ Kante zwischen i und j, i ≠ j 2 true true 3 5 true true true true 4 true true true true (*) nur eine Möglichkeit hs / fub - alp2-12 98 Erster Lösungsversuch: Suche Ausweg (Knoten) { Gehe Gang bis zum nächsten Knoten k; Wenn k=Ausgang: fertig sonst für alle abzweigenden Gänge suche Ausweg(k); gehe Gang zurück; Problem: Kreise! z.B. 0 → 1 → 2 →0 → 1 → … Vorsicht: Allgemeinbildung Faden der Ariadne: Markiere Knoten, die du schon besucht hast. hs / fub - alp2-12 99 Suche im Labyrinth boolean exit[]; boolean mark[] // anfangs false boolean findPath (int node){ if (marked[node]) return false; //legal? marked[node]=true; //next step //finished? if (exit[node]) return true; for (int i= 0; i< graph.length; i++){ if (!graph[node][i]) continue; //all continuat. if (findPath(i)){ path[next] = i; // path construction next++; Keine besonders effiziente return true; Lösung. } } // no unmarked successor, no solution. return false; } hs / fub - alp2-12 100 Backtracking wichtiges Lösungsmuster Verwandt mit "Constraint Satisfaction" Problemen: Problemlösung unter Nebenbedingungen. n-Damen-Problem aber nicht Labyrinth! Anwendungsbeispiele für Backtracking: • Syntaxanalyse • Kombinatorische Probleme • Symbolisches Problemlösen in der KI (Planungsprozesse, "Suche im Zustandsraum" u.v.m. oft in Kombination mit Heuristiken) hs / fub - alp2-12 101 This is the end Viel Erfolg für die Klausur! hs / fub - alp2-12 102