Musterlösung

Werbung
Algorithmen und Programmieren 1
Funktionale Programmierung
- Musterlösung zu Übung 8 -
Dozent: Prof. Dr. G. Rote
Tutoren: J. Fleischer, T. Haimberger,
N. Lehmann, C. Pockrandt, A. Steen
03.01.2012
Ziele dieser Übung
Mit Ein- und Ausgabe in Haskell sicher umgehen und das Konzept der primitiv-rekursiven
Funktionen verstehen, sowie das Arbeiten mit Suchbäumen sicher beherrschen.
In dieser Übung sollte man folgende Konzepte geübt und verstanden haben:
ˆ
Ein- und Ausgabe
ˆ
Bisektionsalgorithmus
ˆ
Fibonacci-Folge
ˆ
Arbeiten mit Suchbäumen
ˆ
Primitive Rekursion
Aufgabe 48: Ein- und Ausgabe
Schreiben Sie ein Programm für folgende Aufgabe: Der Benutzer wird nach dem Namen eine
Eingabedatei und einer Ausgabedatei gefragt; dann wird die Eingabedatei auf die Ausgabedatei kopiert, wobei Folgen von Leerzeichen am Zeilenende weggelassen werden.
Lösung zu Aufgabe 48
copy :: IO ()
copy = do putStr "Eingabedatei? "
inFile <- getLine
putStr "Ausgabedatei? "
outFile <- getLine
input <- readFile inFile
writeFile outFile (format input)
-- Entfernt Leerzeichen am Zeilenende.
format :: String -> String
format = unlines . (map prune) . lines
where prune [] = []
prune xs | last xs /= ' ' = xs
| otherwise = prune (init xs)
1
Aufgabe 49: Bäume dekodieren
Schreiben Sie ein Programm für die Umkehrfunktion zu Aufgabe 41.
Lösung zu Aufgabe 49
data Show a => Baum a = Blatt a | Knoten' a (Baum a) (Baum a)
deriving Show
-- Teile die Zeichenkette so auf, dass im linken Teil eine komplette Zahl steht.
-- Bsp: "-15D3UD27U" -> value = "-15" und rest = "D3UD27U"
-- Ist die zweite Zeichenkette leer, so handelt es sich um ein Blatt;
-- sonst, teile die zweite Zeichenkette wieder auf, so dass der erste Teil
-- dem linken Teilbaum und der zweite Teil dem rechten Teilbaum entspricht.
-- Bsp: "D3UD27U" -> left = "D3U" und right = "D27U"
-- Entferne das erste D und das letzte U von left und right, so dass wieder
-- gültige Kodes entstehen.
-- Dekodiere rekursiv den linken und den rechten Teilbaum.
decode :: String -> Baum Int
decode xs
| rest == [] = Blatt (read value)
| otherwise = Knoten' (read value) lTree rTree
where (value,rest) = span (`notElem` ['U','D']) xs
lTree
= decode ((init.tail) left)
rTree
= decode ((init.tail) right)
(left,right) = splitCode [] rest 0
-- Teilt die Zeichenkette so auf, dass der erste Teil dem linken Teilbaum
-- und der zweite Teil dem rechten Teilbaum entspricht. Dabei wird ein
-- Zähler benutzt, der anfangs 0 ist. Der Zähler wird um 1 erhöht, wenn
-- wir eine Ebene tiefer gehen, und um 1 verringert, wenn wir eine Ebene
-- höher gehen. Wir sind fertig, wenn der Zähler wieder 0 erreicht.
splitCode :: String -> String -> Int -> (String,String)
splitCode (l:ls) rs 0 = (l:ls,rs)
splitCode ls (r:rs) n
| r == 'D' = splitCode (ls ++ [r]) rs (n+1)
| r == 'U' = splitCode (ls ++ [r]) rs (n-1)
| otherwise = splitCode (ls ++ [r]) rs n
encode :: Baum Int -> String
encode (Blatt x)
= show x
encode (Knoten' x l r) = show x ++ "D" ++ encode l ++ "UD" ++ encode r ++ "U"
bspCode :: String
bspCode = "27D5D-2UD7UUD44D8UD5D7UD9UUU"
bspBaum :: Baum Int
bspBaum = Knoten' 27
(Knoten' 5 (Blatt (-2)) (Blatt 7))
(Knoten' 44 (Blatt 8) (Knoten' 5 (Blatt 7) (Blatt 9)))
2
Aufgabe 50: Intervallhalbierung, binäre Suche
nullStelle:: (Floating a, Ord a) => (a -> a) -> a -> a
-> a -> a zum Finden einer Näherungslösung x der Gleichung f (x) = 0 für eine stetige reelle
Funktion f : [a, b] → R, die auf einem Intervall [a, b] deniert ist. Die Funktion nullStelle
f a b epsilon setzt voraus, dass f (a) und f (b) verschiedenes Vorzeichen haben. Wegen der
Stetigkeit von f folgt daraus, dass f im Intervall [a, b] mindestens eine Nullstelle hat. Das
Verfahren der Intervallhalbierung bestimmt zunächst das Vorzeichen von f ((a + b)/2) und
Schreiben Sie ein Programm
grenzt dadurch das Intervall auf die linke oder die rechte Hälfte ein. Dieser Vorgang wird
iteriert, bis die Länge des Lösungsintervalls unter
ε
reduziert ist.
Finden Sie mit Ihrem Programm die Lösung von
xx = 2,
und von
ex = sin x, x > −4,
exp x
x
jeweils auf 6 Nachkommastellen genau. Die Exponentialfunktion e wird in Haskell als
geschrieben.
Lösung zu Aufgabe 50
-- Voraussetzungen: a < b, signum (f a) /= signum (f b), epsilon > 0
-- (In der dritten Zeile wurde die signum-Funktion benutzt, weil wir
-- nach der gegebenen Signatur nicht voraussetzen können, dass der
-- Typ t zur Typklasse Ord gehört.)
nullStelle :: Floating t => (t -> t) -> t -> t -> t -> t
nullStelle f a b epsilon
| signum (b - a - epsilon) == -1 = c -- falls b - a < epsilon
| f a == 0
= a
| f b == 0
= b
| signum (f c) == signum (f b)
= nullStelle f a c epsilon
| otherwise
= nullStelle f c b epsilon
where c = (a+b)/2
-f1
f1
n1
--f2
f2
n2
Intervall: [1,2]
:: (Floating a) => a -> a
x = x**x - 2
= nullStelle f1 1 2 1e-7 -- Lösung = 1.559610
----------------------------Intervall: [-4,0]
:: (Floating a) => a -> a
x = exp x - sin x
= nullStelle f2 (-4) 0 1e-7 -- Lösung = -3.183063
Aufgabe 51: Die Fibonacci-Folge
Man kann die Fibonacci-Folge auch bei
fibo::
fibo 0
fibo 1
fibo n
F0 = 0
beginnen lassen:
Integer -> Integer
= 0
= 1
| n>1 = fibo (n-1) + fibo (n-2)
Wie oft wird beim Aufruf von
fibo n
die letzte Zeile der Denition ausgeführt? Wie oft
wird die erste Zeile ausgeführt? Wie oft die zweite Zeile? Beweisen Sie Ihre Aussagen durch
Induktion nach
n.
3
Lösung zu Aufgabe 51
n
0
1
2
3
4
5
6
7
8
9
10
11
12
# 1. Def. Zeile
1
0
1
1
2
3
5
8
13
21
34
55
89
# 2. Def. Zeile
0
1
1
2
3
5
8
13
21
34
55
89
144
# 3. Def. Zeile
0
0
1
2
4
7
12
20
33
54
88
143
232
T1 (0) = 1,
T1 (1) = 0,
∀n ≥ 2 : T1 (n) = T1 (n − 1) + T1 (n − 2)
T2 (0) = 0,
T2 (1) = 1,
∀n ≥ 2 : T2 (n) = T2 (n − 1) + T2 (n − 2)
T3 (0) = 0,
T3 (1) = 0,
∀n ≥ 2 : T3 (n) = T3 (n − 1) + T3 (n − 2) + 1
Behauptung
T1 (0) = 1 und ∀n ≥ 1 : T1 (n) = Fn−1
II. T2 (n) = Fn
III. T3 (n) = Fn+1 − 1
I.
Es folgt ein Beweis mit vollständiger Induktion über
n.
Induktionsbasis
T1 (0) = 1, T1 (1) = 0 = F0 = F1−1 und T1 (2) = 1 = F1 = F2−1
T2 (0) = 0 = F0 und T2 (1) = 1 = F1
III. T3 (0) = 0 = 1 − 1 = F1 − 1 = F0+1 − 1 und T3 (1) = 0 = 1 − 1 = F2 − 1 = F1+1 − 1
I.
II.
Induktionsannahme
Für ein beliebiges, aber festes n ∈ N gilt:
T1 (n) = Fn−1 und T1 (n − 1) = F(n−1)−1 = Fn−2
II. T2 (n) = Fn und T2 (n − 1) = Fn−1
III. T3 (n) = Fn+1 − 1 und T3 (n − 1) = Fn − 1 = F(n−1)+1 − 1
I.
Induktionsbehauptung
Dann gilt auch:
T1 (n + 1) = Fn = F(n+1)−1
II. T2 (n + 1) = Fn+1
III. T3 (n + 1) = Fn+2 − 1 = F(n+1)+1 − 1
I.
Induktionsschritt n −→ n + 1
I.
IA
T1 (n + 1) = T1 (n) + T1 (n − 1) = Fn−1 + Fn−2 = Fn = F(n+1)−1
II.
IA
T2 (n + 1) = T2 (n) + T2 (n − 1) = Fn + Fn−1 = Fn+1
III. T3 (n + 1) = T3 (n) + T3 (n − 1) + 1
IA
= Fn+1 − 1 + Fn − 1 + 1
= Fn+1 + Fn − 1 = Fn+2 − 1 = F(n+1)+1 − 1
Es wurde mit vollständiger Induktion gezeigt, dass die Behauptung gilt.
4
Aufgabe 52: Suchen und Einfügen in einem Suchbaum
data Ord a => Suchbaum a b = Leer | Knoten a b (Suchbaum a b) (Suchbaum a b)
1. Schreiben Sie eine Funktion
finde:: Ord a => a -> Suchbaum a b -> Maybe b
zum
Suchen eines Elements in einem Suchbaum (Vorlesung vom 28. 11.)
einf:: Ord a => a -> b -> Suchbaum a b -> Suchbaum
a b zum Einfügen eines Elements in einen Suchbaum. Das Ergebnis von einf s t b ist
ein neuer Suchbaum, in dem ein zusätzliches Element mit Schlüssel s vom Typ a und
Wert t vom Typ b enthalten ist. Sie dürfen annehmen, dass der Schlüssel s vorher nicht
2. Programmieren Sie eine Funktion
im Baum vorkommt.
Lösung zu Aufgabe 52
data (Ord a) => Suchbaum a b = Leer | Knoten a b (Suchbaum a b) (Suchbaum a b)
-- a ist der Typ der Schlüssel
-- b ist der Typ der Nutzdaten
-- Achtung: Die Signaturen aus der Aufgabenstellung wurden geändert,
-- damit wir die Schlüssel vergleichen können.
finde :: (Ord a) => a -> Suchbaum a b -> Maybe b
finde _ Leer = Nothing
finde x (Knoten a b links rechts)
| x == a = Just b
| x < a = finde x links
-- links weitersuchen
| x > a = finde x rechts
-- rechts weitersuchen
einf :: (Ord a) => a -> b -> Suchbaum a b -> Suchbaum a b
einf s t Leer = Knoten s t Leer Leer
einf s t (Knoten a b links rechts)
| s < a = Knoten a b (einf s t links) rechts
-- links einfügen
| s > a = Knoten a b links (einf s t rechts)
-- rechts einfügen
bspSuchbaum = Knoten 10 "xy" (Knoten 4 "ab" Leer Leer) Leer
Aufgabe 53: Bedingte Ausdrücke in primitiv-rekursiven Funktionen
f, g, h : Nk → N drei k -stellige primitiv-rekursive
folgende Funktion a eine primitiv-rekursive Funktion:
Beweisen Sie: Wenn
ist auch die
Funktionen sind, dann
a(x1 , . . . , xk ) := if f (x1 , . . . , xk ) = 0 then g(x1 , . . . , xk ) else h(x1 , . . . , xk )
Beweisen Sie dieselbe Schlussfolgerung auch für Bedingungen der Form
f1 (x1 , . . . , xk ) < f2 (x1 , . . . , xk ), f1 (x1 , . . . , xk ) ≤ f2 (x1 , . . . , xk ) und
f1 (x1 , . . . , xk ) = f2 (x1 , . . . , xk ), falls f1 und f2 primitiv-rekursiv sind.
5
Lösung zu Aufgabe 53
Sei
ifelse(b, x, y) := if b = 0 then x else y .
Dann ist
ifelse
nach dem Rekursionssche-
ma eine primitiv-rekursive Funktion:
ifelse(0, x, y) = P12 (x, y) = x
(siehe Aufgabe 55)
ifelse(n, x, y) = P44 (ifelse(n − 1), n, x, y) = y ,
Nach der Einsetzungsregel ist auch
a
für
n>0
primitiv-rekursiv:
a(x1 , ..., xk ) = ifelse(f (x1 , ..., xk ), g(x1 , ..., xk ), h(x1 , ..., xk ))
minus und plus primitiv-rekursive Funktionen sind. (Zur
minus(a, b) = 0; sonst ist minus(a, b) = a − b > 0.) Nach der
In der Vorlesung wurde gezeigt, dass
Erinnerung: Ist
a ≤ b,
so ist
Einsetzungsregel sind also die folgenden Funktionen primitiv-rekursiv:
f< (x1 , ..., xk ) := minus(nachf(f1 (x1 , ..., xk )), f2 (x1 , ..., xk ))
f≤ (x1 , ..., xk ) := minus(f1 (x1 , ..., xk ), f2 (x1 , ..., xk ))
f≥ (x1 , ..., xk ) := minus(f2 (x1 , ..., xk ), f1 (x1 , ..., xk ))
f= (x1 , ..., xk ) := plus(f≤ (x1 , ..., xk ), f≥ (x1 , ..., xk ))
Die obigen Funktionen liefern genau dann 0, wenn die jeweilige Bedingung erfüllt ist. Es gilt
also z.B.
f≤ (x1 , ..., xk ) = 0 ⇐⇒ f1 (x1 , ..., xk ) ≤ f2 (x1 , ..., xk ).
Wir können nun wieder durch
Einsetzen die gewünschten Funktionen denieren:
a< (x1 , ..., xk ) := ifelse(f< (x1 , ..., xk ), g(x1 , ..., xk ), h(x1 , ..., xk ))
a≤ (x1 , ..., xk ) := ifelse(f≤ (x1 , ..., xk ), g(x1 , ..., xk ), h(x1 , ..., xk ))
a= (x1 , ..., xk ) := ifelse(f= (x1 , ..., xk ), g(x1 , ..., xk ), h(x1 , ..., xk ))
Aufgabe 54: Gröÿte enthaltene Zweierpotenz
Zeigen Sie, dass die Funktion
p2 : N → N
p2 (n) := max{ i ≥ 0 | 2i
mit
ist ein Teiler von
n}
für
n > 0, p2 (0) := 0
primitiv-rekursiv ist. Tipp: Konstruieren Sie zuerst eine Hilfsfunktion
h(m, n) := max{ i ≥ 0 | 2i ≤ m + 1
und
2i
ist ein Teiler von
n}
Sie dürfen unter anderem voraussetzen, dass die modulo-Funktion und die Potenzfunktion
primitiv-rekursiv ist.
Lösung zu Aufgabe 54
Wir setzen voraus, dass die modulo-Funktion
modulo und die Potenzfunktion power primitivg primitiv-rekursiv (nach Einsetzungsregel).
rekursiv sind. Dann ist auch die folgende Funktion
g(x, y, z) : = if modulo(z, 2y+1 ) = 0 then y + 1 else x
= ifelse(modulo(z, power(2, nachf(y))), nachf(y), x)
Wir denieren eine Hilfsfunktion
h0
fast genau so wie in der Aufgabenstellung vorgeschlagen:
h0 (m, n) := max{ i ≥ 0 | i ≤ m
6
und
2i
ist ein Teiler von
n}
Die Funktion
h0
ist nach dem Rekursionsschema primitiv-rekursiv.
h0 (0, n) = 0
h0 (m, n) = g(h0 (m − 1, n), m − 1, n)
= if modulo(n, 2m ) = 0 then m else h0 (m − 1, n),
Nach der Einsetzungsregel ergibt sich, dass
p2
für
m>0
primitiv-rekursiv ist:
p2 (n) = h0 (n, n)
Für
n>0
ergibt sich nämlich
h0 (n, n) = max{ i ≥ 0 | i ≤ n
= max{ i ≥ 0 | 2i
weil
2i > i
ist, und für
n=0
ist
und
2i
ist ein Teiler von
ist ein Teiler von
n}
n },
p2 (0) = h0 (0, 0) = 0.
Aufgabe 55: Einsetzung
Klassischerweise wird die Einsetzungsregel bei der Denition der primitiv-rekursiven Funktionen strenger formuliert:
Wenn eine
k -stellige
primitiv-rekursive Funktionen
die
n-stellige
f : Nk → N und k n-stellige
g1 , . . . , gk : Nk → N gegeben sind, dann ist auch
primitiv-rekursive Funktion
Funktion
h(x1 , . . . , xn ) := f g1 (x1 , . . . , xn ), g2 (x1 , . . . , xn ), . . . gk (x1 , . . . , xn )
primitiv-rekursiv.
Zusätzlich wird dabei deniert, dass alle Projektionsfunktionen
für
1≤i≤k
Pik (x1 , . . . , xk ) := xi
primitiv-rekursiv sind.
Zeigen Sie mit dieser Einsetzungsregel und mit Hilfe geeigneter Projektionsfunktionen, dass
die Funktionen
p(x, y, z) := h(x, h(y, z))
q(x, y, z) := g(h(x, y), y, g(z, z, y))
primitiv-rekursiv sind, falls die Funktionen
g und h als primitiv-rekursiv vorausgesetzt werden.
Lösung zu Aufgabe 55
Wir denieren zuerst drei primitiv-rekursive Hilfsfunktionen:
h1 (x, y, z) := h(P23 (x, y, z), P33 (x, y, z)) = h(x, z)
h2 (x, y, z) := h(P13 (x, y, z), P23 (x, y, z)) = h(x, y)
g1 (x, y, z) := g(P33 (x, y, z), P33 (x, y, z), P23 (x, y, z)) = g(z, z, y)
Dann setzen wir die Hilfsfunktionen und geeignete Projektionsfunktionen ein:
p(x, y, z) = h(P13 (x, y, z), h1 (x, y, z))
q(x, y, z) = g(h2 (x, y, z), P23 (x, y, z), g1 (x, y, z))
7
Aufgabe 56: Suchbäume
Schreiben Sie eine Funktion, die überprüft, ob ein Suchbaum richtig sortiert ist.
(Das heiÿt, ob die Suchbaumeigenschaft erfüllt ist: Für jeden Knoten mit Schlüssel
die Knoten im linken Teilbaum kleinere Schlüssel als
s
s
müssen
haben und die Knoten im rechten
Teilbaum gröÿere Schlüssel.)
Lösung zu Aufgabe 56
-- Alle Knoten im linken Teilbaum sollen kleinere Schlüssel als s
-- haben und alle Knoten im rechten Teilbaum gröÿere Schlüssel.
-- Um diese Eigenschaft zu überprüfen, können wir einfach den
-- rechtesten Wert des linken Teilbaums und den linkesten Wert des
-- rechten Teilbaums mit s vergleichen, solange wir auch noch überprüfen,
-- ob die Teilbaume selbst richtig sortiert sind.
check :: (Ord a) => Suchbaum a b -> Bool
check Leer = True
check (Knoten s _ links rechts) =
checkMax links && checkMin rechts && check links && check rechts
where
-- Ist der rechteste Wert im linken Teilbaum kleiner als a?
checkMax Leer = True
checkMax (Knoten x _ _ Leer) = x <= s
checkMax (Knoten _ _ _ rechts) = checkMax rechts
-- Ist der linkeste Wert im rechten Teilbaum gröÿer als a?
checkMin Leer = True
checkMin (Knoten x _ Leer _) = x >= s
checkMin (Knoten _ _ links _) = checkMin links
bspRichtig = Knoten 10 "xy"
(Knoten 4 "ab" Leer (Knoten 6 "richtig" Leer Leer))
Leer
bspFalsch = Knoten 10 "xy"
(Knoten 4 "ab" Leer (Knoten 12 "fehler" Leer Leer))
Leer
Aufgabe 57: Verschmelzen
Scheiben Sie eine Funktion
verschmelze:: (Ord a) => [a] -> [a] -> [a],
die zwei auf-
steigend sortierte Listen zu einer sortierten Gesamtliste vereinigt. Zum Beispiel:
verschmelze [1,3,4,7] [2,3,7,8,10] = [1,2,3,3,4,7,7,8,10]
Lösung zu Aufgabe 57
verschmelze
verschmelze
verschmelze
verschmelze
:: (Ord a) => [a] -> [a] -> [a]
[] ys = ys
xs [] = xs
(x:xs) (y:ys)
-- "Reiÿverschluss"
| x <= y
= x : verschmelze xs (y:ys)
| otherwise = y : verschmelze (x:xs) ys
8
Herunterladen