Sequentielle Suche
Einfache Methode eine Symboltabelle (key value storage) basierend auf einer einfach verketteten Liste zu implementieren
put(k, v): Beginnend mit dem Listenkopf werden die Schlüssel verglichen und der Wert verändert, ggf. wird neuer Listenkopf erstelltget(k): Beginnend mit dem Listenkopf werden die Schlüssel verglichen
Worst-Case-Laufzeit für Suche / Einfügen / Löschen:
Average-Case-Laufzeit bei Suche und Löschen sogar:
Binäre Suche
Idee: Halbierung des Suchraums bei sortierter Liste möglich
Datenstruktur: Sortiertes Array (Zugriff ) von Schlüssel und Werten
Hilfsfunktion: rank(k) gibt zurück, wie viele Schlüssel kleiner als sind (also den Index von )
- findet beim Einfügen die korrekte Position für das neue Element Elemente dahinter müssen zunächst verschoben werden
int rank(const Key& key) {
int lo = 0, hi = n-1;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
if (key < keys[mid])
hi = mid - 1;
else if (key > keys[mid])
lo = mid + 1;
else
return mid;
}
return lo;
}Traversierung von Bäumen
Tiefensuche (depth first traversal)
- Pre-Order: 1. Knoten, 2. linker Teilbaum, 3. rechter Teilbaum
- In-Order: 1. linker Teilbaum, 2. Knoten, 3. rechter Teilbaum
- Post-Order: 1. linker Teilbaum, 2. rechter Teilbaum, 3. Knoten
- Die Tiefensuche markiert alle Knoten, die mit einem gegebenen Startknoten verbunden sind
- Die Tiefensuche berechnet einen Pfad von einem gegebenen Startknoten zu jedem verbundenen Knoten in einer Zeit, die proportional zur Länge des Pfades ist (effizient)
- ermittelt, ob es einen Pfad zwischen zwei Knoten gibt
- Praktische Anwendung: Zusammenhängende Flächen auf digitalen Fotos füllen (Pixel als Knoten und Kanten genau dann wenn benachbarte Pixel eine ähnliche Intensität haben)
Implementierung
markedmarkiert bereits besuchte Knotenedge_to[w] == vbedeutet, dass die Kante nachwvonvkommt
void dfs(const Graph& g, const int v) {
marked[v] = true;
for (auto w : g.adj(v)) {
if (!marked[w]) {
dfs(g, w);
edge_to[w] = v; // edge_to exists outside this function
}
}
}Breitensuche (breadth first traversal)
- wird mithilfe einer Queue implementiert
- berechnet von einem gegebenen Startknoten den kürzesten Pfad zu einem gegebenen Zielknoten in einem ungewichteten Graphen
- selber Algorithmus wie Tiefensuche, nur mit Queue statt Stack für zu besuchende Knoten
Implementierung
markedmarkiert schon besuchte Knotenedge_to[w] == vbedeutet, dass die Kante nach von kommt (auf dem kürzesten Weg zu )dist_tospeichert, wie lang der kürzeste Pfad zum Startknoten ist
- Füge den Startknoten in eine Warteschlange ein und markiere ihn
- Solange die Warteschlange nicht leer ist
- Entferne den Kopf der Warteschlange
- Überprüfe alle Nachbarn, ob sie schon markiert sind. Wenn nicht, markiere den Nachbarn und füge ihn zur Warteschlange hinzu
void bfs(const Graph& g, const int s) {
Queue<int> q;
// initialize distances and visitations
for (auto v = 0; v < no_vertices; v++) {
dist_to[v] = std::numeric_limits<int>::max();
edge_to[v] = -1;
marked[v] = false;
}
dist_to[s] = 0;
marked[s] = true;
q.enqueue(s);
// run BFS by processing the queue
while(!q.is_empty()) {
int v = q.dequeue();
for (auto w : g.adj(v)) {
if (!marked[w]) {
edge_to[w] = v;
dist_to[w] = dist_to[v] + 1;
marked[w] = true;
q.enqueue(w);
}
}
}
return;
}Binärer Suchbaum
- Ein binärer Suchbaum (binary search tree) ist ein geordneter binärer Baum
- Jeder Knoten hat einen Schlüssel. In jedem Knoten gilt für den Schlüssel:
- größer als alle Schlüssel im linken Teilbaum
- kleiner als alle Schlüssel im rechten Teilbaum
- Für die gleichen Schlüssel, gibt es viele BSTs (mehr oder weniger balanciert)
- Optimalerweise ist der Baum balanciert (initial und als Reaktion auf Änderungen)
get nutzt die Ordnung des Baumes
- Vergleiche: Tiefes des Baums + 1
Einfügen
put nutzt erneut die Ordnung des Baumes (gleich zur Suche) und fügt dann ein
- Suche, die an einem
nullptrendet - Den neuen Knoten anfügen
- Rekursiv die Größe der darüber liegenden Teilbäume updaten
- Vergleiche: Tiefe des Baums + 1
- ggf. geht Balance des Baumes verloren
Satz von Reed: Wenn Schlüssel in zufälliger Reihenfolge in einen binären Suchbaum eingefügt werden, dann ist die erwartete Anzahl an Vergleichen für Suche / Einfügen und der Baum hat eine erwartete Tiefe von .
Entfernen (Hibbard Deletion)
Fall 1 (keine Kinder): Schlicht Knoten löschen
Fall 2 (ein Kind): Knoten löschen und Kind stattdessen anhängen
Fall 3 (zwei Kinder): min im rechten Teilbaum finden und entfernen (remove_min) und damit den zu entfernenden Knoten ersetzen
- der neue Knoten ist größer als alle im linken Teilbaum (da er aus dem rechten kommt), aber kleiner als alle im rechten Teilbaum (da er dort das Minimum ist)
- Nach links gehen, bis ein
nullptr - Pointern auf Minimum nun auf den rechten unteren Teilbaum des Minimums zeigen lassen
Satz: Durchschnittliche Anzahl an Schritten zum Löschen: