Sie sind auf Seite 1von 52

Einführung

in die Programmierung
Andreas Hildebrandt

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Motivation
Organisation der Daten im Speicher kann entscheidenden Ein uss auf Laufzeiten haben

Verschiedene Datenstrukturen ordnen Daten unterschiedlich an

Führt zu unterschiedlichem Verhalten

Keine Datenstruktur ist für alle Zwecke optimal

Geeignete Struktur muss vom Entwickler anhand des aktuellen Problems ausgewählt
werden
Datenstrukturen oft als Container oder Collection interpretiert

Erlauben, ein oder mehrere Daten abzulegen und später wieder darauf zuzugreifen

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Motivation
Eine offensichtlich wichtige Eigenschaft ist der Speicherbedarf einer Datenstruktur

Daneben entscheidend ist die Unterstützung mehrerer Operationen (überhaupt möglich?


wie ef zient?)
Erzeugung

Zugriff auf erstes Element

Zugriff auf letztes Element

Wahlfreier Zugriff

Iteration

Hinzufügen am Anfang/Ende/beliebiger Stelle

Löschen am Anfang/Ende/beliebiger Stelle

...

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Arrays
Wir kennen bereits dynamisch wachsende Arrays

In Python als list bekannt

Speichern Daten (in Python genauer: Objektreferenzen) hintereinander weg im Speicher (in
der Praxis etwas komplexer zur Unterstützung dynamischen Wachstums)
Eigenschaften:

linearer Speicherbedarf mit kleiner Konstante

Zugriff auf beliebiges Element in (1)

Iteration über ganzes Array in ( ) mit kleiner Konstante


Hinzufügen und Löschen am Ende amortisiert in (1) , d.h., Hinzufügungen
verursachen Laufzeit in ( )

Hinzufügen und Löschen am Anfang in ( )

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Einfach verkettete Listen


Arrays für viele Anwendungen sehr gut geeignet

Konstanten in -Notation bei Zugriff und Iteration extrem gering

Amortisiert konstante Laufzeit für insert/delete am Ende ist i.A. ok...

...kann aber problematisch werden, da einzelne inserts/deletes deutlich längere Laufzeiten


haben können als andere
Lineare Laufzeit beim Hinzufügen am Anfang (oder in der Mitte...) ist oft ein Problem!

Einfach verkettete Liste (linked list) ist einfache Datenstruktur, die diese Probleme
vermeidet...
...dafür aber lineare Laufzeit bei wahlfreiem Zugriff benötigt

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Einfach verkettete Listen


Idee hinter linked list: Liste ist entweder

leer oder

besteht aus Referenz auf Wert und Referenz auf eine weitere Liste

Rekursive De nition!

Einzelne Listenelemente bezeichnen wir als Knoten (nodes) der Liste

Liste endet bei erstem leeren Knoten

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Einfach verkettete Listen


Umsetzung in Python:

In [16]: class Node:


"""Einfach verkettete Liste"""
def __init__(self, item, next):
self.item = item
self.next = next

# Verwendung (Beispiel aus Sedgewick et al., Introduction to Programming in Python)


third = Node('or', None) # Ende der Liste, zeigt auf keine weitere Teilliste
second = Node('be', third) # Zweites Element, zeigt auf Liste, die mit letztem Element beginnt
first = Node('to', second) # Anfang der Liste, zeigt auf Liste, die mit zweitem Element beginnt

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Einfach verkettete Listen


Zugriff auf erstes Element ist einfach - Referenz auf Liste entspricht Referenz auf erstes
Element

In [17]: class Node:


"""Einfach verkettete Liste"""
def __init__(self, item, next):
self.item = item
self.next = next

# Verwendung (Beispiel aus Sedgewick et al., Introduction to Programming in Python)


third = Node('or', None) # Ende der Liste, zeigt auf keine weitere Teilliste
second = Node('be', third) # Zweites Element, zeigt auf Liste, die mit letztem Element beginnt
first = Node('to', second) # Anfang der Liste, zeigt auf Liste, die mit zweitem Element beginnt

print(first.item)

to

Aber wie kommen wir allgemein an i-tes Element der Liste?

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Einfach verkettete Listen


Wahlfreier Zugriff in der einfach verketteten Liste durch Iteration

Zum Zugriff auf Element i, beginne bei erstem Element (Kopf oder head der Liste) und folge i-
mal der next-Referenz

Laufzeit in ( ), also allgemein in ( )

In [18]: class Node:


"""Einfach verkettete Liste"""
def __init__(self, item, next):
self.item = item
self.next = next
def __getitem__(self, index):
element = self
while index > 0:
element = element.next
if element == None:
raise IndexError("linked list index out of range")
index -= 1

return element

list = Node('to', Node('be', Node('or', None)))

for i in range(3):
print(list[i].item, end=" ")

to be or

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Einfach verkettete Listen


class Node:
"""Einfach verkettete Liste"""
def __init__(self, item, next):
self.item = item
self.next = next
def __getitem__(self, index):
element = self
while index > 0:
element = element.next
if element == None:
raise IndexError("linked list index out of range")
index -= 1

return element

list = Node('to', Node('be', Node('or', None)))

for i in range(3):
print(list[i].item, end=" ")

Was ist die Laufzeit der Schleife in diesem Beispiel?

Zugriff in Durchlauf i benötigt  ⇒ Gesamtlaufzeit in


( −1)
(( =1 ) = ( 2
) = ( 2 )
Quadratische Laufzeit!

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Einfach verkettete Listen


Iteration über einfach verkettete Liste aber erheblich ef zienter möglich

In [19]: class Node:


"""Einfach verkettete Liste"""
def __init__(self, item, next):
self.item = item
self.next = next
def __getitem__(self, index):
element = self
while index > 0:
element = element.next
if element == None:
raise IndexError("linked list index out of range")
index -= 1

return element

list = Node('to', Node('be', Node('or', None)))

i = list
while i != None:
print(i.item, end=" ")
i = i.next

to be or

Jeder Schritt in (1) ⇒ Gesamtkosten von ( )

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Einfach verkettete Listen


Naive Umsetzung der Iteration führt zu quadratischer Laufzeit

Clevere Umsetzung führt zu linearer Laufzeit

Moral:

Aufpassen bei Verwendung von Datenstrukturen!

Verwendung muss Eigenheiten der Datenstruktur berücksichtigen

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Einfach verkettete Listen


Löschen des ersten Elements ähnlich einfach wie Hinzufügen

Müssen zum Löschen des ersten Elements aus l nur l durch l.next ersetzen

Offensichtlich in (1)

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Einfach verkettete Listen


Können wir auch an beliebiger Stelle hinzufügen oder löschen?

Wenn wir eine Referenz auf das Element haben, hinter dem eingefügt oder hinter dem
gelöscht werden soll, sind Löschen und Hinzufügen einfach und in (1)

Idee: ersetze Liste hinter dem gespeicherten Element durch

neue Liste die mit neuem Element beginnt und auf alte Teilliste verweist oder

neue Liste die mit Nachfolger des zu Löschenden Elements beginnt

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Doppelt verkettete Listen


In einfach verketteter Liste können wir uns in eine Richtung schnell bewegen, indem wir next-
Referenzen folgen
Oft wollen wir aber in beide Richtungen iterieren

Ef zient möglich in doppelt verketteter Liste

Idee:

jedes Element speichert Wert, Vorgänger (prev) und Nachfolger (next)

Liste repräsentiert durch erstes und/oder letztes Element

Erlaubt Hinzufügen/Löschen auch am Ende in (1)

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Stacks und Queues


Häu g benötigt: Datenstruktur, die ef zientes Einfügen und Entnehmen jeweils eines
Elements ermöglicht
Charakterisiert durch Verhalten beim Entnehmen: welches Element wird entnommen?

Wichtige Varianten:

Last In, First Out (LIFO) - zuletzt hinzugefügtes Element wird als erstes
entnommen
First In, First Out (FIFO) - zuerst hinzugefügtes Element wird als erstes
entnommen

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Stacks und Queues


Ein Stack (Stapel) ist eine LIFO-Datenstruktur, bei der Einfügen und Entnehmen in (1)
möglich sind
Entspricht einem Papierstapel auf einem Schreibtisch:

Dokument hinzufügen ⇒ Ablage ganz oben auf dem Stapel

Dokument entnehmen ⇒ oberstes Element aus Stapel entfernen

⇒ LIFO
Operationen auf Stacks typischerweise push und pop genannt:

c.push(e) fügt Element e in Collection c ein

c.pop() liefert zuletzt hinzugefügtes Element aus Collection c zurück und entfernt
es danach aus c

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Stacks und Queues


Stack kann mit verketteter Liste leicht implementiert werden:

Leerer Stack entspricht leerer Liste

push: neuen Knoten vorne an Liste anfügen und als neuen Kopf speichern

pop: Kopf aus der Liste entfernen und gespeichertes Element zurückliefern

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Stacks und Queues


In [20]: class Stack:
# Unterstrich deutet an, dass _Node nur von Klasse Stack selbst verwendet werden soll
class _Node:
def __init__(self, item, next):
self.item = item
self.next = next

def __init__(self):
self.head = None # Starte mit leerer Liste

def push(self, item):


self.head = self._Node(item, self.head)

def pop(self):
if self.head == None:
return None
else:
old_head = self.head
self.head = self.head.next
return old_head.item

def empty(self): # nützlich für Schleifen


return self.head == None

stack = Stack()
stack.push(1)
stack.push(2)
stack.push(3)

while not stack.empty():


print(stack.pop())

Johannes Gutenberg-Universität Mainz


3
Elementare
Datenstrukturen

Stacks und Queues


Beispielanwendung für Stack: vollständig geklammerte arithmetische Ausdrücke

Python verarbeitet Ausdrücke der Art ((1 + 2) * (3 + 4))

Wie programmiert man sowas?

Klassischer Algorithmus von Edsgar Dijkstra aus den 60ern nutzt zwei Stacks

Operandenstack

Operatorstack

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Stacks und Queues


Ablauf: Lese einzelnes Token ein; es tritt einer von vier Fällen auf:

1. Token ist Operand (keine Klammer, kein Operator) ⇒ push auf Operandenstack

2. Token ist Operator ⇒ push auf Operatorstack

3. Token ist linke Klammer ⇒ tue nichts

4. Token ist rechte Klammer:

entnehme letzten Operator von Operatorstack

entnehme benötigte Anzahl Argumente vom Operandenstack

Werte Operator aus

pushe Ergebnis auf Operandenstack

letzte rechte Klammer erreicht ⇒ Operandenstack enthält einzigen Wert: Ergebnis des
Ausdrucks

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Stacks und Queues


Beispiel: (Sedgewick et al., Introduction to Programming in Python)

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Stacks und Queues


Beispielimplementierung:

nehmen zur Vereinfachung an, dass alle Token durch Leerzeichen getrennt sind

verwendet Stack-Implementierung von oben

behandelt Operatoren

+ (binär)

- (binär)

* (binär)

sqrt (unär)

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Stacks und Queues


In [21]: import math

def parseExpression(expression):
operands = Stack()
operators = Stack()

for token in expression.split():


if token == '+': operators.push(token)
elif token == '-': operators.push(token)
elif token == '*': operators.push(token)
elif token == 'sqrt': operators.push(token)
elif token == ')':
op = operators.pop()
value = operands.pop()
if op == '+': value = operands.pop() + value
elif op == '-': value = operands.pop() - value
elif op == '*': value = operands.pop() * value
elif op == 'sqrt': value = math.sqrt(value)
operands.push(value)
elif token != '(':
operands.push(float(token))
return operands.pop()

print(parseExpression("( ( 1 + ( 2 * 3 ) ) + ( sqrt ( 4 ) ) )"))

9.0

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Stacks und Queues


Beobachtung: in Dijkstras Algorithmus ist Position des Operators innerhalb der Klammer
unerheblich!
Wir können Operator also auch immer hinter seine Operanden schreiben, statt zwischen sie

Bekannt als post x - Notation (bisher: in x - Notation)

Beispiel: die folgenden Ausdrücke berechnen das gleiche Ergebnis:

( ( 1 + ( 2 * 3 ) ) + ( sqrt ( 4 ) ) )

( ( 1 ( 2 3 * ) + ) ( 4 sqrt ) + )

In [22]: print(parseExpression("( ( 1 + ( 2 * 3 ) ) + sqrt ( 4 ) )"))


print(parseExpression("( ( 1 ( 2 3 * ) + ) ( 4 sqrt ) + )"))

9.0
9.0

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Stacks und Queues


In Post x - Notation folgen rechte Klammern immer auf Operatoren

Tragen also keine zusätzliche Information, können weggelassen werden

Bekannt als reverse Polish notation (RPN)

Oft auch post x als Synonym für RPN gebraucht

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Stacks und Queues


Sobald Operator in RPN auftaucht, sind seine Operanden schon auf dem Stack

Können dann direkt Operanden pushen und anwenden

Benötigen dann gar keinen Operator-Stack mehr

Stark vereinfachte Implementierung

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Stacks und Queues


Beispiel: (Sedgewick et al., Introduction to Programming in Python)

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Stacks und Queues


Beispielimplementierung:

In [23]: import math

def parseExpression(expression):
operands = Stack()

operators = ['+', '-', '*', 'sqrt']

for token in expression.split():


if token in operators:
value = operands.pop()
if token == '+': value = operands.pop() + value
elif token == '-': value = operands.pop() - value
elif token == '*': value = operands.pop() * value
elif token == 'sqrt': value = math.sqrt(value)
operands.push(value)
else:
operands.push(float(token))
return operands.pop()

print(parseExpression("1 2 3 * + 4 sqrt +"))

9.0

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Stacks und Queues


Weitere Anwendung von Stacks: Funktionsaufrufe in Programmiersprachen

Funktion hat Parameter, lokale Variablen, Rückgabewert, Rücksprungpunkt ⇒ werden auf


Stack gepusht
Neuer Funktionsaufruf: neue Variablen, ..., landen auf Stack

Rücksprung: lokale Variablen, ... vom Stack entnommen, Rücksprungpunkt entnommen,


Rückgabewert gepusht

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Stacks und Queues

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Stacks und Queues


Eine Queue (Warteschlange) ist eine FIFO-Datenstruktur, bei der Einfügen und Entnehmen
in (1) möglich sind

Entspricht z.B. einer Schlange im Supermarkt

Person stellt sich an ⇒ Ablage vorne in der Schlange

Person entnehmen ⇒ hinterstes Element aus Schlange entfernen

⇒ FIFO
Operationen auf Schlangen typischerweise enqueue und dequeue genannt:

c.enqueue(e) fügt Element e in Collection c ein

c.dequeue() liefert zuerst hinzugefügtes Element aus Collection c zurück und


entfernt es danach aus c

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Stacks und Queues


Queue kann mit verketteter Liste leicht implementiert werden, wenn wir uns Anfang und
Ende merken
Bekannt als deque - double ended queue

Leere Queue entspricht leerer Liste

enqueue: neuen Knoten vorne an Liste anfügen und als neuen Kopf speichern

dequeue: Ende aus der Liste entfernen und gespeichertes Element zurückliefern

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Stacks und Queues

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

In [24]: class Queue:


# Unterstrich deutet an, dass _Node nur von Klasse Queue selbst verwendet werden soll
class _Node:
def __init__(self, item, next):
self.item = item
self.next = next

def __init__(self):
self._first = None # Starte mit leerer Liste
self._last = None

def empty(self): # nützlich für Schleifen


return self._first is None

def enqueue(self, item):


old_last = self._last
self._last = self._Node(item, None)
if self.empty():
self._first = self._last
else:
old_last.next = self._last

def dequeue(self):
item = self._first.item
self._first = self._first.next
if self.empty():
self._last = None
return item

queue = Queue()
for i in range(3):
queue.enqueue(i+1)

while not queue.empty():


print(queue.dequeue())

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Stacks und Queues


Beispielanwendung einer Queue: Simulation einer Supermarktwarteschlange

Annahmen:

es gibt genau eine Kasse

Kunden stellen sich hinten an und warten bis sie drankommen

der vorderste Kunde geht an die Kasse, wird dort bedient und verlässt danach die
Schlange
dann rückt der nächste Kunde nach (falls es noch einen gibt)

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Stacks und Queues


Beispielanwendung einer Queue: Simulation einer Supermarktwarteschlange

Brauchen zur Simulation zwei Wahrscheinlichkeitsverteilungen:

Zeitintervall bis der nächste Kunde in die Schlange kommt

Wartezeit an der Kasse ("Bearbeitungszeit")

In der Realität oft beobachtet: beide Zufallsvariablen sind exponentiell verteilt mit
Parametern und , d.h.

Wahrscheinlichkeit, dass der nächste Kunde irgendwann in den nächsten t Minuten


eintritt, beträgt 1 − −
Wahrscheinlichkeit, dass ein Kunde an der Kasse innerhalb von t Minuten fertig
wird, beträgt 1 − −
Bekannt als M/M/1-Queue

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Stacks und Queues


Beispielanwendung einer Queue: Simulation einer Supermarktwarteschlange

Generalisierung mit mehr als einem Kassierer (Server): M/M/c-Queue

Sehr beliebtes Modell in der Informatik; modelliert neben Supermarkt z.B.:

Warteschlangen im Webserver

Wartezeiten auf Ressourcen in Parallelrechnern

...

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Stacks und Queues


Beispielanwendung einer Queue: Simulation einer Supermarktwarteschlange

Simulation solcher Queues erlaubt

Studium der Wartezeiten bei gegebenen Verhältnissen (wie lange werde ich im
Supermarkt brauchen?)
Optimierung der Eigenschaften (sollte ich einen Server schneller machen oder
mehrere einsetzen?)
...

Für M/M/1 noch viel analytisch möglich; komplexere Modelle benötigen Simulation am
Rechner

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Stacks und Queues


Beispielanwendung einer Queue: Simulation einer Supermarktwarteschlange

Wie simulieren wir eine solche Queue (hier: nur M/M/1 betrachtet, Techniken bleiben aber
gleich)?
Idee:

verwende Queue zur Simulation der Schlange

generiere zufällige Ankunftszeiten, die exponentiell verteilt sind

generiere zufällige Bearbeitungszeiten, die exponentiell verteilt sind

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Stacks und Queues


Beispielanwendung einer Queue: Simulation einer Supermarktwarteschlange

Simulationsablauf:

ziehe eine erste Ankunftszeit und füge sie in die Queue ein

ziehe eine Bearbeitungszeit für den ersten Kunden

ziehe so lange neue Ankunftszeiten und füge sie ein, bis die Bearbeitungszeit
erreicht ist
entferne vordersten Kunden aus der Schlange und ziehe neue Bearbeitungszeit

iteriere, bis gewünschte Abbruchbedingung erfüllt ist

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

In [31]: import random


import matplotlib.pyplot

l = 0.167
mu = 0.25
num_bins = 60
queue = Queue()

nextArrival = random.expovariate(l)
nextService = nextArrival + random.expovariate(mu)
data = []
for i in range(5000):
while nextArrival < nextService:
queue.enqueue(nextArrival)
nextArrival += random.expovariate(l)
arrival = queue.dequeue()
wait = nextService - arrival
data.append(min(num_bins, int(round(wait))))
if queue.empty(): nextService = nextArrival + random.expovariate(mu)
else: nextService = nextService + random.expovariate(mu)

n, bins, patches = matplotlib.pyplot.hist(data, 60, normed=1)


matplotlib.pyplot.show()

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Bäume
Bäume sind eine in der Informatik sehr wichtige Datenstruktur

Bäume sind - ähnlich wie Listen - rekursiv de niert: ein Baum ist entweder

Leer (None) oder

Ein Knoten, bestehend aus

Daten

einer Menge von Kindknoten

Enthält Knoten Knoten als Kind, nennen wir den Vater von

Genau ein Knoten hat keinen Vater; diesen Knoten nennen wir die Wurzel (Root) des
Baumes
Knoten ohne Kinder nennen wir Blätter (leaves) des Baumes

Die Anzahl der Ebenen im Baum unter der Wurzel nennen wir seine Höhe (height)

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Binärbäume
Ein Binärbaum ist ein Spezialfall eines Baumes, bei dem jeder Knoten immer entweder 0, 1
oder 2 Kinder hat

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Binärbäume

Wurzel

1 2

3 4 5

6 7 8 9 10

11 12 13 14

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Binärbäume
Oft nützlich: Bäume mit kleiner Höhe

De nition: Ein Baum heißt balanciert, falls sich für jeden Knoten die Höhen der durch seine
Kinder aufgespannten Teilbäume um maximal 1 unterscheidet
Intuitiv: balancierter Baum geht in die Breite statt in die Tiefe

Für gegebene Knotenanzahl n hat balancierter Baum minimale Höhe

Balancierter Binärbaum mit n Knoten: Höhe in ( ( ))

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Binärbäume

Wurzel

1 2

3 4 5 6

7 8 9 10 11 12 13

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Suchbäume
Binäre Bäume hervorragend geeignet, um Daten schnell durchsuchbar zu machen

Für einen Knoten sei ( ) der darin gespeicherte Wert


Idee: speichere Daten im Baum so, dass für jeden Knoten gilt:

falls linkes Kind besitzt, so ist ()≤ ( )


falls rechtes Kind besitzt, so ist ( )< ( )
(der in einem Knoten gespeicherte Wert ist größer oder gleich dem Wert des linken Kinds
und kleiner dem des rechten Kinds)
Ein Baum mit dieser Eigenschaft heißt binärer Suchbaum (binary search tree)

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Suchbäume

4 12

2 6 10 14

1 3 5 8 9 11 13

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Suchbäume
Wie sucht man im binären Suchbaum?

Äquivalent zur binären Suche: gesucht ist Wert ; Starte mit = Wurzel

Falls ( ) = ⇒ gesuchtes Element gefunden


Falls ( ) ≥ ⇒ setze auf das linke Kind und suche weiter (kein linkes Kind?
Element nicht enthalten!)
Falls ( ) < ⇒ setze auf das rechte Kind und suche weiter (kein rechtes Kind?
Element nicht enthalten!)
Jede Iteration steigt eine Ebene im Baum ab ⇒ Laufzeit im worst-case in ( Höhe)

balancierter binärer Suchbaum: Laufzeit im worst-case in (log( ))


Suchbaum balanciert halten ist nicht einfach, aber möglich; wird in DSEA im Detail
besprochen

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

Suchbäume
Oft verwendet zur Implementierung von Dictionaries

Knoten speichern Schlüssel und Werte

Suchbaumeigenschaft für die Schlüssel

Johannes Gutenberg-Universität Mainz


Elementare
Datenstrukturen

In [26]: class BinarySearchTree: # Achtung: resultierender Baum i.A. **nicht** balanciert


class _Node:
def __init__(self, key, value):
self.key = key
self.value = value
self.left_child = None
self.right_child = None
def insert(self, key, value):
if key <= self.key:
if self.left_child == None: self.left_child = BinarySearchTree._Node(key, value)
else: self.left_child.insert(key, value)
else:
if self.right_child == None: self.right_child = BinarySearchTree._Node(key, value)
else: self.right_child.insert(key, value)
def find(self, key):
if self.key == key: return self.value
elif self.left_child != None: return self.left_child.find(key)
elif self.right_child != None: return self.right_child.find(key)
else: return None

def __init__(self):
self.root = None

def insert(self, key, value):


if self.root == None: self.root = self._Node(key, value)
else: self.root.insert(key, value)

def find(self, key):


if self.root == None: return None
else: return self.root.find(key)

bst = BinarySearchTree()
bst.insert(1, "eins")
bst.insert(2, "zwei")
bst.insert(3, "drei")

print(bst.find(2))
print(bst.find(4))

zwei
None

Johannes Gutenberg-Universität Mainz