Fachrichtung 6.2 — Informatik Universität des Saarlandes Tutorenteam der Vorlesung Programmierung 1 Programmierung 1 (Wintersemester 2015/16) Lösungsblatt: Aufgaben für die Übungsgruppen: 7 (Kapitel 7) Hinweis: Dieses Übungsblatt enthält von den Tutoren für die Übungsgruppe erstellte Aufgaben. Die Aufgaben und die damit abgedeckten Themenbereiche sind für die Klausur weder relevant noch irrelevant. 1 Baumgrundschule Aufgabe 7.1 (Linearisierungen schreiben) Geben Sie die Prä- und Postlinearisierung des folgenden Baumes an: Lösung 7.1: Prälinearisierung: [3, 1, 1, 1, 0, 5, 0, 0, 2, 0, 0, 0, 2, 0, 1, 2, 0, 1, 0, 2, 0, 0] Postlinearisierung: [0, 1, 1, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, 2, 1, 2, 5, 0, 0, 2, 3] Aufgabe 7.2 (Linearisierungen verstehen) Betrachten Sie diese Prälinearisierung: [4, 1, 2, 0, 1, 0, 2, 1, 3, 1, 0, 1, 0, 0, 0, 2, 0, 0, 1, 3, 1, 1, 0, 0, 0] Versuchen Sie, die folgenden Aufgaben zu lösen, ohne den Baum zu zeichnen. Wie viele Knoten hat der Baum? Wie viele davon sind innere Knoten, wie viele sind Blätter? Wie ist sein Grad? Können Sie auch die Tiefe des Baum an seiner Prälinearisierung erkennen? Lösung 7.2: Der Baum hat die Größe 25. Er hat 14 innere Knoten und 11 Blätter. Sein Grad ist 4. Die Tiefe ist 5. Hier ist eine grafische Darstellung des Baumes: 1 Aufgabe 7.3 Schreiben Sie eine Prozedur unterbaum: tree → tree → bool, die zu einem Baum t und einem Baum t0 testet, ob t0 ein Unterbaum von t ist. Lösung 7.3: fun unterbaum ( T ts ) t ’ = List . exists ( fn x ⇒ t ’ = x ) ts 2 Baumgymnasium Aufgabe 7.4 (Elementare Baumprozeduren) Betrachten Sie die folgenden Prozeduren. Erstellen Sie jeweils ein Ausführungsprotokoll zu dem passenden unten angegebenen Baum. Machen Sie sich daran klar, wie die Prozeduren funktionieren. (a) fun size ( T []) = 1 | size ( T ts ) = foldl op+ 1 ( map size ts ) (b) fun depth ( T ts ) = 1 + foldl Int . max ∼1 ( map depth ts ) (c) fun breadth ( T []) = 1 | breadth ( T ts ) = foldl op+ 0 ( map breadth ts ) (d) Überlegen Sie sich eine allgemeine Vorgehensweise, wie man an Baumprozeduren mittels foldl bzw. foldr und map herangehen kann. (a) (b) (c) Lösung 7.4: (a) size ( T [ T [] , T [ T [] , T []]]) = foldl op+ 1 ( map size [ T [] , T [ T [] , T []]]) = foldl op+ 1 [ size ( T []) , size ( T [ T [] , T []])] = foldl op+ 1 [1 , foldl op+ 1 ( map size [ T [] , T []])] = foldl op+ 1 [1 , foldl op+ 1 [ size ( T []) , size ( T [])]] = foldl op+ 1 [1 , foldl op+ 1 [1 ,1]] = foldl op+ 1 [1 , 3] = 5 (b) depth ( T [ T [ T []] , T []]) = 1 + foldl Int . max ∼1 ( map depth [ T [ T []] , T []]) = 1 + foldl Int . max ∼1 [ depth ( T [ T []]) , depth ( T [])] = 1 + foldl Int . max ∼1 [1 + foldl Int . max ∼1 ( map depth [ T []]) , 2 1 + foldl Int . max ( map depth [])] = 1 + fold Int . max ∼1 [1 + foldl Int . max ∼1 [ depth ( T [])] , 1 + foldl Int . max ∼1 []] = 1 + foldl Int . max ∼1 [1 + foldl Int . max ∼1 [1 + foldl Int . max ∼1 ( map depth [])] , 1 + ∼1] = 1 + foldl Int . max ∼1 [1 + foldl Int . max ∼1 [1 + foldl Int . max ∼1 []] , 0] = 1 + foldl Int . max ∼1 [1 + foldl Int . max ∼1 [1 + ∼1] , 0] = 1 + foldl Int . max ∼1 [1 + foldl Int . max ∼1 [0] ,0] = 1 + foldl Int . max ∼1 [1+0 ,0] = 1 + foldl Int . max ∼1 [1 ,0] = 1 + 1 = 2 (c) breadth ( T [ T [] , T [ T [] , T []]]) = foldl op+ 0 ( map breadth [ T [] , T [ T [] , T []]]) = foldl op+ 0 [ breadth ( T []) , breadth ( T [ T [] , T []])] = foldl op+ 0 [1 , foldl op+ 0 ( map breadth [ T [] , T []])] = foldl op+ 0 [1 , foldl op+ 1 [ breadth ( T []) , breadth ( T [])]] = foldl op+ 0 [1 , foldl op+ 0 [1 ,1]] = foldl op+ 0 [1 , 2] = 3 Aufgabe 7.5 (Elementare Baumprozeduren – jetzt mit fold) Betrachten Sie die folgenden Prozeduren, die mit Faltung auf Bäumen arbeiten, und erstellen Sie ein Ausführungsprotokoll, jeweils zum passenden Baum aus Aufgabe 4. Machen Sie sich daran klar, wie die Prozeduren funktionieren. (a) fun size ( T ts ) = fold ( fn xs ⇒ foldl op+ 1 xs ) ( T ts ) (b) fun depth ( T ts ) = fold ( fn xs ⇒ 1 + foldl Int . max ∼1 xs ) ( T ts ) (c) fun breadth ( T ts ) = fold ( fn nil ⇒ 1 | xs ⇒ foldl op+ 0 xs ) ( T ts ) (d) Überlegen Sie sich eine allgemeine Vorgehensweise, wie man an Baumprozeduren mittels fold herangehen kann. Lösung 7.5: (a) Sei f := (fn xs ⇒ foldl op+ 1 xs) size ( T [ T [] , T [ T [] , T []]]) = fold f ( T [ T [] , T [ T [] , T []]]) = f ( map ( fold f ) [ T [] , T [ T [] , T []]]) = f [ fold f ( T []) , fold f ( T [ T [] , T []])] = f [ f ( map ( fold f ) []) , f ( map ( fold f ) [ T [] , T []])] = f [ f [] , f [ fold f ( T []) , fold f ( T [])]] = f [1 , f [ f ( map ( fold f ) []) , f ( map ( fold f ) [])]] = f [1 , f [ f [] , f []]] = f [1 , f [1 ,1]] = f [1 , 3] = 5 (b) Sei f := (fn xs ⇒ 1 + foldl Int.max ∼1 xs) depth ( T [ T [ T []] , T []]) = fold f ( T [ T [ T []] , T []]) = f ( map ( fold f ) [ T [ T []] , T []]) = f [ fold f ( T [ T []]) , fold f ( T [])] = f [ f ( map ( fold f ) [ T []]) , f ( map ( fold f ) [])] = f [ f [ fold f ( T []) , f []]] = f [ f [ f ( map ( fold f ) []) , 1 + ∼1]] = f [ f [ f [] , 0]] 3 = f [ f [0 ,0]] = f [1] = 2 (c) Sei f:= (fn nil ⇒ 1 fn xs => foldl op+ 0 xs)| breadth ( T [ T [] , T [ T [] , T []]]) = fold f ( T [ T [] , T [ T [] , T []]]) = f ( map ( fold f ) [ T [] , T [ T [] , T []]]) = f [ fold f ( T []) , fold f ( T [ T [] , T []])] = f [ f ( map fold f nil ) , fold f ( T [ T [] , T []])] = f [ f nil , fold f ( T [ T [] , T []])] = f [1 , fold f ( T [ T [] , T []])] = f [1 , f ( map ( fold f ) [ T [] , T []])] = f [1 , f [ fold f ( T []) , fold f ( T [])]] = f [1 , f [ f ( map ( fold f ) nil , fold f ( T [])]] = f [1 , f [ f nil , fold f ( T [])]] = f [1 , f [1 , fold f ( T [])]] = f [1 , f [1 , f ( map ( fold f ) nil )]] = f [1 , f [1 , f nil ]] = f [1 , f [1 , 1]] = f [1 , 2] = 3 Aufgabe 7.6 Schreiben Sie eine Prozedur ternary: tree → tree, die testet, ob ein Baum ternär ist. Ist dies der Fall, so soll der Baum selbst ausgegeben werden, andernfalls soll die Ausnahme Notter geworfen werden. Lösung 7.6: exception Notter fun ternary ’ ( T []) | ternary ’ ( T [ t1 , t2 , t3 ]) ternary ’ t1 andalso | ternary ’ _ = true = ternary ’ t2 andalso ternary ’ t3 = false fun ternary t = if ternary ’ t then t else raise Notter Alternative: fun ternary ( T []) = T [] | ternary ( T [ t1 , t2 , t3 ]) = T [ ternary t1 , ternary t2 , ternary t3 ] | ternary _ = raise exception Notter Aufgabe 7.7 Schreiben Sie eine Prozedur fraktal: tree → tree, die die Blätter eines Baumes durch eine Kopie des Eingabebaums ersetzt. Lösung 7.7: fun fraktal ’ t ( T nil ) = t | fraktal ’ t ( T ts ) = T ( map ( fraktal ’ t ) ts ) fun fraktal t = fraktal ’ t t Alternative: fun fraktal t = fold ( fn nil ⇒ t | ts ⇒ T ts ) t 4 Aufgabe 7.8 (Waldbrand) (a) Der Baum brennt. Nach dem Feuer sind alle Blätter des Baumes verschwunden, nur noch die verkohlten Äste sind übrig. Schreiben Sie eine Prozedur feuer: tree → tree, die alle Blätter eines Baumes entfernt. Lediglich der atomare Baum darf unverändert bleiben. Beispiel: • • • Aus dem Baum • • • wird • . (b) Ist es möglich, dass der Baum nach einer Anwendung mehr Blätter als vorher hat? (c) Wie oft müssen Sie feuer anwenden, um den Baum komplett zu zerstören? Der Baum ist komplett zerstört, wenn nur noch die Wurzel übrig ist: T []. Welche wichtige Größe spielt hier mit? (d) Schreiben Sie eine Prozedur, die mithilfe von feuer diese Größe bestimmt! Lösung 7.8: (a) fun feuer ( T nil ) = T nil | feuer ( T ts ) = T ( foldl ( fn (x , a ) ⇒ if x = T [] then a else a @ [ feuer x ]) nil ts ) (b) Der Baum hat nach dem Brand maximal so viele Blätter, wie er vorher hatte (durch jedes zerstörte Blatt wird maximal ein Knoten zu einem neuen Blatt). (c) feuer muss t-mal angewendet werden. Die wichtige Größe ist die Tiefe t. Jeder Aufruf von feuer verringert die Tiefe des Baumes um 1. (d) fun d ( T []) = 0 | d t = d ( feuer t ) + 1 Aufgabe 7.9 (Achtung, nicht ganz nach Schema!) Deklarieren Sie eine Prozedur even: tree → bool, die für einen Baum true ausgibt, wenn die Anzahl der Knoten gerade ist und ansonsten false ausgibt. Verwenden Sie nur Werte vom Typ bool und Prozeduren. (a) Deklarieren Sie even ohne fold. (b) Deklarieren Sie even mit fold. Tipp: Verwenden Sie den Gleichheits-Operator (=). Zwei Bäume zusammen betrachtet (ohne den Vorgänger, der beide verbindet) haben genau dann eine gerade Anzahl an Knoten, wenn beide Bäume eine gerade Anzahl Knoten haben oder beide eine ungerade Anzahl Knoten haben. Lösung 7.9: (a) fun even t = let fun even ’ ( T ts , b ) = foldl even ’ ( not b ) ts in even ’ (t , true ) end not b berücksichtigt den Knoten, auf den even gerade aufgerufen wird. (b) val even = fold ( foldl op= false ) Der Startwert ist false, da er den Knoten, auf dem even aufgerufen wird, repräsentiert, denn der einzelne Knoten ist ungerade, also false. 5 3 Markierte Bäume Aufgabe 7.10 Schreiben Sie zwei Prozeduren, die die Größe und die Tiefe von markierten Bäumen bestimmen. Lösung 7.10: fun size ( L (_ , ts )) = foldl op+ 1 ( map size ts ) fun depth ( L (_ , ts )) = 1 + foldl Int . max ∼1 ( map depth ts ) Aufgabe 7.11 Schreiben Sie eine Prozedur greatest: int ltr → int, die die größte Markierung eines markierten Baumes zurückgibt. Lösung 7.11: fun greatest ( L (n , ts )) = foldl Int . max n ( map greatest ts ) Aufgabe 7.12 Schreiben Sie eine Prozedur avg: int ltr → int, die den (abgerundeten) Durchschnitt aller Marken zurückgibt. Lösung 7.12: fun avg ’ ( L (n , ts )) = foldl ( fn (( a , b ) , (c , d )) ⇒ ( a + c , b + d )) (n , 1) ( map avg ’ ts ) fun avg lt = let val ( summe , anzahl ) = avg ’ lt in summe div anzahl end Aufgabe 7.13 Schreiben Sie eine Prozedur exists: (α → bool) → α ltr → bool, die bestimmt, ob im Baum eine Marke existiert, die eine Bedingung p erfüllt. Lösung 7.13: fun exists p ( L (x , ls )) = foldl ( fn (y , a ) ⇒ y orelse a ) ( p x ) ( map ( exists p ) ls ) Aufgabe 7.14 Schreiben Sie eine Prozedur toLtr: α → tree → α ltr, die einen reinen Baum in einen markierten Baum überführt, indem alle seine Knoten mit einem übergebenen Wert markiert werden. Der Aufruf toLtr 2 t sollte also einen Baum t wie folgt in einen markierten Baum überführen: • • • • 2 • 2 2 2 2 Schreiben Sie die Prozedur sowohl mit als auch ohne die Hilfsprozedur fold. 6 Lösung 7.14: fun toLtr x ( T ts ) = L (x , map ( toLtr x ) ts ) Alternative: fun toLtr x t = fold ( fn xs ⇒ L (x , xs )) t 4 Baumuniversität Aufgabe 7.15 (fold lesen) Was berechnet die Prozedur val mystery = fold T? Lösung 7.15: Die Identität auf Bäumen. Aufgabe 7.16 Schreiben Sie eine Prozedur size: tree → int, die endrekursiv die Größe eines Baumes bestimmt. Überlegen Sie sich zunächst, warum die Berechnungen mit fold oder foldl und map nicht endrekursiv sind. Orientieren Sie sich stattdessen an der Prozedur prest aus dem Buch, die zu einer Pränummer den dazugehörigen Teilbaum liefert. Schreiben Sie zunächst eine endrekursive Hilfsprozedur size’: tree list → int → int. Lösung 7.16: fun size ’ nil n = n | size ’ (( T ts ):: tr ) n = size ’ ( ts @ tr ) ( n + 1) fun size t = size ’ [ t ] 0 Aufgabe 7.17 (Bäume bauen) Dieter hat gelernt, dass ein Baum durch die Menge seiner möglichen Adressen beschrieben werden kann. Da ihm diese Darstellung besser gefällt, sind alle seine Bäume vom Typ int list list. Damit Sie mit Dieters Bäumen etwas anfangen können, müssen Sie einen Baum aus einer Menge von Adressen konstruieren. Da Dieter ordentlich ist, können Sie davon ausgehen, dass Sie nur gültige Bäume bekommen und dass die Adressen strikt sortiert sind. Beispiel für eine solche Liste: [ [] , [1] , [1,1] , [1,2] , [2] ] Schreiben Sie eine Prozedur buildTree: int list list → tree, mit der Sie Dieters Baumdarstellung in die Ihnen vertraute Darstellung konvertieren können! Lösung 7.17: replaceNth xs n y ersetzt das n-te Listenelement durch y. insertInTree(a, t) fügt einen neuen Knoten, der als Adresse a übergeben wird, in den Baum t ein. Wir bauen den Baum, indem wir alle Adressen einfügen. fun replaceNth nil n y = raise Subscript | replaceNth ( x :: xr ) 0 y = y :: xr | replaceNth ( x :: xr ) n y = x ::( replaceNth xr ( n − 1) y ) fun insertInTree ( nil , | insertInTree ([ a ] , | insertInTree ( a :: ar , T ( replaceNth ts t) = t T ts ) = T ( ts @ [ T []]) T ts ) = ( a − 1) ( insertInTree ( ar , List . nth ( ts , a − 1)))) fun buildTree adrlist = foldl insertInTree ( T []) adrlist Aufgabe 7.18 (Baumschüler Dieter Schlau, heute mal ganz linear) (a) Schreiben Sie eine Prozedur postlin: tree → int list, die die Postlinearisierung eines Baumes bestimmt. 7 (b) Und nun zurück: Schreiben Sie eine Prozedur parsePostLin: int list → tree, die aus einer Postlinearisierung den Baum rekonstruiert. Lösung 7.18: (a) fun postlin ( T ts ) = List . concat ( map postlin ts ) @ [ length ts ] (b) fun parsepost ’ ([ ] , stack ) = stack | parsepost ’ (( x :: xr ) , stack ) = let val (h , t ) = iter x ([] , stack ) ( fn (h , t ) ⇒ ( hd t :: h , tl t )) handle Empty ⇒ raise Invalid in parsepost ’ ( xr , T ( rev h ) :: t ) end fun parsepost pl = case parsepost ’ ( pl ,[]) of [ r ] ⇒ r | _ ⇒ raise Invalid 8