• Keine Ergebnisse gefunden

Suchb¨ aume als verschachtelte Arrays

Im Dokument Programmiertechniken (NF) (Seite 25-29)

2. Suchb¨ aume 19

2.8. Suchb¨ aume als verschachtelte Arrays

Suchb¨aume k¨onnen in Python als verschachtelte Arrays dargestellt werden. Alternativ kann man sehr ¨ahnlich auch Tupel verwenden,w as wir hier aber nicht weiter betrachten.

In unserer Implementierung stellen wir jeden Knoten, welcher einen Wert enth¨alt, als Array der Gr¨oße drei dar. Also auch die Bl¨atter in unseren Suchb¨aumen, die eigentlich keine Kindknoten haben. Die Bl¨atter in unserer Implementierung sind also vielmehr leere Listen, welche noch unterhalb der Bl¨atter des Baumes verwendet werden. Hierdurch hat tats¨achlich jeder Knoten im Bin¨arbaum entweder zwei oder kein Kind.

Als Beispiel betrachten wir den folgenden Suchbaum:

5

2 10

Mit verschachtelten Listen w¨urde er dann wie folgt repr¨asentiert:

5

, ,

2

,

, ,

10

,

tr

Suchbaum als verschachtelte Liste

Um Bin¨arb¨aume in dieser Form konstruieren zu k¨onnen ist es sinnvoll zun¨achst einen Satz Hilfsfunktionen zu definieren, die uns helfen, solche B¨aume zu konstruieren.

d e f node ( l , v , r ) :

r e t u r n [ l , v , r ] d e f empty ( ) :

r e t u r n [ ]

Unter Verwendung dieser Funktionen k¨onnen wir obigen Suchbaum konstruieren mittels t = node ( node ( empty ( ) , 2 , empty ( ) ) , 5 , node ( empty ( ) , 1 0 , empty ( ) ) Da man mit diesen Funktionen also recht elegant Datenstrukturen konstruieren kann, nennt man solche Funktionen auchSmartkonstruktoren. Wie wir sp¨ater sehen werden, verstecken Sie auch gewissermaßen die Implementierung, was dann auch durch passende Selektoren zum Selektieren der Kindb¨aume bzw. des Wertes undTestfunktionen zum Unterscheiden der beiden Knotentypen, elegant erg¨anzt werden kann:

# S e l e k t o r e n d e f l e f t ( l ) :

r e t u r n l [ 0 ] d e f r i g h t ( l ) :

r e t u r n l [ 2 ] d e f v a l u e ( l ) :

r e t u r n l [ 1 ]

# T e s t f u n k t i o n

d e f i s e m p t y ( t r e e ) : r e t u r n t r e e == [ ]

Die Verwendung unserer Hilfsfunktionen erleichtert nun die Realisierung der Funktion zum effizienten Suchen eines Wertes im Suchbaum:

d e f e l e m ( v , t r e e ) : i f i s e m p t y ( t r e e ) :

r e t u r n F a l s e

e l i f v < v a l u e ( t r e e ) :

r e t u r n e l e m ( v , l e f t ( t r e e ) ) e l i f v > v a l u e ( t r e e ) :

r e t u r n e l e m ( v , r i g h t ( t r e e ) ) e l s e :

r e t u r n True # h i e r g i l t v == v a l u e ( t r e e )

Durch die Hilfsfunktionen sehen wir in der Implementierung der Funktion elem gar nicht mehr, wie genau wir den Suchbaum implementiert haben und produzieren sehr gut lesbaren Code.

Als n¨achsten Schritt wollen wir eine Funktion add definieren, welche einen Wert zu unse-rem Suchbaum hinzuf¨ugt. Wir beginnen mit dem Absteigen, analog zur Implementierung von elem. Wir haben ja zun¨achst angenommen, dass Werten nicht mehrfach in unseren

Suchb¨aumen vorkommen. Sollte der Wert, den wir einf¨ugen wollen bereits in dem Baum vorhanden sein, k¨onnen wir dies mit dem R¨uckgabewert False anzeigen. Waren wir erfolg-reich liefert unsere Funktion True:

d e f add ( v , t r e e ) :

i f i s e m p t y ( t r e e ) :

? ? ? # E i n f u e g e n d e s n e u e n K n o t e n s r e t u r n True

e l i f v < v a l u e ( t r e e ) :

r e t u r n add ( v , l e f t ( t r e e ) ) e l i f v > v a l u e ( t r e e ) :

r e t u r n add ( v , r i g h t ( t r e e ) ) e l s e :

r e t u r n F a l s e # h i e r g i l t v == v a l u e ( t r e e )

An der Stelle ??? ist uns nicht klar, wie wir den neuen Knoten node(empty(),v,empty()) einf¨ugen k¨onnen. Der ¨ubergeordnete Knoten verweist auf die leere Liste, an die die Variable tree nun gebunden ist. In unserer Implementierung m¨ussen wir diese Liste zu der Liste node(empty(),v,empty()) mutieren, damit der ¨ubergeordnete Knoten auf unseren neuen Knoten verweist.

Hierzu erweitern wir unsere Hilfsfunktionen um eine spezielle Mutationsfunktion d e f e m p t y t o v a l u e ( t r e e , v ) :

i f t r e e ==[] :

t r e e . e x t e n d ( [ empty ( ) , v , empty ( ) ] )

Hierbei ist die Funktion extend eine mutierende Methode der list Klasse, d.h. das vorhan-dene Listenobjekt bleibt erhalten, aber wird ver¨andert (mutiert). Mit dieser Funktion ist es dann m¨oglich unsere Definition fertig zu stellen:

d e f add ( v , t r e e ) :

i f i s e m p t y ( t r e e ) :

e m p t y t o v a l u e ( t r e e , v ) r e t u r n True

e l i f v < v a l u e ( t r e e ) :

r e t u r n add ( v , l e f t ( t r e e ) ) e l i f v > v a l u e ( t r e e ) :

r e t u r n add ( v , r i g h t ( t r e e ) ) e l s e :

r e t u r n F a l s e # h i e r g i l t v == v a l u e ( t r e e ) Dieser Einf¨ugeschritt s¨ahe dann f¨ur das Einf¨ugen des Wertes 7 wie folgt aus:

5

, ,

2

,

, ,

10

,

, ,

7

tr

Einf¨ugen in einen Suchbaum, kodiert als verschachtelte Liste

Bei allen Implementierungen (also auch mit Klassen oder Dictionaries, siehe ¨Ubung) ist es wichtig, dass das Einf¨ugen eines neuen Wertes unbedingt als Mutation der bereits vorhan-denen Objekte/Eintr¨age geschieht, da der neue Eintrag sonst nicht mit seinem Elternknoten verbunden werden kann. Wenn man neue Eintr¨age/Objekte verwenden will, muss man die Rekursion bereits eine Ebene h¨oher beenden und den linken/rechten Kindknoten ersetzen.

Dies ist aber in der Regel sehr viel aufwendiger, so dass es besser ist, leere Bl¨atter zu verwenden, welche man in nicht-leere Knoten mutieren kann.

Wir haben nun einige Funktionen definiert, welche ein potentieller Nutzer unserer klei-nen Bibliothek verwenden k¨onnte. Hierbei ist es aber nur sinnvoll, dass ein Benutzer die Funktionen elem und add verwendet. Außerdem k¨onnte er noch empty zur Konstruktion eines leeren Suchbaums verwenden. Von einer Verwendung der Funktion node sollte ein Benutzer aber absehen, da man mit ihr auch ung¨ultige Suchb¨aume konstruieren kann. Wir verwenden die Funktion node lediglich, innerhalb unserer eigenen Definition. Deshalb ist es sinnvoll eine klare Benutzerschnittstelle zur Verf¨ugung zu stellen und hierzu ein Synonym f¨ur die Funktion empty zu definieren, welche f¨ur den Benutzer unserer Bibliothek gedacht ist:

# K o n s t r u k t o r f u e r den B e n u t z e r d e f e m p t y s e a r c h t r e e ( ) :

r e t u r n empty ( )

Damit stehen dem Benutzer nun drei Funktionen empty search tree , elem und add zur Verf¨ugung. In den ¨Ubungen kommt noch delete hinzu.

Das Abstraktionskonzept hinter dieser Aufteilung werden wir sp¨ater noch bei der Betrach-tung abstrakter Datentypen vertiefen.

Zum Abschluss des Kapitels beginnen wir noch mit einer Implementierung der Suchb¨aume mit Klassen. Klassen geben uns die M¨oglichkeit Objekte mit Zustand zu definieren. Die einzelnen Komponenten unserer Listen (linkes Kind, Wert und rechtes Kind) werden dabei Attribute des zugeh¨origen Objekts der Klasse. Außerdem verwenden wir noch ein weiteres

boolesches Attribut empty, welche uns zus¨atzlich anzeigt, ob der jeweilige Knoten leer ist (entspricht empty) oder nicht (node).

c l a s s S e a r c h T r e e :

d e f i n i t ( s e l f ) : s e l f . empty = True s e l f . v a l u e = None s e l f . l e f t = None s e l f . r i g h t = None d e f e l e m ( s e l f , v a l u e ) :

. . .

d e f add ( s e l f , v a l u e ) : . . .

Der Konstruktor entspricht hier dem Anlegen eines leeren Suchbaums, also der Funktion empty. Die Attribute left und right werden dann sp¨ater auch andere Objekte der Klasse SearchTree enthalten, wodurch die Baumstruktur repr¨asentiert wird. Ver¨andert man die-se Attribute entspricht dies genau der Mutation der leeren Liste in eine nichtleere Liste mit Hilfe der Funktion empty to value. Die Methoden elem und add werden dann in den Ubungen realisiert.¨

Im Dokument Programmiertechniken (NF) (Seite 25-29)