-
Notifications
You must be signed in to change notification settings - Fork 0
/
_binary_search_tree.py
191 lines (152 loc) · 7.64 KB
/
_binary_search_tree.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
"""Implementation der generischen Klasse `BinarySearchTree[ComparableContentT]`."""
from __future__ import annotations
__all__: Final[list[str]] = ["BinarySearchTree"]
from typing import Final, Generic
from nrw.datastructures._comparable_content import ComparableContentT
from nrw.datastructures._utils import display_binary_node
class _BSTNode(Generic[ComparableContentT]):
"""Durch diese innere Klasse kann man dafür sorgen, dass ein leerer Baum
`None` ist, ein nicht-leerer Baum jedoch immer eine nicht-`None`-Wurzel sowie
nicht-`None`-Teilbäume hat.
"""
__slots__: Final[tuple[str, str, str]] = ("_content", "_left", "_right")
__hash__ = None # type: ignore[assignment]
def __init__(self, content: ComparableContentT) -> None:
self._content: ComparableContentT = content
self._left: BinarySearchTree[ComparableContentT] = BinarySearchTree()
self._right: BinarySearchTree[ComparableContentT] = BinarySearchTree()
def __repr__(self) -> str:
return (
f"{self.__class__.__name__}(content={self._content!r}, "
f"left_tree={self._left!r}, right_tree={self._right!r})"
)
def __str__(self) -> str:
return display_binary_node(self)
class BinarySearchTree(Generic[ComparableContentT]):
"""Mithilfe der generischen Klasse `BinarySearchTree` können beliebig viele
Objekte in einem Binaerbaum (binaerer Suchbaum) entsprechend einer
Ordnungsrelation verwaltet werden.
Ein Objekt der Klasse stellt entweder einen leeren binären Suchbaum dar oder
verwaltet ein Inhaltsobjekt sowie einen linken und einen rechten Teilbaum,
die ebenfalls Objekte der Klasse `BinarySearchTree` sind.
Die Klasse der Objekte, die in dem Suchbaum verwaltet werden sollen, muss
das Protocol `ComparableContent` implementieren. Dabei muss durch
Ueberschreiben der drei Vergleichsmethoden `__lt__`, `__eq__`, `__gt__` (s.
Dokumentation des Protocols) eine eindeutige Ordnungsrelation festgelegt
sein.
Alle Objekte im linken Teilbaum sind kleiner als das Inhaltsobjekt des
binären Suchbaums. Alle Objekte im rechten Teilbaum sind grösser als das
Inhaltsobjekt des binären Suchbaums. Diese Bedingung gilt (rekursiv) auch in
beiden Teilbäumen.
Hinweis: In dieser Version wird die Klasse `BinaryTree` nicht benutzt.
"""
__slots__: Final[tuple[str]] = ("_node",)
__hash__ = None # type: ignore[assignment]
def __init__(self) -> None:
"""Der Konstruktor erzeugt einen leeren Suchbaum."""
self._node: _BSTNode[ComparableContentT] | None = None
def __repr__(self) -> str:
return f"{self.__class__.__name__}(node={self._node!r})"
def __str__(self) -> str:
return str(self._node) if not self.is_empty else ""
@property
def is_empty(self) -> bool:
"""Diese Anfrage liefert den Wahrheitswert `True`, wenn der Suchbaum leer ist,
sonst liefert sie den Wert `False`.
"""
return self._node is None
@property
def content(self) -> ComparableContentT | None:
"""Diese Anfrage liefert das Inhaltsobjekt des Suchbaumes. Wenn der Suchbaum
leer ist, wird `None` zurückgegeben.
"""
return self._node._content if not self.is_empty else None
@property
def left_tree(self) -> BinarySearchTree[ComparableContentT] | None:
"""Diese Anfrage liefert den linken Teilbaum des binären Suchbaumes.
Wenn er leer ist, wird `None` zurückgegeben.
"""
return self._node._left if not self.is_empty else None
@property
def right_tree(self) -> BinarySearchTree[ComparableContentT] | None:
"""Diese Anfrage liefert den rechten Teilbaum des binären Suchbaumes.
Wenn er leer ist, wird `None` zurückgegeben.
"""
return self._node._right if not self.is_empty else None
def insert(self, content: ComparableContentT | None) -> None:
"""Falls der Parameter `None` ist, geschieht nichts.
Falls ein bezüglich des verwendeten Vergleichs `==` mit
`content` übereinstimmendes Objekt im geordneten binaeren Suchbau
enthalten ist, passiert nichts.
Achtung: hier wird davon ausgegangen, dass `==` genau dann `True`
liefert, wenn `<` und `>` `False` liefern.
Andernfalls (`<` order `>`) wird das Objekt `content` entsprechend
der vorgegebenen Ordnungsrelation in den `BinarySearchTree` eingeordnet.
"""
if content is None:
return
if self.is_empty:
self._node = _BSTNode(content)
elif content < self._node._content:
self._node._left.insert(content)
elif content > self._node._content:
self._node._right.insert(content)
def remove(self, content: ComparableContentT | None) -> None:
"""Falls ein bezüglich des verwendeten Vergleichs mit
`content` übereinstimmendes Objekt im binaeren Suchbaum enthalten
ist, wird dieses entfernt. Falls der Parameter `None` ist, ändert sich
nichts.
"""
if self.is_empty or content is None:
return
if content < self._node._content:
self._node._left.remove(content)
elif content > self._node._content:
self._node._right.remove(content)
elif self._node._left.is_empty and self._node._right.is_empty:
self._node = None
elif self._node._left.is_empty and not self._node._right.is_empty:
self._node = self._node_of_right_successor
elif not self._node._left.is_empty and self._node._right.is_empty:
self._node = self._node_of_left_successor
elif self._node_of_right_successor._left.is_empty:
self._node._content, self._node._right = (
self._node_of_right_successor._content,
self._node_of_right_successor._right,
)
else:
previous: BinarySearchTree[ComparableContentT] = (
self._node._right._ancestor_of_small_right()
)
smallest: BinarySearchTree[ComparableContentT] | None = previous._node._left
self._node._content = smallest._node._content
previous.remove(smallest._node._content)
def search(self, content: ComparableContentT | None) -> ComparableContentT | None:
"""Falls ein bezüglich des verwendeten Vergleichs `==` mit
`content` übereinstimmendes Objekt im binaeren Suchbaum enthalten ist,
liefert die Anfrage dieses, ansonsten wird `None` zurückgegeben.
Falls der Parameter `None` ist, wird `None` zurückgegeben.
"""
if self.is_empty or content is None:
return None
if content < self._node._content:
return self.left_tree.search(content)
if content > self._node._content:
return self.right_tree.search(content)
if content == self._node._content:
return self._node._content
return None # pragma: no cover
def _ancestor_of_small_right(self) -> BinarySearchTree[ComparableContentT]:
"""Die Methode liefert denjenigen Baum, dessen linker Nachfolger keinen linken
Nachfolger mehr hat. Es ist also später möglich, in einem Baum im
rechten Nachfolger den Vorgänger des linkesten Nachfolgers zu finden.
"""
if self._node_of_left_successor._left.is_empty:
return self
return self._node._left._ancestor_of_small_right()
@property
def _node_of_left_successor(self) -> _BSTNode[ComparableContentT] | None:
return self._node._left._node
@property
def _node_of_right_successor(self) -> _BSTNode[ComparableContentT] | None:
return self._node._right._node