ProMo08: Algorithmen für Bäume

Werbung
Linearisierung Breitendurchlauf Arithmetische Ausdrücke
Programmierung und Modellierung
mit Haskell
Algorithmen für Bäume
Martin Hofmann
Steffen Jost
LFE Theoretische Informatik, Institut für Informatik,
Ludwig-Maximilians Universität, München
19. Mai 2014
Martin Hofmann, Steffen Jost
Programmierung und Modellierung
08-1
Linearisierung Breitendurchlauf Arithmetische Ausdrücke
Binärbäume
Ist A eine Menge, so wird die Menge A4 der Binärbäume mit
Knotenmarkierungen in A (oder einfach Binärbäume über
A) induktiv definiert durch:
1
2
Der leere Baum ε ist in A4 .
Sind l und r in A4 und x in A, so ist das Tripel (l , x, r ) in A4 .
Alternativ kann man die Mengen A4
n der Binärbäume mit
Höhe (Tiefe) kleiner n rekursiv definieren durch:
A4
0 = { } (kein Baum hat negative Höhe).
4
2 An+1 = {ε} ∪ {(l , x, r ) | x ∈ A, l , r ∈ A4
n }.
1
4
Man beweist leicht A4
n ⊆ An+1 (Kumulativität).
S
4
4
A4 = n∈N A4
n ist der Limes der Folge A0 , A1 , . . .
Die Höhe (Tiefe) eines Baums t ist das kleinste n, so dass
t ∈ A4
n+1 .
Martin Hofmann, Steffen Jost
Programmierung und Modellierung
08-2
Linearisierung Breitendurchlauf Arithmetische Ausdrücke
Terminologie für Bäume
x heißt Wurzel oder (Wurzel)beschriftung von (x, l , r ).
l heißt linker und r rechter Unterbaum von (x, l , r ).
Ein Binärbaum der Form (x, ε, ε) heißt Blatt.
ε heißt leerer Baum, jeder andere Baum ist nichtleer.
Die Knoten(markierungen) und Teilbäume eines Binärbaums
sind rekursiv definiert:
Der leere Baum ε hat keine Knoten und nur sich selbst als
Teilbaum.
2 Die Knoten von (x, l , r ) sind x plus die Knoten von l und r .
Die Teilbäume von (x, l , r ) sind (x, l , r ) plus die Teilbäume von
l und r .
1
Sei [x] definiert als Abkürzung für ein Blatt (x, ε, ε) und
t = (6, (3, [2], (8, [5], ε)), (8, ε, [4]))
t hat Knoten {2, 3, 5, 8, 6, 4} und Teilbäume
{t, ((3, [2], (8, [5]ε)), (8, ε, [4])), (8, [5], ε), [2], [4], [5], ε}.
Martin Hofmann, Steffen Jost
Programmierung und Modellierung
08-3
Linearisierung Breitendurchlauf Arithmetische Ausdrücke
Typische Darstellung eines Binärbaumes
Wurzel: 6
Blätter: 2, 5, 4
6
3
8
2
8
4
5
Martin Hofmann, Steffen Jost
Programmierung und Modellierung
08-4
Linearisierung Breitendurchlauf Arithmetische Ausdrücke
Binärbäume in Haskell (Wdh.)
data Tree a = Empty | Node a (Tree a)
(Tree a)
leaf :: a -> Tree a
leaf a = Node a Empty Empty
oder besser mit Record Syntax:
data Tree a = Empty | Node { label :: a,
left :: Tree a,
right :: Tree a
t = Node 6 (Node 3 (leaf 2) (Node 8 (leaf 5) Empty ))
(Node 8 Empty (leaf 4))
Martin Hofmann, Steffen Jost
Programmierung und Modellierung
08-5
Linearisierung Breitendurchlauf Arithmetische Ausdrücke
Rekursion auf Bäumen
Höhe eines Baumes:
height :: Tree a -> Integer
height ( Empty)
= 0
height (Node _ l r) = 1 + max ( height l)( height r)
Eine Funktion f auf alle Beschriftungen anwenden:
mapTree :: (a -> b) -> Tree a -> Tree b
mapTree f ( Empty )
= Empty
mapTree f (Node x l r)
=
let l' = mapTree f l
x' = f x
r' = mapTree f r
in
Node x' l' r'
Beachte: mapTree f t erzeugt einen komplett neuen Baum!
mapTree (\x -> x) t liefert eine Kopie von t. Bringt aber
nichts!
Martin Hofmann, Steffen Jost
Programmierung und Modellierung
08-6
Linearisierung Breitendurchlauf Arithmetische Ausdrücke
Linearisierung
Mit Hilfe eines Baumdurchlaufs (engl. tree traversal) können
wir alle Knotenmarkierungen in einer Liste aufsammeln.
Die Reihenfolge der Listenelemente hängt von der Art des
Durchlaufs ab:
1
2
3
Vorordnung (engl. preorder)
Symmetrische Ordnung (engl. inorder)
Nachordnung (engl. postorder)
Martin Hofmann, Steffen Jost
Programmierung und Modellierung
08-7
Linearisierung Breitendurchlauf Arithmetische Ausdrücke
Vorordnung
Zuerst Markierung, dann linker, dann rechter Teilbaum.
preorder :: Tree a -> [a]
preorder ( Empty )
= []
preorder (Node x l r))
=
[x] ++ preorder l ++ preorder r
1
2
6
3
4
7
5
Martin Hofmann, Steffen Jost
Programmierung und Modellierung
08-8
Linearisierung Breitendurchlauf Arithmetische Ausdrücke
Symmetrische Ordnung
Zuerst linker Teilbaum, dann Markierung, dann rechter Teilbaum.
inorder :: Tree a -> [a]
inorder ( Empty )
= []
inorder (Node x l r)
=
inorder l ++ [x] ++ inorder r
5
2
7
1
4
6
3
Martin Hofmann, Steffen Jost
Programmierung und Modellierung
08-9
Linearisierung Breitendurchlauf Arithmetische Ausdrücke
Nachordnung
Zuerst linker, dann rechter Teilbaum, dann Markierung.
postorder :: Tree a -> [a]
postorder ( Empty )
= []
postorder (Node x l r)
=
postorder l ++ postorder r ++ [x]
7
4
6
1
3
5
2
Martin Hofmann, Steffen Jost
Programmierung und Modellierung
08-10
Linearisierung Breitendurchlauf Arithmetische Ausdrücke
Generischer Tiefendurchlauf
Alle Durchläufe bearbeiten die Teilbäume unabhängig
voneinander.
Das zugrundeliegende Schema heißt Tiefendurchlauf (engl.
depth-first)
foldTree :: ((b,a,b) -> b) -> b -> Tree a -> b
foldTree fn fe Empty = fe
foldTree fn fe (Node x l r) =
fn( foldTree fn fe l,x, foldTree fn fe r)
Die drei Baumlinearisierungen sind Instanzen davon:
preorder t = foldTree (\(l,x,r) -> [x]++l++r) [] t
inorder
t = foldTree (\(l,x,r) -> l++[x]++r) [] t
postorder t = foldTree (\(l,x,r) -> l++r++[x]) [] t
Martin Hofmann, Steffen Jost
Programmierung und Modellierung
08-11
Linearisierung Breitendurchlauf Arithmetische Ausdrücke
Breitendurchlauf
Beim Breitendurchlauf (engl. breadth-first traversal)
werden die Teilbäume nicht unabhängig behandelt.
1
2
3
4
5
6
7
Der Baum wird schichtenweise abgearbeitet: Hier also 1, 2, 3,
4, 5, 6, 7
Martin Hofmann, Steffen Jost
Programmierung und Modellierung
08-12
Linearisierung Breitendurchlauf Arithmetische Ausdrücke
Breitendurchlauf
Verallgemeinerung: Breitendurchlauf eines Waldes (engl.
forest), einer Liste von Bäumen.
breadthForest :: [Tree a] -> [a]
breadthForest [] = []
breadthForest ( Empty : frst) = breadthForest frst
breadthForest (Node x l r) : frst) =
x : breadthForest (frst ++[l,r])
Zuerst alle Wurzeln, dann alle Knoten der Tiefe 1, . . . .
breadthForest erlaubt eine elegante rekursive Formulierung
wie oben.
Für einen Breitendurchlauf eines einzelnen Baums t ruft man
breadthForest([t]) auf.
Martin Hofmann, Steffen Jost
Programmierung und Modellierung
08-13
Linearisierung Breitendurchlauf Arithmetische Ausdrücke
breadthForest[
]=
1
2
3
4
5
6
7
1 : breadthForest[
,
2
4
5
3 ]=
6
7
1 :: 2 :: breadthForest[
3 , 4 ,
6
Martin Hofmann, Steffen Jost
5
] = ...
7
Programmierung und Modellierung
08-14
Linearisierung Breitendurchlauf Arithmetische Ausdrücke
Arithmetische Ausdrücke als Bäume
Beispiel: Ein Mathe-Nachhilfe-Programm für Grundschüler. Es
druckt zufällig einen arithmetischen Ausdruck wie
(5 + 3) ∗ 2, und vergleicht den Lösungsvorschlag mit dem
Wert des Ausdrucks (hier: 16). Die Zahlen sollen einstellig
sein und die Operationen + und ∗.
Den Ausdruck können wir intern als Binärbaum repräsentieren.
∗
Richtig:
+
5
Falsch:
2
3
Martin Hofmann, Steffen Jost
+
∗
5
3
Programmierung und Modellierung
2
08-15
Linearisierung Breitendurchlauf Arithmetische Ausdrücke
Arithmetische Ausdrücke als Binärbäume
Es gibt drei Arten von Knotenmarkierungen: Zahl, “+”, “*”.
data Label
= Const ( Integer )
| Plus
| Times
type Expr = Tree Label
Beispiel:
a1 = Node Times (Node Plus (leaf( Const 5))
(leaf( Const 3)))
(leaf( Const 2))
Martin Hofmann, Steffen Jost
Programmierung und Modellierung
08-16
Linearisierung Breitendurchlauf Arithmetische Ausdrücke
Auswertung
Den Wert des Ausdrucks berechnen wir rekursiv:
eval
eval
eval
eval
eval
:: Expr -> Integer
( Empty )
= 0
(Node( Const n) _ _)
= n
(Node Plus l r)
= eval l + eval r
(Node Times l r)
= eval l * eval r
Wir sagen, eval (dt. auswerten) interpretiert den Ausdruck.
a1v = eval a1
16
Übung: Implementieren Sie eval als Anwendung von
foldTree!
Martin Hofmann, Steffen Jost
Programmierung und Modellierung
08-17
Linearisierung Breitendurchlauf Arithmetische Ausdrücke
Ausdrücke ausdrucken
Eine andere Interpretation des Ausdrucks ist seine
Repräsentation as Zeichenkette.
exprToString :: Expr -> String
exprToString Empty
= ""
exprToString (Node ( Const n) _ _) = show n
exprToString (Node Plus l r) =
"("++ exprToString l++" + "++ exprToString r++")"
exprToString (Node Times l r) =
"("++ exprToString l ++" * "++ exprToString r++")"
Alternativ als show-Instanz.
a1s = "((5 + 3) * 2)" : string
Übung: Implementieren Sie exprToString als Instanz von
foldTree!
Übung: Schreiben Sie eine bessere Ausgabefunktion, die
Klammern nur verwendet wenn nötig!
Martin Hofmann, Steffen Jost
Programmierung und Modellierung
08-18
Linearisierung Breitendurchlauf Arithmetische Ausdrücke
Ungültige Ausdrücke
Die Binärbaum-Repräsentation erlaubt ungültige Ausdrücke
(engl. malformed expressions).
∗
5
+
2
3
bad = Node Times (Node ( Const 5)
(leaf Plus) ( Const 3)) (leaf ( Const 2))
Zahlen sollen nur als Blattknoten auftreten!
Operationen nur als innere Knoten!
Martin Hofmann, Steffen Jost
Programmierung und Modellierung
08-19
Linearisierung Breitendurchlauf Arithmetische Ausdrücke
Arithmetische Ausdrücke als Datentyp
Wir definieren einen speziellen Baumtyp expr:
data Expr = Const Integer
| Plus Expr Expr
| Times Expr Expr
deriving (Eq)
a2 = Times (Plus ( Const 5) ( Const 3)) ( Const 2)
Die Ausdrucksrepräsentation ist ökonomischer.
Die Funktionsdefinitionen sind auch klarer!
eval
eval
eval
eval
:: Expr -> Integer
( Const
n)
= n
(Plus l r)
= eval l + eval r
( Times l r)
= eval l * eval r
Martin Hofmann, Steffen Jost
Programmierung und Modellierung
08-20
Linearisierung Breitendurchlauf Arithmetische Ausdrücke
Parsing
Jetzt wollen wir Ausdrücke einlesen! Wir verwenden einen Datentyp
von “Token”
data Token
= CONST Integer
| LPAREN | RPAREN | PLUS | TIMES
Als String notierte Ausdrücke entsprechen Token-Listen, z.B.
s1 = [ CONST 3, TIMES , LPAREN , CONST 8, PLUS , CONST 3,
RPAREN , PLUS , CONST 5, TIMES , CONST 4]
entspricht
"3 * ( 8 + 3 ) + 5 * 4"
Die Aufgabe, aus einer Zeichenkette solch eine Token-Liste zu
erzeugen, heißt lexikalische Analyse, oder lexing.
Übung: Implementieren Sie eine Funktion lex, die das tut.
lex :: String -> [ Token ]
Martin Hofmann, Steffen Jost
Programmierung und Modellierung
08-21
Linearisierung Breitendurchlauf Arithmetische Ausdrücke
Parsing durch rekursiven Abstieg
Die Aufgabe, aus einer Token-Liste einen Syntaxbaum zu erzeugen,
heißt Parsing, oder Syntaxanalyse.
Man kann die arithmetischen Ausdrücke durch folgende
BNF-Grammatik beschreiben:
expr
::= prod | prod + expr
prod
::= factor | factor * prod Punkt-vor-Strich und
factor ::= const | ( expr )
Klammerung wird von dieser Grammatik richtig behandelt.
expr
prod
factor
*
+
expr
prod
factor
3
(
expr
factor
8
prod
factor
3
Martin Hofmann, Steffen Jost
prod
)
prod + expr
factor * prod
5
factor
4
Programmierung und Modellierung
08-22
Linearisierung Breitendurchlauf Arithmetische Ausdrücke
Parsing durch rekursiven Abstieg
Die Syntaxanalyse orientiert sich an dieser Grammatik. Wir
schreiben drei gegenseitig rekursive Funktionen
parseExpr ::
[ Token ] -> (Expr ,[ Token ])
parseProd ::
[ Token ] -> (Expr ,[ Token ])
parseFactor :: [ Token ] -> (Expr ,[ Token ])
wobei parseExpr l versucht, ein möglichst großes Anfangsstück
von l als expr zu interpretieren. Ebenso: parseProd,
parseFactor.
parseExpr l = let (summand1 , rest1 ) = parseProd l in
case rest1 of
PLUS: rest2 -> let (summand2 , rest3 ) =
parseExpr rest2 in
(Plus(summand1 , summand2 ), rest3 )
_ -> (summand1 , rest1 )
Martin Hofmann, Steffen Jost
Programmierung und Modellierung
08-23
Linearisierung Breitendurchlauf Arithmetische Ausdrücke
Zusammenfassung
Bäume als induktiv definierte mathematische Objekte.
Bäume als rekursiver Datentyp
Rekursive Funktionen auf Bäumen
Linearisierungen: Vor-, In-, Nachordnung
Tiefen- und Breitensuche für Bäume
Bäume als Repräsentation von Syntax. Lexikalische und
Syntaxanalyse.
Martin Hofmann, Steffen Jost
Programmierung und Modellierung
08-24
Herunterladen