forked from deinprogramm/schreibe-dein-programm
-
Notifications
You must be signed in to change notification settings - Fork 0
/
i1tree.tex
2346 lines (2227 loc) · 79.7 KB
/
i1tree.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
% Diese Datei ist Teil des Buchs "Schreibe Dein Programm!"
% Das Buch ist lizensiert unter der Creative-Commons-Lizenz
% "Namensnennung - Weitergabe unter gleichen Bedingungen 4.0 International (CC BY-SA 4.0)"
% https://creativecommons.org/licenses/by-sa/4.0/deed.de
\chapter{Bäume}
\label{cha:trees}
\index{Baum}Bäume sind eine Form von Daten, die (wie Listen) besonders
oft in der Informatik vorkommt. Oft ergeben sich baumförmige
Datendefinitionen aus der Problemstellung. Wenn wir über diese
Datendefinitionen abstrahieren, entsteht eine universell verwendbare
Form von Daten, der \textit{Binärbaum}\index{Binärbaum}. Diese
Binärbäume sind ähnlich vielseitig wie Listen und erlauben uns
außerdem, Daten so in Bäumen zu organisieren, dass wir sie schnell
wiederfinden können.
\section{Stammbäume}
Die Idee, den Baum als Metapher für eine bestimmte Form von Daten zu
benutzen, findet sich bereits in der Bibel, die Wörter wie
"<Baumstumpf"> und "<Spross"> benutzt, um Abstammmung zu beschreiben.
Erste bildliche Darstellungen von Stammbäumen sind aus diesen
Beschreibungen ab dem 11.\ Jahrhundert abgeleitet worden.
Das Stammbaum-Beispiel ist etwas ausgetreten, aber dennoch instruktiv.
In Stammbäumen sind in der Regel für eine Person ihr Name sowie
Verbindungen zu den beiden Eltern vermerkt. Das führt zu folgender
Datendefinition.
%
\begin{lstlisting}
; Eine Person hat folgende Eigenschaften:
; - Name
; - Elternteil #1
; - Elternteil #2
\end{lstlisting}
%
Diese Definition lässt offen, um was für Daten es sich bei den beiden
Elternteilen handelt. Natürlich sind es auch Personen, aber wenn wir
in einem Stammbaum weit genug nach oben gehen, sind diese irgendwann
unbekannt: Jeder konkrete Stammbaum endet irgendwo. Wir brauchen also
auch noch eine Repräsentation für einen "<unbekannten Elternteil">
ohne (bekannte) Eigenschaften:
%
\begin{lstlisting}
; Ein unbekannter Elternteil hat keine Eigenschaften
(define-record unknown-parent
make-unknown-parent
unknown-parent?)
\end{lstlisting}
%
Daraus entsteht eine Datendefinition für "<Elternteil">:
%
\begin{lstlisting}
; Ein Elternteil ist eins der folgenden:
; - eine Person
; - ein unbekannter Elternteil
\end{lstlisting}
%
Diese~-- und die Datendefinition für "<Person"> können wir nun in Code
übersetzen:
%
\begin{lstlisting}
(define-record person
make-person
person?
(person-name string)
(person-parent-1 parent)
(person-parent-2 parent))
(define parent
(signature
(mixed person unknown-parent)))
\end{lstlisting}
%
Für das unbekannte Elternteil stellen wir gleich mal einen Wert her:
%
\begin{lstlisting}
(define an-unknown-parent (make-unknown-parent))
\end{lstlisting}
%
Hier ein kleiner Stammbaum als Beispiel:
%
\begin{lstlisting}
(define slash
(make-person "Slash"
(make-person "Ola Hudson"
an-unknown-parent
an-unknown-parent)
(make-person "Anthony Hudson"
an-unknown-parent
an-unknown-parent)))
(define london-hudson
(make-person "London Hudson"
slash
(make-person "Perla Ferrar"
an-unknown-parent
an-unknown-parent)))
\end{lstlisting}
%
Wir schreiben nun eine Funktion, die feststellen soll, ob jemand der
Vorfahr einer Person ist, so etwa:
%
\begin{lstlisting}
; Ist jemand Vorfahr:in einer Person?
(: ancestor? (string person -> boolean))
(check-expect (ancestor? "Slash" london-hudson) #t)
(check-expect (ancestor? "Axl" london-hudson) #f)
\end{lstlisting}
%
Die Schablone für diese Funktion sieht folgendermaßen aus:
%
\begin{lstlisting}
(define ancestor?
(lambda (name person)
...
(person-name person)
(person-parent-1 person)
(person-parent-2 person)
...))
\end{lstlisting}
%
Was können wir mit diesen Bestandteilen anfangen? Den Namen der
Person könnten wir mit dem gesuchten Namen vergleichen~-- wenn ja,
handelt es sich um einen Vorfahren:
%
\begin{lstlisting}
(define ancestor?
(lambda (name person)
(if (string=? name (person-name person))
#t
...)
(person-parent-1 person)
(person-parent-2 person)
...))
\end{lstlisting}
%
Bei \lstinline{(person-parent-1 person)} und
\lstinline{(person-parent-2 person)} handelt es sich um gemischte
Daten. Wir könnten die nötige Verzweigung direkt in
\lstinline{ancestor?} einbauen. Genauso können wir eine separate
Funktion schreiben, welche die Frage beantwortet, ob ein Elternteil
Vorfahr ist. Da es zwei Elternteile gibt, lohnt sich tendenziell eine
solche separate Funktion mit Kurzbeschreibung und Signatur wie folgt:
%
\begin{lstlisting}
; Ist jemand Vorfahr:in eines Elternteils?
(: parent-ancestor? (string parent -> boolean))
\end{lstlisting}
%
Diese Funktion schreiben wir gleich im Anschluss. Aber ihre Signatur
ist genug, damit wir die Schablone in Funktion \lstinline{ancestor?}
weiter ausfüllen, indem wir überprüfen, ob Elternteil Nr.~1 oder Nr.~2
Vorfahr ist:
%
\begin{lstlisting}
(define ancestor?
(lambda (name person)
(if (string=? name (person-name person))
#t
(if (or (parent-ancestor? name (person-parent-1 person))
(parent-ancestor? name (person-parent-2 person)))
#t
#f))))
\end{lstlisting}
%
Diese Funktion ist schon korrekt, aber sie könnte noch etwas eleganter
sein. Der zweite \lstinline{if}-Ausdruck liefert \lstinline{#t}, falls
die Bedingung \lstinline{#t} und \lstinline{#f}, falls die Bedingung
\lstinline{#f} liefert: Es kommt also immer das Ergebnis der Bedingung
heraus. Das ist eine allgemein anwendbare Regel:
%
\begin{lstlisting}
(if $b$ #t #f) $=$ $b$
\end{lstlisting}
%
Wir können \lstinline{ancestor?} also verkürzen auf:
%
\begin{lstlisting}
(define ancestor?
(lambda (name person)
(if (string=? name (person-name person))
#t
(or (parent-ancestor? name (person-parent-1 person))
(parent-ancestor? name (person-parent-2 person))))))
\end{lstlisting}
%
Auch den verbleibenden \lstinline{if}-Ausdruck können wir noch
loswerden, weil er \lstinline{#t} ergibt, wenn die Bedingung
\lstinline{#t} ergibt oder wenn der \lstinline{or}-Ausdruck
\lstinline{#t} liefert. Wir können deshalb die Funktion mit einem
großen \lstinline{or} schreiben:
%
\begin{lstlisting}
(define ancestor?
(lambda (name person)
(or (string=? name (person-name person))
(parent-ancestor? name (person-parent-1 person))
(parent-ancestor? name (person-parent-2 person)))))
\end{lstlisting}
%
Notwendig war diese Vereinfachung nicht, aber schöner sieht das
Resultat schon aus, finden wir!
Es fehlt noch die Hilfsfunktion \lstinline{parent-ancestor?}. Hier
sind ein paar Tests:
%
\begin{lstlisting}
(check-expect (parent-ancestor? "Slash" london-hudson) #t)
(check-expect (parent-ancestor? "Axl" london-hudson) #f)
(check-expect (parent-ancestor? "Slash" an-unknown-parent) #f)
\end{lstlisting}
%
Gerüst und Schablone ergeben sich~-- wie immer~-- aus der
Datendefinition von \lstinline{parent}:
%
\begin{lstlisting}
(define parent-ancestor?
(lambda (name parent)
(cond
((person? parent) ...)
((unknown-parent? parent) ...))))
\end{lstlisting}
%
Für den ersten Fall können wir \lstinline{ancestor?} benutzen, im
zweiten Fall können wir mit \lstinline{#f} antworten:
%
\begin{lstlisting}
(define parent-ancestor?
(lambda (name parent)
(cond
((person? parent)
(ancestor? name parent))
((unknown-parent? parent)
#f))))
\end{lstlisting}
%
Fertig!
\begin{aufgabeinline}
Ändere die Funktion \lstinline{ancestor?} dahingehend, dass eine
Person nicht ihr eigener Vorfahr ist.
Achte darauf, dass ansonsten die Funktion noch richtig arbeitet!
Wird die Funktion einfacher?
\end{aufgabeinline}
\section{Binärbäume}
\label{sec:trees}
Wir schauen uns nochmal die Record-Defininiton von \lstinline{person}
an:
%
\begin{lstlisting}
(define-record person
make-person
person?
(person-name string)
(person-parent-1 parent)
(person-parent-2 parent))
\end{lstlisting}
%
Vielleicht hat Dich das an eine Record-Definition aus
Kapitel~\ref{cha:selbstbezug} erinnert:
%
\begin{lstlisting}
(define-record confluence
make-confluence
confluence?
(confluence-location string)
(confluence-main-stem river)
(confluence-tributary river))
\end{lstlisting}
%
Die Struktur ist bei beiden Definitionen gleich. Insbesondere
enthalten beide Definitionen jeweils zwei Selbstreferenzen. Bei
\lstinline{person} ist die Selbstreferenz auf \lstinline{parent}, das
so definiert ist:
%
\begin{lstlisting}
(define parent
(signature
(mixed person unknown-parent)))
\end{lstlisting}
%
Bei \lstinline{confluence} ist die Selbstreferenz auf
\lstinline{river}:
%
\begin{lstlisting}
(define river
(signature (mixed creek confluence)))
\end{lstlisting}
%
Die jeweils anderen Fälle von \lstinline{parent} und
\lstinline{person} unterscheiden sich leicht:
%
\begin{lstlisting}
(define-record unknown-parent
make-unknown-parent
unknown-parent?)
(define-record creek
make-creek
creek?
(creek-origin string))
\end{lstlisting}
%
In beiden steckt selbst aber keine Selbstreferenz mehr. Beide
Datendefinitionen bilden baumartige Strukturen ab: Ein
\lstinline{person}- oder \lstinline{confluence}-Record bildet einen
Ast, der zweifach verzweigt. Ein Baum endet jeweils bei
\lstinline{unknown-parent}- oder \lstinline{creek}-Records. Weil die
"<inneren"> Äste immer zweifach verzweigen, handelt es sich in beiden
Fällen um \textit{Binärbäume}\index{Binärbaum}.
Über diese beiden Sätze von Definitionen können wir abstrahieren.
Fangen wir mit \lstinline{person} und \lstinline{confluence} an. Der
gängige Name für die Verzweigungen innerhalb eines Binärbaums ist
\textit{Knoten}\index{Knoten} oder \textit{innerer Knoten}, auf
Englisch \textit{node}. Wir brauchen außerdem einen Namen für die
"<Namensdaten">, die bei beiden noch dabei sind. Üblich ist
\textit{Markierung}\index{Markierung}, auf Englisch \textit{label}.
Die Signatur für den Selbstbezug nennen wir einfach \lstinline{tree}:
%
\begin{lstlisting}
(define-record node
make-node node?
(node-label string)
(node-left-branch tree)
(node-right-branch tree))
\end{lstlisting}
%
Das Wort "<branch"> heißt wörtlich übersetzt "<Zweig">, wir verwenden
aber die Begriffe "<linker Teilbaum"> und "<rechter Teilbaum">, was im
Deutschen üblicher ist.\index{Teilbaum}
Bei der Definition für \lstinline{tree} brauchen wir noch einen Namen
für die Werte an den Rändern des Baums~-- genannt
\textit{Blätter}\index{Blatt}, auf Englisch \textit{leaf}.
%
\begin{lstlisting}
(define tree
(signature (mixed leaf node)))
\end{lstlisting}
%
Es fehlt noch die Definition von \lstinline{leaf}. Hier ist es nicht
ganz so einfach, weil \lstinline{creek} noch einen Namen enthält,
\lstinline{unknown-parent} aber nicht. Wir müssen also über beide
abstrahieren. Einen Namen haben wir ja schon~-- \lstinline{leaf}~--
es fehlt noch das \lstinline{lambda}:
%
\begin{lstlisting}
(define tree-of
(lambda (leaf)
(signature (mixed leaf node))))
\end{lstlisting}
%
Das zieht noch eine weitere Änderung nach sich, weil \lstinline{tree}
ja in der Definition von \lstinline{node} verwendet wird. Wir müssen
da entsprechend den \lstinline{leaf}-Parameter mit durchziehen:
%
\begin{lstlisting}
(define-record (node-of leaf)
make-node node?
(node-label string)
(node-left-branch (tree-of leaf))
(node-right-branch (tree-of leaf)))
\end{lstlisting}
%
Die Notation für die Abstraktion der Record-Signatur mit den
Extra-Klammern um \lstinline{(node-of leaf)} haben wir bisher erst
einmal gesehen, bei der Definition von \lstinline{cons-list-of} in
Abschnitt~\ref{function:cons-list-of} auf Seite~\pageref{function:cons-list-of}.
Wir könnten an dieser Stelle fertig sein. Wir nehmen aber noch eine
Verallgemeinerung vor: Wie wir sehen werden, müssen die Markierungen
in Bäumen nicht unbedingt Zeichenketten sein~-- wir werden da noch
andere Arten von Werten ablegen wollen. Darum abstrahieren wir auch
über die Signatur der Markierungen noch. Außerdem reichen wir noch
die Datendefinitionen nach:
%
\begin{lstlisting}
; Ein Knoten besteht aus
; - Markierung
; - linken Ast
; - rechter Ast
(define-record (node-of leaf label)
make-node node?
(node-label label)
(node-left-branch (tree-of leaf label))
(node-right-branch (tree-of leaf label)))
; Ein Binärbaum ist entweder ein Blatt oder ein Knoten
(define tree-of
(lambda (leaf label)
(signature (mixed leaf (node-of leaf label)))))
\end{lstlisting}
%
Das nun ist die Definition eines Binärbaums in Reinform. Hier sind
zwei Beispiele, bei denen wir einfach den Wert \lstinline{#f} als
Blatt verwendet haben:
%
\begin{lstlisting}
(: tree1 (tree-of false number))
(define tree1 (make-node 3 (make-node 4 #f (make-node 7 #f #f)) #f))
(: tree2 (tree-of false number))
(define tree2 (make-node 17 (make-node 3 #f tree1) #f))
\end{lstlisting}
%
Hier ist noch ein weiteres Beispiel, bei dem die Blätter Zahlen sind
und die Markierungen Zeichenketten
\begin{lstlisting}
(: tree3 (tree-of number string))
(define tree3 (make-node "Axl"
(make-node "Slash" 17
(make-node "Duff" 14 23))
12))
\end{lstlisting}
%
\begin{figure}[tb]
\centering
\begin{minipage}{0.3\textwidth}
\begin{center}
\lstinline{tree1}\\[1ex]
\begin{tikzpicture}[level/.style={sibling distance=20mm/#1, level distance=10mm}]
\node (three){3}
child {node (four) {4}
child {node {$\bullet$}}
child {node (seven) {7}
child {node {$\bullet$}}
child {node {$\bullet$}}
}
}
child {node {$\bullet$}};
\end{tikzpicture}
\end{center}
\end{minipage}
\begin{minipage}{0.3\textwidth}
\begin{center}
\lstinline{tree2}\\[1ex]
\begin{tikzpicture}[level/.style={sibling distance=20mm/#1, level distance=10mm}]
\node (seventeen){17}
child {node (three0) {3}
child {node {$\bullet$}}
child {node (three){3}
child {node (four) {4}
child {node {$\bullet$}}
child {node (seven) {7}
child {node {$\bullet$}}
child {node {$\bullet$}}
}
}
}
}
child {node {$\bullet$}};
\end{tikzpicture}
\end{center}
\end{minipage}
\begin{minipage}{0.3\textwidth}
\begin{center}
\lstinline{tree3}\\[1ex]
\begin{tikzpicture}[level/.style={sibling distance=20mm/#1, level distance=10mm}]
\node (Axl){Axl}
child {node (Slash) {Slash}
child {node {17}}
child {node (Duff) {Duff}
child {node {14}}
child {node {23}}
}
}
child {node {12}};
\end{tikzpicture}
\end{center}
\end{minipage}
\caption{Beispielbäume}
\label{fig:tree123}
\end{figure}
\noindent Abbildung~\ref{fig:tree123} stellt die drei Bäume \lstinline{tree1},
\lstinline{tree2} und \lstinline{tree3} grafisch dar. Dort hat jeder
Baum erkennbar einen "<obersten"> Knoten, die sogenannte
\textit{Wurzel}\index{Wurzel}~-- ein Begriff, der im Zusammenhang mit
Bäumen häufig verwendet wird. Bei dem Begriff muss immer mitgesagt
werden, \emph{wovon} die Wurzel gemeint ist. So ist
\lstinline{tree1} ein Teil von \lstinline{tree2}. Entsprechend steckt
die Wurzel von \lstinline{tree1} (der Knoten mit der 3) auch in
\lstinline{tree2} drin, aber die Wurzel von \lstinline{tree2} ist eben
der Knoten mit der 17.
Mit der Wurzel hängt auch der Begriff \textit{Pfad}\index{Pfad}
zusammen: Der Pfad eines Knotens oder Blattes ist die Liste der Knoten
von der Wurzel zu diesem Knoten oder Blatt. Der Pfad der 14 in
\lstinline{tree3} in Abbildung~\ref{fig:tree123} besteht zum Beispiel
aus den Knoten Axl, Slash und Duff.
Wir können \lstinline{tree-of} benutzen, um die Definitionen von
\lstinline{river} und \lstinline{confluence} zu vereinfachen:
%
\begin{lstlisting}
(define river (tree-of creek string))
(define river? node?)
(define make-confluence make-node)
(define confluence-location node-label)
(define confluence-main-stem node-left-branch)
(define confluence-tributary node-right-branch)
\end{lstlisting}
%
\begin{aufgabeinline}
Definiere \lstinline{person} mit Hilfe von \lstinline{tree-of}!
\end{aufgabeinline}
%
Auf Bäumen kann man alle möglichen Sachen berechnen. Ein Beispiel ist
die \textit{Tiefe}\index{Tiefe!eines Baums}, also die maximale Anzahl
Knoten auf dem Weg zu einem Blatt. (Manchmal heißt diese Größe auch
die \textit{Höhe}\index{Höhe!eines Baums} eines Baums.) Für die Tiefe
des Baums sind die Signaturen der Blätter und Markierungen egal:
%
\begin{lstlisting}
; Tiefe eines Baums berechnen
(: depth ((tree-of %leaf %label) -> natural))
\end{lstlisting}
%
Hier sind zwei Testfälle:
%
\begin{lstlisting}
(check-expect (depth tree1) 3)
(check-expect (depth tree2) 5)
\end{lstlisting}
%
Bei \lstinline{tree1} sind es die Knoten mit den Markierunen 3, 4 und
7, die den maximal langen Weg zu einem Blatt bilden. Bei
\lstinline{tree2} sind es 17, 3, 3, 4, 7.
Es geht wieder los mit der Konstruktionsanleitung. Wir brauchen die
Schablone für gemischte Daten als Eingabe. Da die Datendefinition für
Binärbäume zwei Fälle hat, brauchen wir ein \lstinline{cond} mit zwei
Zweigen. Beim ersten können wir Bedingungen mit \lstinline{node?}
bilden. Die Blätter haben kein festes Prädikat, aber das sind einfach
alle Bäume, die keine Knoten sind~-- wir können also statt einer
Bedingung \lstinline{else} schreiben:
%
\begin{lstlisting}
(define depth
(lambda (tree)
(cond
((node? tree) ...)
(else ... 0))))
\end{lstlisting}
%
In den Knoten stecken zwei Selbstbezüge, wir brauchen also zwei
rekursive Aufrufe:
%
\begin{lstlisting}
(define depth
(lambda (tree)
(cond
((node? tree)
...
(depth (node-left-branch tree))
(depth (node-right-branch tree))
...)
(else ...))))
\end{lstlisting}
%
Für die Tiefe zählt nur der Weg mit der maximalen Anzahl von Knoten.
Außerdem müssen wir den Knoten in \lstinline{tree} noch mitzählen.
Blätter zählen überhaupt nicht:
%
\begin{lstlisting}
(define depth
(lambda (tree)
(cond
((node? tree)
(+ 1
(max (depth (node-left-branch tree))
(depth (node-right-branch tree)))))
(else 0))))
\end{lstlisting}
%
Fertig!
%
\begin{aufgabeinline}
Schreibe eine Funktion, die alle Knoten eines Baums zählt!
\end{aufgabeinline}
\begin{aufgabeinline}
Schreibe eine Funktion, die für einen Baum eine Liste aller Blätter
des Baums liefert!
\end{aufgabeinline}
\section{Bäume für's Suchen}
\label{sec:search-trees}
Viele Probleme bei der Programmierung sind "<Suchprobleme">: Einen
Namen, eine Telefonummer, eine Bestellnummer aus einer Liste
heraussuchen. Darum geht es in diesem Abschnitt und wir fangen damit
an, dass wir das Wort "<Liste"> wörtlich nehmen und eine Funktion wie
folgt schreiben:
%
\begin{lstlisting}
; ist Wert Element einer Liste?
(: member? (%a (list-of %a) -> boolean))
\end{lstlisting}
%
Wir haben eine Signaturvariable verwendet, weil es sich bei den
Listenelementen mal um Zahlen, mal um Zeichenketten, mal um etwas
anderes handeln kann. Hier sind ein paar Testfälle:
%
\begin{lstlisting}
(check-expect (member? 5 empty) #f)
(check-expect (member? 2 (list 1 2 3)) #t)
(check-expect (member? "Slash" (list "Axl" "Slash")) #t)
(check-expect (member? "Buckethead" (list "Axl" "Slash")) #f)
\end{lstlisting}
%
Hier Gerüst und Schablone für Funktionen auf Listen:
%
\begin{lstlisting}
(define member?
(lambda (element list)
(cond
((empty? list) ...)
((cons? list)
...
(first list)
(member? element (rest list))
...))))
\end{lstlisting}
%
Bei der leeren Liste kann die Funktion nur \lstinline{#f}
zurückgeben. Bei der Cons-Liste legt die Schablone nahe, dass die
Funktion erst einmal prüfen sollte, ob \lstinline{(first list)} das
gesucht Element ist.
Klingt einfach, oder? Aber \emph{wie} prüfen wir das? Wir könnten
das hier hinschreiben:
%
\begin{lstlisting}
(= element (first list))
\end{lstlisting}
%
\ldots~aber das würde die Funktion auf Zahlen beschränken, weil
\lstinline{=} nur auf Zahlen funktioniert. Für die beiden Testfälle
mit Zeichenketten müssten wir \lstinline{string=?} verwenden. Wir
müssen also \lstinline{member?} über \lstinline{=} respektive
\lstinline{string=?} abstrahieren, noch bevor die Funktion überhaupt
fertig ist. Wir brauchen wie immer einen weiteren Parameter und
nennen ihn \lstinline{equals?}:
%
\begin{lstlisting}
(define member?
(lambda (equals? element list)
(cond
((empty? list) ...)
((cons? list)
...
(first list)
(member? equals? element (rest list))
...))))
\end{lstlisting}
%
(Aufpassen: Der rekursive Aufruf muss~-- wie immer~-- auch durch den
neuen Parameter erweitert werden.)
Jetzt können wir den Vergleich mit Hilfe von \lstinline{equals?}
durchführen und diesen mit einer binären Verzweigung verarbeiten:
%
\begin{lstlisting}
(define member?
(lambda (equals? element list)
(cond
((empty? list) #f)
((cons? list)
(if (equals? element (first list))
#t
(member? equals? element (rest list)))))))
\end{lstlisting}
%
Signatur und Testfälle haben von dem neuen Parameter noch nichts
mitbekommen. Die \lstinline{equals?}-Funktion akzeptiert zwei
Listenelemente und liefert ein boolesches Ergebnis. Da die
Listenelemente die Signatur \lstinline{%a}
haben, sieht die Signaturdeklaration für \lstinline{member?} so aus:
%
\begin{lstlisting}
(: member? ((%a %a -> boolean) %a (list-of %a) -> boolean))
\end{lstlisting}
%
Bei den Testfällen müssen wir jeweils noch die richtige
Vergleichsfunktion übergeben. Das ist \lstinline{=} für Zahlen und
\lstinline{equals?} für Zeichenketten.
%
\begin{lstlisting}
(check-expect (member? = 5 empty) #f)
(check-expect (member? = 2 (list 1 2 3)) #t)
(check-expect (member? string=? "Slash" (list "Axl" "Slash")) #t)
(check-expect (member? string=? "Buckethead" (list "Axl" "Slash")) #f)
\end{lstlisting}
%
Fertig!
Allerdings hat \lstinline{member?} einen Nachteil: Bei kurzen Listen
oder wenn das gesuchte Element am Anfang der Liste steht, wird
\lstinline{member?} ziemlich schnell fertig. Aber stell Dir vor, die
Liste hat ein paar Millionen Elemente und das gesuchte Element ist am
Ende. Oder gar nicht drin: Dann muss \lstinline{member?} die gesamte
Liste abklappern.
%
\begin{aufgabeinline}
Schreibe mit Hilfe von \lstinline{member?} eine Funktion, die von
zwei Listen alle Elemente liefert, die in beiden Listen stehen, also
deren Schnittmenge. Wie lange braucht diese Funktion im
ungünstigsten Fall?
\end{aufgabeinline}
%
Kann irgendwie schneller herausfinden, ob ein Wert Element einer Menge
ist oder nicht? In der Tat ist das möglich, aber nicht mit Listen:
Wir brauchen eine andere Struktur, um das Suchen zu beschleunigen~--
Bäume.
\begin{figure}[tb]
\begin{tikzpicture}[level/.style={sibling distance=60mm/#1}]
\node (z){$M$}
child {node (a) {$B$}
child {node (b) {$A$}
child {node {$\bullet$}}
child {node {$\bullet$}}
}
child {node (g) {$D$}
child {node {$\bullet$}}
child {node {$\bullet$}}
}
}
child {node (j) {$O$}
child {node (k) {$N$}
child {node {$\bullet$}}
child {node {$\bullet$}}
}
child {node (l) {$R$}
child {node {$\bullet$}}
child {node {$\bullet$}}
}
};
\end{tikzpicture}
\caption{Sortierter Baum über Buchstaben}
\label{fig:searchtree}
\end{figure}
Schau Dir mal Abbildung~\ref{fig:searchtree} an. In diesem Baum musst
Du nicht alle Elemente anschauen, um ein bestimmtes Element zu
finden. Das liegt daran, dass die Buchstaben in dem Baum auf
bestimmte Art nach dem Alphabet sortiert sind:
Die Wurzel mit der Markierung $M$ hat zwei Teilbäume~-- die
Markierungen des linken Teilbaums liegen allesamt \emph{vor} $M$ im
Alphabet, alle Markierungen des rechten Teilbaums \emph{nach} $M$.
Wenn Du also nach einem Buchstaben suchst~-- nehmen wir mal $D$~--
dann weißt Du, wenn Du die Wurzel mit $M$ siehst, dass $D$ im linken
Teilbaum von $M$~-- mit der Markierung $B$ und von da aus im rechten
Teilbaum von $B$ stehen muss. Die Knoten $A$, $O$, $N$, $R$ kannst Du
ignorieren.
Die Suche braucht also höchstens so viele Schritte wie der Baum tief
ist. Das ist schonmal besser, als in der Liste zu suchen, wo wir
potenziell alle Elemente anschauen müssen.
Programmieren wir das also!
Wir fangen mit einem sortierten Baum über reellen Zahlen an.
(Reelle Zahlen deshalb, weil wir sie einfach mit \lstinline{=} und
\lstinline{<} vergleichen können. Wir verallgemeinern das später.)
Die Zahlen kleben als Markierungen an den Knoten. An den Blättern
steht nichts relevantes, wir benutzen deshalb immer \lstinline{#f}.
Entsprechend sehen Kurzbeschreibung und Signatur so aus:
%
\begin{lstlisting}
; Ist eine Zahl in einem sortierten Baum vorhanden?
(: tree-member? (real (tree-of false real) -> boolean))
\end{lstlisting}
%
Die Signatur \lstinline{false}\index{false@\texttt{false}} ist neu:
Sie beschreibt nur den Wert \lstinline{#f}. Entsprechend gibt es
natürlich auch eine Signatur
\lstinline{true}\index{true@\texttt{true}} für \lstinline{#t}.
Als Beispielbaum für die Tests benutzen wir diesen hier:
%
\begin{lstlisting}
(define tree4
(make-node 5
(make-node 3 #f #f)
(make-node 17
(make-node 10 #f (make-node 12 #f #f))
#f)))
\end{lstlisting}
%
Hier sind ein paar Tests:
%
\begin{lstlisting}
(check-expect (tree-member? 5 tree4) #t)
(check-expect (tree-member? 17 tree4) #t)
(check-expect (tree-member? 3 tree4) #t)
(check-expect (tree-member? 10 tree4) #t)
(check-expect (tree-member? 2 tree4) #f)
\end{lstlisting}
%
Hier ist das Gerüst:
\begin{lstlisting}
(define tree-member?
(lambda (value tree)
...))
\end{lstlisting}
%
In die Schablone für Bäume tragen wir gleich den zweiten Fall ein:
Wenn die Funktion ein Blatt erreicht, dann ist das \lstinline{value}
definitiv nicht im Baum, das Ergebnis dann \lstinline{#f}:
%
\begin{lstlisting}
(define tree-member?
(lambda (value tree)
(cond
((node? tree)
...
(node-label tree)
(tree-member? value (node-left-branch tree))
(tree-member? value (node-right-branch tree))
...
(else #f)))))
\end{lstlisting}
%
Bei Knoten können wir drei Fälle unterscheiden: Wenn die Markierung
gerade das gesucht \lstinline{value} ist, wenn \lstinline{value}
kleiner ist als die Markierung (also im linken Teilbaum stehen muss)
und wenn sie größer ist. Daraus ergibt sich folgende
Weiterentwicklung:
\begin{lstlisting}
(define tree-member?
(lambda (value tree)
(cond
((node? tree)
...
(tree-member? value (node-left-branch tree)))
(tree-member? value (node-right-branch tree))
...
(cond
((= value (node-label tree)) #t)
((< value (node-label tree)) ...
(else ...)))
(else #f))))
\end{lstlisting}
%
Da \lstinline{(node-label tree)} zweimal vorkommt, machen wir dafür
eien Definition und setzen die Bestandteile der Schablone so zusammen:
%
\begin{lstlisting}
(define tree-member?
(lambda (value tree)
(cond
((node? tree)
(define label (node-label tree))
(cond
((= value label) #t)
((< value label)
(tree-member? value (node-left-branch tree)))
(else
(tree-member? value (node-right-branch tree)))))
(else #f))))
\end{lstlisting}
%
Fertig!
\section{Sortierte Bäume herstellen}
In den Testfällen für \lstinline{tree-member?} haben wir immer den
Baum \lstinline{tree4} vwerwendet, den wir direkt mit
\lstinline{make-node} konstruiert haben. Dabei mussten wir selbst
darauf achten, dass er auch sortiert ist. In diesem Abschnitt
automatisieren wir diese Konstruktion, dann kann dabei auch kein
Fehler passieren. (Wenn wir korrekt programmieren zumindest.)
Wir schreiben dafür eine Funktion, die ein neues Element in einen
bestehenden sortierten Baum einfügt. Kurzbeschreibung und Signatur
sind wie folgt:
%
\begin{lstlisting}
; Zahl in sortierten Baum einfügen
(: tree-insert (real (tree-of false real) -> (tree-of false real)))
\end{lstlisting}
%
Testfälle brauchen wir als nächstes. Wir könnten das so machen wie
immer: Wir schreiben einen Aufruf von \lstinline{tree-member?} hin und
den Ergebniswert, den wir uns erhoffen. In diesem Fall aber ist es
gar nicht so wichtig, was der Ergebniswert genau ist. Wichtig ist,
dass ein eingefügtes Element im Ergebnisbaum auch drin ist. Außerdem
ist es mühsam, immer den ganzen Baum hinzuschreiben. Darum benutzen
wir \lstinline{tree-member?}, um \lstinline{tree-insert} zu testen.
%
\begin{lstlisting}
(check-expect (tree-member? 5 (tree-insert 5 tree4)) #t)
(check-expect (tree-member? 11 (tree-insert 11 tree4)) #t)
\end{lstlisting}
%
Später werden wir feststellen, dass \lstinline{tree-insert}
unterschiedliche sortierte Bäume liefern kann, die allesamt korrekt
sind.
%
\begin{aufgabeinline}
Die beiden Tests erwarten jeweils, dass \lstinline{#t} bei
\lstinline{tree-member?} herauskommt. Wäre es sinnvoll, auch noch
welche mit \lstinline{#f} zu schreiben?
\end{aufgabeinline}
%
Wenn Du ein mulmiges Gefühl bei den spärlichen beiden Tests hast:
richtig! In Kapitel~\ref{cha:properties} auf
Seite~\pageref{cha:properties} werden wir zeigen, wie man Funktionen
wie \lstinline{tree-insert} besser testet.
Aber jetzt geht's erst einmal mit der Funktion selbst los. Gerüst und
Schablone sind genau wie bei \lstinline{tree-member?}:
%
\begin{lstlisting}
(define tree-insert
(lambda (value tree)
(cond
((node? tree)
...
(tree-insert value (node-left-branch tree))
(tree-insert value (node-right-branch tree))
...)
(else ...))))
\end{lstlisting}
%
Die Fallunterscheidung bei Knoten ist ebenfalls wie in
\lstinline{tree-member?}, darum können wir auch die Verzweigung aus
der dortigen Schablone übernehmen:
%
\begin{lstlisting}
(define tree-insert
(lambda (value tree)
(cond
((node? tree)
...
(tree-insert value (node-left-branch tree))
(tree-insert value (node-right-branch tree))
...
(cond
((= value (node-label tree)) ...)
((< value (node-label tree)) ...)
(else ...)))
(else ...))))
\end{lstlisting}
%
Auch hier ist der erste Fal einfach: Wenn \lstinline{value} gerade die
Markierung eines Knotens ist, dann enthält der Baum den Wert bereits,
die Funktion muss nichts einfügen und kann einfach \lstinline{tree}
liefern. Auch für den Fall, dass \lstinline{tree} ein Blatt ist (das
letzte \lstinline{else}), ist es recht einfach: Wir konstruieren einen
neuen, einelementigen Baum:
%
\begin{lstlisting}
(define tree-insert
(lambda (value tree)
(cond
((node? tree)
...
(tree-insert value (node-left-branch tree))
(tree-insert value (node-right-branch tree))
...
(cond
((= value (node-label tree)) tree)
((< value (node-label tree)) ...)
(else ...)))
(else (make-node value #f #f)))))
\end{lstlisting}
%
Es bleiben noch zwei Fälle, in denen der einzufügende Wert links
beziehungsweise rechts von der Knotenmarkierung liegt. Er muss
entsprechend im linken oder rechten Teilbaum eingefügt werden: Genau
das erledigen die beiden rekursiven Aufrufe aus der Schablone. Der
jeweils andere Teilbaum bleibt so wie er ist:
%
\begin{lstlisting}
(define tree-insert
(lambda (value tree)
(cond
((node? tree)
(cond
((= value (node-label tree)) tree)
((< value (node-label tree))
(make-node (node-label tree)
(tree-insert value (node-left-branch tree))
(node-right-branch tree)))
(else
(make-node (node-label tree)
(node-left-branch tree)
(tree-insert value (node-right-branch tree))))))
(else
(make-node value #f #f)))))
\end{lstlisting}
%