Operationen
Suchen (search)
function search(node, target):
if node == null:
return "nicht gefunden"
if target == node.value:
return "gefunden!"
if target < node.value:
return search(node.left, target) // nach links
else:
return search(node.right, target) // nach rechts
Bei jedem Schritt halbiert sich der Suchbereich. Höhe des Baumes = ca. log₂(n) → O(log n) im Idealfall.
Einfügen (insert)
Genau wie Suchen, aber wenn du am Ende ankommst (null), hängst du den neuen Knoten dort an.
function insert(node, value):
if node == null:
return new Node(value) // hier eingefügt
if value < node.value:
node.left = insert(node.left, value)
else:
node.right = insert(node.right, value)
return node
Beispiel: in den Baum oben 42 einfügen.
- Bei 50: 42 < 50 → links
- Bei 25: 42 > 25 → rechts
- Bei 35: 42 > 35 → rechts
- Bei null: hier ankommen → neuen Knoten 42 anhängen
Löschen (delete) im Detail
Löschen ist der trickreichste Fall und in jeder BST-Klausur ein Klassiker. Aufwand: O(h) mit h = Baumhöhe. Bei balanciertem Baum O(log n), bei entartetem Baum O(n). Es gibt drei Fälle:
Fall 1: Knoten ist Blatt (kein Kind). Einfach abhängen, Parent-Pointer auf null setzen.
50 50
/ \ → / \
25 75 25 75
/ /
10 (10 entfernt)
Fall 2: Knoten hat genau ein Kind. Das Kind rückt an die Stelle des gelöschten Knotens.
50 50
/ \ → / \
25 75 25 75
\ \
35 (25 entfernt, 35 rückt hoch)
→ 35 sitzt jetzt wo 25 war
Fall 3: Knoten hat zwei Kinder. Schwierigster Fall. Standardlösung: In-Order-Successor finden (der kleinste Knoten im rechten Teilbaum), seinen Wert in den zu löschenden Knoten kopieren, dann den Successor rekursiv löschen (der hat per Definition höchstens ein rechtes Kind, also Fall 1 oder 2).
50 60
/ \ → / \
25 75 25 75 ← 60 ist der In-Order-Successor von 50
/ \ \ (kleinster Wert im rechten Teilbaum von 50)
60 90 90
Pseudocode für alle drei Fälle:
function delete(node, value):
if node == null:
return null
if value < node.value:
node.left = delete(node.left, value)
elif value > node.value:
node.right = delete(node.right, value)
else:
# Knoten gefunden, drei Fälle
if node.left == null and node.right == null:
return null # Fall 1: Blatt
if node.left == null:
return node.right # Fall 2a: nur rechtes Kind
if node.right == null:
return node.left # Fall 2b: nur linkes Kind
# Fall 3: zwei Kinder
successor = findMin(node.right) # kleinster im rechten Teilbaum
node.value = successor.value
node.right = delete(node.right, successor.value)
return node
function findMin(node):
while node.left != null:
node = node.left
return node
Symmetrisch könnte man auch den In-Order-Vorgänger (größter Wert im linken Teilbaum) nehmen, beide Varianten sind korrekt. Achtung Klausur: bei Duplikaten im BST muss die Lösch-Logik konsistent zur Insert-Konvention sein (wir nehmen ≥ rechts, also stehen Duplikate rechts und Fall 3 trifft sie).
Reihenfolge der Inserts ist KRITISCH
Selbe Werte, andere Reihenfolge → komplett anderer Baum.
Inserts: 50, 25, 75, 10, 35, 60, 90 ← mittig zuerst
50
/ \
25 75
/ \ / \
10 35 60 90 ← schön ausgewogen, Höhe 3
Inserts: 10, 25, 50, 60, 75, 90 ← aufsteigend
10
\
25
\
50
\
60
\
75
\
90 ← entartet zur Linked-List, Höhe 6
Aufsteigende Inserts → Linked-List → O(n) statt O(log n). Genau hier liegt das Problem mit naivem BST.
In der Praxis nutzt man selbstbalancierende Bäume (AVL, Red-Black), die garantieren Höhe O(log n) automatisch. TreeMap und TreeSet in Java sind Red-Black-Trees.