Beruflich Dokumente
Kultur Dokumente
in die Programmierung
Andreas Hildebrandt
Motivation
Organisation der Daten im Speicher kann entscheidenden Ein uss auf Laufzeiten haben
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
Motivation
Eine offensichtlich wichtige Eigenschaft ist der Speicherbedarf einer Datenstruktur
Wahlfreier Zugriff
Iteration
...
Arrays
Wir kennen bereits dynamisch wachsende Arrays
Speichern Daten (in Python genauer: Objektreferenzen) hintereinander weg im Speicher (in
der Praxis etwas komplexer zur Unterstützung dynamischen Wachstums)
Eigenschaften:
Einfach verkettete Liste (linked list) ist einfache Datenstruktur, die diese Probleme
vermeidet...
...dafür aber lineare Laufzeit bei wahlfreiem Zugriff benötigt
leer oder
besteht aus Referenz auf Wert und Referenz auf eine weitere Liste
Rekursive De nition!
print(first.item)
to
Zum Zugriff auf Element i, beginne bei erstem Element (Kopf oder head der Liste) und folge i-
mal der next-Referenz
return element
for i in range(3):
print(list[i].item, end=" ")
to be or
return element
for i in range(3):
print(list[i].item, end=" ")
return element
i = list
while i != None:
print(i.item, end=" ")
i = i.next
to be or
Moral:
Müssen zum Löschen des ersten Elements aus l nur l durch l.next ersetzen
Offensichtlich in (1)
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)
neue Liste die mit neuem Element beginnt und auf alte Teilliste verweist oder
Idee:
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
⇒ LIFO
Operationen auf Stacks typischerweise push und pop genannt:
c.pop() liefert zuletzt hinzugefügtes Element aus Collection c zurück und entfernt
es danach aus c
push: neuen Knoten vorne an Liste anfügen und als neuen Kopf speichern
pop: Kopf aus der Liste entfernen und gespeichertes Element zurückliefern
def __init__(self):
self.head = None # Starte mit leerer Liste
def pop(self):
if self.head == None:
return None
else:
old_head = self.head
self.head = self.head.next
return old_head.item
stack = Stack()
stack.push(1)
stack.push(2)
stack.push(3)
Klassischer Algorithmus von Edsgar Dijkstra aus den 60ern nutzt zwei Stacks
Operandenstack
Operatorstack
1. Token ist Operand (keine Klammer, kein Operator) ⇒ push auf Operandenstack
letzte rechte Klammer erreicht ⇒ Operandenstack enthält einzigen Wert: Ergebnis des
Ausdrucks
nehmen zur Vereinfachung an, dass alle Token durch Leerzeichen getrennt sind
behandelt Operatoren
+ (binär)
- (binär)
* (binär)
sqrt (unär)
def parseExpression(expression):
operands = Stack()
operators = Stack()
9.0
( ( 1 + ( 2 * 3 ) ) + ( sqrt ( 4 ) ) )
( ( 1 ( 2 3 * ) + ) ( 4 sqrt ) + )
9.0
9.0
def parseExpression(expression):
operands = Stack()
9.0
⇒ FIFO
Operationen auf Schlangen typischerweise enqueue und dequeue genannt:
enqueue: neuen Knoten vorne an Liste anfügen und als neuen Kopf speichern
dequeue: Ende aus der Liste entfernen und gespeichertes Element zurückliefern
def __init__(self):
self._first = None # Starte mit leerer Liste
self._last = None
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)
Annahmen:
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)
In der Realität oft beobachtet: beide Zufallsvariablen sind exponentiell verteilt mit
Parametern und , d.h.
Warteschlangen im Webserver
...
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
Wie simulieren wir eine solche Queue (hier: nur M/M/1 betrachtet, Techniken bleiben aber
gleich)?
Idee:
Simulationsablauf:
ziehe eine erste Ankunftszeit und füge sie in die Queue ein
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
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)
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
Daten
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)
Binärbäume
Ein Binärbaum ist ein Spezialfall eines Baumes, bei dem jeder Knoten immer entweder 0, 1
oder 2 Kinder hat
Binärbäume
Wurzel
1 2
3 4 5
6 7 8 9 10
11 12 13 14
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
Binärbäume
Wurzel
1 2
3 4 5 6
7 8 9 10 11 12 13
Suchbäume
Binäre Bäume hervorragend geeignet, um Daten schnell durchsuchbar zu machen
Suchbäume
4 12
2 6 10 14
1 3 5 8 9 11 13
Suchbäume
Wie sucht man im binären Suchbaum?
Äquivalent zur binären Suche: gesucht ist Wert ; Starte mit = Wurzel
Suchbäume
Oft verwendet zur Implementierung von Dictionaries
def __init__(self):
self.root = None
bst = BinarySearchTree()
bst.insert(1, "eins")
bst.insert(2, "zwei")
bst.insert(3, "drei")
print(bst.find(2))
print(bst.find(4))
zwei
None