Wolfgang Hönig / Andreas Ecke SS 10 Übung Programmierung 2. Übung: Zusatzaufgaben Im folgenden wollen wir die Funktion mergesort in Haskell programmieren. Mergesort basiert genau wie Quicksort auf dem Divide and Conquer-Prinzip und arbeitet folgendermaßen: Zuerst wird die Liste in 2 Teillisten aufgespaltet. diese werden rekursiv sortiert und im Anschluß daran wieder so zusammengefügt, dass die Gesamtliste ebenfalls sortiert ist. Dazu müssen beim Zusammenfügen jeweils immer die Kopfelemente der Teillisten verglichen werden, um festzustellen, welches Element das nächst kleinste ist. Als erstes benötigen wir die Funktion split, die uns eine Liste in 2 ca. gleichgroße Teillisten aufteilt (z.B. split :: [Int] −> ([Int],[Int])). Eine triviale Funktion, die das realisiert, würde so aussehen: split l = let halflen = div (length l) 2 in (take halflen l , drop halflen l ) Dabei gibt take n l die ersten n Elemente der Liste l zurück und drop n l entsprechend alle Elemente aus l außer den ersten n. Diese Funktion ist jedoch sehr ineffektiv, da sie die Liste l drei mal durchlaufen muss: einmal um die Länge der Liste zu ermitteln, einmal um die ersten n Elemente herauszufinden und ein drittes Mal, um die restlichen Elemente zu ermitteln. Die Aufgabe ist es deswegen, eine Funktion zu schreiben, die diese Aufteilung einer Liste in nur einem Durchlauf schafft. Erstelle anschließend aus der Funktion split und der Funktion merge aus Aufgabe 2.b) (Aufgabensammlung 11.21) eine Funktion mergesort, die eine Liste von Zahlen aufsteigend sortiert. Hinweise: 1. Die Listenelemente müssen nicht genau so aufgeteilt werden: Eine Möglichkeit wäre auch, die Elemente immer abwechselnd auf Liste 1 und 2 aufzuteilen. 2. Da man in Haskell nicht 2 Rückgabewerte hat, gibt die Funktion split ein 2er-Tupel aus 2 Listen zurück. Um auf den ersten Wert eines solchen Tupels zuzugreifen, kann die Funktion fst genutzt werden, für den 2ten Wert die Funktion snd. (Die Signatur von fst ist: fst :: (a, b) −> a). Lösungen Die Funktion split teilt hier die Elemente der Eingabeliste abwechselnd auf die beiden Ausgabelisten aus. Damit klar ist, auf welche Liste das nächste Element kommt, wird noch ein Parameter z übergeben, welcher entweder den Wert 0 oder 1 hat: 1 2 3 4 5 6 7 8 9 10 11 12 13 s p l i t : : [ Int ] −> Int −> ( [ Int ] , [ Int ] ) split [ ] z = ( [ ] , [ ] ) split (a : x) z | z == 0 = ( a : ( f s t t ) , snd t ) | otherwise = ( f s t t , a : ( snd t ) ) where t = s p l i t x (1−z ) merge merge merge merge : : [ Int ] −> [ Int ] −> [ Int ] [] r = r l [] = l (a : x) (b : y) | a < b = a : ( merge x ( b : y ) ) | otherwise = b : ( merge ( a : x ) y ) Die Funktion mergesort ist nun relativ einfach. Sie splittet die Liste mit der Funktion split in 2 Teile, sortiert diese rekursiv und fügt die beiden Listen dann mit der Funkti- on merge wieder zusammen. Der Rekursionsabbruch muss in diesem Fall schon bei der einelementigen Liste erfolgen, um eine Endlosschleife zu verhindern. 1 2 3 4 5 mergesort mergesort mergesort mergesort : : [ Int ] −> [ Int ] [] = [] [a] = [a] l = merge ( m e r g e s o r t ( f s t t ) ) ( m e r g e s o r t ( snd t ) ) where t = s p l i t l 0