bgneal@18
|
1 """
|
bgneal@15
|
2 Copyright (C) 2012 Brian G. Neal.
|
bgneal@15
|
3
|
bgneal@15
|
4 Permission is hereby granted, free of charge, to any person obtaining a copy of
|
bgneal@15
|
5 this software and associated documentation files (the "Software"), to deal in
|
bgneal@15
|
6 the Software without restriction, including without limitation the rights to
|
bgneal@15
|
7 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
bgneal@15
|
8 the Software, and to permit persons to whom the Software is furnished to do so,
|
bgneal@15
|
9 subject to the following conditions:
|
bgneal@15
|
10
|
bgneal@15
|
11 The above copyright notice and this permission notice shall be included in all
|
bgneal@15
|
12 copies or substantial portions of the Software.
|
bgneal@15
|
13
|
bgneal@15
|
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
bgneal@15
|
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
bgneal@15
|
16 FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
bgneal@15
|
17 COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
bgneal@15
|
18 IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
bgneal@15
|
19 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
bgneal@15
|
20
|
bgneal@18
|
21 ----
|
bgneal@18
|
22
|
bgneal@18
|
23 A red-black tree for Section 3.4, Exercise 4 in Allen Downey's _Think
|
bgneal@18
|
24 Complexity_ book.
|
bgneal@18
|
25
|
bgneal@18
|
26 http://greenteapress.com/complexity
|
bgneal@18
|
27
|
bgneal@18
|
28 Red black trees are described on Wikipedia:
|
bgneal@18
|
29 http://en.wikipedia.org/wiki/Red-black_tree.
|
bgneal@18
|
30
|
bgneal@18
|
31 This is basically a Python implementation of Julienne Walker's Red Black Trees
|
bgneal@18
|
32 tutorial found at:
|
bgneal@18
|
33 http://www.eternallyconfuzzled.com/tuts/datastructures/jsw_tut_rbtree.aspx
|
bgneal@18
|
34 We implement Julienne's top-down insertion and deletion algorithms here.
|
bgneal@18
|
35
|
bgneal@18
|
36 Some ideas were also borrowed from code by Darren Hart at
|
bgneal@18
|
37 http://dvhart.com/darren/files/rbtree.py
|
bgneal@18
|
38
|
bgneal@15
|
39 """
|
bgneal@15
|
40
|
bgneal@16
|
41 BLACK, RED = range(2)
|
bgneal@18
|
42 LEFT, RIGHT = range(2)
|
bgneal@18
|
43
|
bgneal@18
|
44
|
bgneal@18
|
45 class TreeError(Exception):
|
bgneal@18
|
46 """Base exception class for red-black tree errors."""
|
bgneal@18
|
47 pass
|
bgneal@18
|
48
|
bgneal@15
|
49
|
bgneal@15
|
50 class Node(object):
|
bgneal@15
|
51 """A node class for red-black trees.
|
bgneal@15
|
52
|
bgneal@18
|
53 * A node has a color, either RED or BLACK.
|
bgneal@18
|
54 * A node has a key and an optional value.
|
bgneal@15
|
55
|
bgneal@18
|
56 * The key is used to order the red-black tree by calling the "<"
|
bgneal@18
|
57 operator when comparing two nodes.
|
bgneal@18
|
58 * The value is useful for using the red-black tree to implement a map
|
bgneal@18
|
59 datastructure.
|
bgneal@18
|
60
|
bgneal@18
|
61 * Nodes have exactly 2 link fields which we represent as a list of
|
bgneal@18
|
62 2 elements to represent the left and right children of the node. This list
|
bgneal@18
|
63 representation was borrowed from Julienne Walker as it simplifies some of
|
bgneal@18
|
64 the code. Element 0 is the LEFT child and element 1 is the RIGHT child.
|
bgneal@15
|
65
|
bgneal@15
|
66 """
|
bgneal@15
|
67
|
bgneal@18
|
68 def __init__(self, key, value=None, color=RED):
|
bgneal@15
|
69 self.key = key
|
bgneal@15
|
70 self.value = value
|
bgneal@15
|
71 self.color = color
|
bgneal@18
|
72 self.link = [None, None]
|
bgneal@17
|
73
|
bgneal@19
|
74 def free(self):
|
bgneal@19
|
75 """Call this function when removing a node from a tree.
|
bgneal@19
|
76
|
bgneal@19
|
77 It updates the links to encourage garbage collection.
|
bgneal@19
|
78
|
bgneal@19
|
79 """
|
bgneal@19
|
80 self.link[0] = self.link[1] = None
|
bgneal@19
|
81
|
bgneal@17
|
82 def __str__(self):
|
bgneal@17
|
83 c = 'B' if self.color == BLACK else 'R'
|
bgneal@17
|
84 if self.value:
|
bgneal@17
|
85 return '({}: {} => {})'.format(c, self.key, self.value)
|
bgneal@17
|
86 else:
|
bgneal@17
|
87 return '({}: {})'.format(c, self.key)
|
bgneal@17
|
88
|
bgneal@17
|
89
|
bgneal@18
|
90 def is_red(n):
|
bgneal@18
|
91 """Return True if the given Node n is RED and False otherwise.
|
bgneal@18
|
92
|
bgneal@18
|
93 If the node is None, then it is considered to be BLACK, and False is
|
bgneal@18
|
94 returned.
|
bgneal@18
|
95
|
bgneal@18
|
96 """
|
bgneal@18
|
97 return n is not None and n.color == RED
|
bgneal@18
|
98
|
bgneal@18
|
99
|
bgneal@17
|
100 class Tree(object):
|
bgneal@17
|
101 """A red-black Tree class.
|
bgneal@17
|
102
|
bgneal@17
|
103 A red-black tree is a binary search tree with the following properties:
|
bgneal@17
|
104
|
bgneal@17
|
105 1. A node is either red or black.
|
bgneal@17
|
106 2. The root is black.
|
bgneal@17
|
107 3. All leaves are black.
|
bgneal@17
|
108 4. Both children of every red node are black.
|
bgneal@17
|
109 5. Every simple path from a given node to any descendant leaf contains
|
bgneal@17
|
110 the same number of black nodes.
|
bgneal@17
|
111
|
bgneal@17
|
112 These rules ensure that the path from the root to the furthest leaf is no
|
bgneal@17
|
113 more than twice as long as the path from the root to the nearest leaf. Thus
|
bgneal@17
|
114 the tree is roughly height-balanced.
|
bgneal@17
|
115
|
bgneal@17
|
116 """
|
bgneal@17
|
117
|
bgneal@17
|
118 def __init__(self):
|
bgneal@17
|
119 self.root = None
|
bgneal@19
|
120 self._size = 0
|
bgneal@19
|
121
|
bgneal@19
|
122 def __len__(self):
|
bgneal@19
|
123 """To support the len() function by returning the number of elements in
|
bgneal@19
|
124 the tree.
|
bgneal@19
|
125
|
bgneal@19
|
126 """
|
bgneal@19
|
127 return self._size
|
bgneal@17
|
128
|
bgneal@17
|
129 def __iter__(self):
|
bgneal@19
|
130 """Return an iterator to perform an inorder traversal of the tree."""
|
bgneal@17
|
131 return self._inorder(self.root)
|
bgneal@17
|
132
|
bgneal@17
|
133 def _inorder(self, node):
|
bgneal@17
|
134 """A generator to perform an inorder traversal of the nodes in the
|
bgneal@17
|
135 tree starting at the given node.
|
bgneal@17
|
136
|
bgneal@17
|
137 """
|
bgneal@19
|
138 if node:
|
bgneal@19
|
139 if node.link[0]:
|
bgneal@19
|
140 for n in self._inorder(node.link[0]):
|
bgneal@19
|
141 yield n
|
bgneal@17
|
142
|
bgneal@19
|
143 yield node
|
bgneal@17
|
144
|
bgneal@19
|
145 if node.link[1]:
|
bgneal@19
|
146 for n in self._inorder(node.link[1]):
|
bgneal@19
|
147 yield n
|
bgneal@17
|
148
|
bgneal@18
|
149 def _single_rotate(self, root, d):
|
bgneal@18
|
150 """Perform a single rotation about the node 'root' in the given
|
bgneal@18
|
151 direction 'd' (LEFT or RIGHT).
|
bgneal@18
|
152
|
bgneal@18
|
153 The old root is set to RED and the new root is set to BLACK.
|
bgneal@18
|
154
|
bgneal@18
|
155 Returns the new root.
|
bgneal@18
|
156
|
bgneal@18
|
157 """
|
bgneal@18
|
158 nd = not d
|
bgneal@18
|
159 save = root.link[nd]
|
bgneal@18
|
160
|
bgneal@18
|
161 root.link[nd] = save.link[d]
|
bgneal@18
|
162 save.link[d] = root
|
bgneal@18
|
163
|
bgneal@18
|
164 root.color = RED
|
bgneal@18
|
165 save.color = BLACK
|
bgneal@18
|
166
|
bgneal@18
|
167 return save
|
bgneal@18
|
168
|
bgneal@18
|
169 def _double_rotate(self, root, d):
|
bgneal@18
|
170 """Perform two single rotations about the node root in the direction d.
|
bgneal@18
|
171
|
bgneal@18
|
172 The new root is returned.
|
bgneal@18
|
173
|
bgneal@18
|
174 """
|
bgneal@18
|
175 nd = not d
|
bgneal@18
|
176 root.link[nd] = self._single_rotate(root.link[nd], nd)
|
bgneal@18
|
177 return self._single_rotate(root, d)
|
bgneal@18
|
178
|
bgneal@18
|
179 def validate(self, root=None):
|
bgneal@18
|
180 """Checks to see if the red-black tree validates at the given node, i.e.
|
bgneal@18
|
181 all red-black tree rules are valid. If root is None, the root of the
|
bgneal@18
|
182 tree is used as the starting point.
|
bgneal@18
|
183
|
bgneal@18
|
184 If any rules are violated, a TreeError is raised.
|
bgneal@18
|
185
|
bgneal@18
|
186 Returns the black height of the tree rooted at root.
|
bgneal@18
|
187
|
bgneal@18
|
188 """
|
bgneal@18
|
189 if root is None:
|
bgneal@18
|
190 root = self.root
|
bgneal@18
|
191
|
bgneal@18
|
192 return self._validate(root)
|
bgneal@18
|
193
|
bgneal@18
|
194 def _validate(self, root):
|
bgneal@18
|
195 """Internal implementation of the validate() method."""
|
bgneal@18
|
196
|
bgneal@18
|
197 if root is None:
|
bgneal@18
|
198 return 1
|
bgneal@18
|
199
|
bgneal@18
|
200 ln = root.link[0]
|
bgneal@18
|
201 rn = root.link[1]
|
bgneal@18
|
202
|
bgneal@18
|
203 # Check for consecutive red links
|
bgneal@18
|
204
|
bgneal@18
|
205 if is_red(root) and (is_red(ln) or is_red(rn)):
|
bgneal@18
|
206 raise TreeError('red violation')
|
bgneal@18
|
207
|
bgneal@18
|
208 lh = self._validate(ln)
|
bgneal@18
|
209 rh = self._validate(rn)
|
bgneal@18
|
210
|
bgneal@18
|
211 # Check for invalid binary search tree
|
bgneal@18
|
212
|
bgneal@18
|
213 if (ln and ln.key >= root.key) or (rn and rn.key <= root.key):
|
bgneal@18
|
214 raise TreeError('binary tree violation')
|
bgneal@18
|
215
|
bgneal@18
|
216 # Check for black height mismatch
|
bgneal@18
|
217 if lh != rh:
|
bgneal@18
|
218 raise TreeError('black violation')
|
bgneal@18
|
219
|
bgneal@18
|
220 # Only count black links
|
bgneal@18
|
221
|
bgneal@18
|
222 return lh if is_red(root) else lh + 1
|
bgneal@18
|
223
|
bgneal@17
|
224 def insert(self, key, value=None):
|
bgneal@19
|
225 """Insert the (key, value) pair into the tree. Duplicate keys are not
|
bgneal@19
|
226 allowed in the tree.
|
bgneal@19
|
227
|
bgneal@19
|
228 Returns a tuple of the form (node, flag) where node is either the newly
|
bgneal@19
|
229 inserted tree node or an already existing node that has the same key.
|
bgneal@19
|
230 The flag member is a Boolean that will be True if the node was inserted
|
bgneal@19
|
231 and False if a node with the given key already exists in the tree.
|
bgneal@19
|
232
|
bgneal@19
|
233 """
|
bgneal@17
|
234
|
bgneal@18
|
235 # Check for the empty tree case:
|
bgneal@17
|
236 if self.root is None:
|
bgneal@18
|
237 self.root = Node(key=key, value=value, color=BLACK)
|
bgneal@19
|
238 self._size = 1
|
bgneal@19
|
239 return self.root, True
|
bgneal@17
|
240
|
bgneal@18
|
241 # False/dummy tree root
|
bgneal@18
|
242 head = Node(key=None, value=None, color=BLACK)
|
bgneal@19
|
243 d = last = LEFT # direction variables
|
bgneal@17
|
244
|
bgneal@19
|
245 # Set up helpers
|
bgneal@18
|
246 t = head
|
bgneal@18
|
247 g = p = None
|
bgneal@19
|
248 q = t.link[1] = self.root
|
bgneal@19
|
249
|
bgneal@19
|
250 # Return values
|
bgneal@19
|
251 target, insert_flag = (None, False)
|
bgneal@17
|
252
|
bgneal@18
|
253 # Search down the tree
|
bgneal@18
|
254 while True:
|
bgneal@18
|
255 if q is None:
|
bgneal@18
|
256 # Insert new node at the bottom
|
bgneal@18
|
257 p.link[d] = q = Node(key=key, value=value, color=RED)
|
bgneal@19
|
258 self._size += 1
|
bgneal@19
|
259 insert_flag = True
|
bgneal@18
|
260 elif is_red(q.link[0]) and is_red(q.link[1]):
|
bgneal@18
|
261 # Color flip
|
bgneal@18
|
262 q.color = RED
|
bgneal@18
|
263 q.link[0].color = BLACK
|
bgneal@18
|
264 q.link[1].color = BLACK
|
bgneal@17
|
265
|
bgneal@18
|
266 # Fix red violation
|
bgneal@18
|
267 if is_red(q) and is_red(p):
|
bgneal@18
|
268 d2 = t.link[1] is g
|
bgneal@17
|
269
|
bgneal@18
|
270 if q is p.link[last]:
|
bgneal@18
|
271 t.link[d2] = self._single_rotate(g, not last)
|
bgneal@18
|
272 else:
|
bgneal@18
|
273 t.link[d2] = self._double_rotate(g, not last)
|
bgneal@17
|
274
|
bgneal@18
|
275 # Stop if found
|
bgneal@18
|
276 if q.key == key:
|
bgneal@19
|
277 target = q
|
bgneal@17
|
278 break
|
bgneal@17
|
279
|
bgneal@18
|
280 last = d
|
bgneal@18
|
281 d = q.key < key
|
bgneal@17
|
282
|
bgneal@18
|
283 # Update helpers
|
bgneal@18
|
284 if g is not None:
|
bgneal@18
|
285 t = g;
|
bgneal@18
|
286 g = p
|
bgneal@18
|
287 p = q
|
bgneal@18
|
288 q = q.link[d]
|
bgneal@17
|
289
|
bgneal@18
|
290 # Update root
|
bgneal@18
|
291 self.root = head.link[1]
|
bgneal@18
|
292 self.root.color = BLACK
|
bgneal@17
|
293
|
bgneal@19
|
294 return target, insert_flag
|
bgneal@19
|
295
|
bgneal@19
|
296 def remove(self, key):
|
bgneal@19
|
297 """Remove the given key from the tree.
|
bgneal@19
|
298
|
bgneal@19
|
299 Returns True if the key was found and removed and False if the key was
|
bgneal@19
|
300 not found in the tree.
|
bgneal@19
|
301
|
bgneal@19
|
302 """
|
bgneal@19
|
303 remove_flag = False # return value
|
bgneal@19
|
304
|
bgneal@19
|
305 if self.root is not None:
|
bgneal@19
|
306 # False/dummy tree root
|
bgneal@19
|
307 head = Node(key=None, value=None, color=BLACK)
|
bgneal@19
|
308 f = None # found item
|
bgneal@19
|
309 d = RIGHT # direction
|
bgneal@19
|
310
|
bgneal@19
|
311 # Set up helpers
|
bgneal@19
|
312 q = head
|
bgneal@19
|
313 g = p = None
|
bgneal@19
|
314 q.link[d] = self.root
|
bgneal@19
|
315
|
bgneal@19
|
316 # Search and push a red down to fix red violations as we go
|
bgneal@19
|
317 while q.link[d]:
|
bgneal@19
|
318 last = d
|
bgneal@19
|
319
|
bgneal@19
|
320 # Move the helpers down
|
bgneal@19
|
321 g = p
|
bgneal@19
|
322 p = q
|
bgneal@19
|
323 q = q.link[d]
|
bgneal@19
|
324 d = q.key < key
|
bgneal@19
|
325
|
bgneal@19
|
326 # Save the node with the matching data and keep going; we'll do
|
bgneal@19
|
327 # removal tasks at the end
|
bgneal@19
|
328 if q.key == key:
|
bgneal@19
|
329 f = q
|
bgneal@19
|
330
|
bgneal@19
|
331 # Push the red node down with rotations and color flips
|
bgneal@19
|
332 if not is_red(q) and not is_red(q.link[d]):
|
bgneal@19
|
333 if is_red(q.link[not d]):
|
bgneal@19
|
334 p.link[last] = self._single_rotate(q, d)
|
bgneal@19
|
335 p = p.link[last]
|
bgneal@19
|
336 elif not is_red(q.link[not d]):
|
bgneal@19
|
337 s = p.link[not last]
|
bgneal@19
|
338
|
bgneal@19
|
339 if s:
|
bgneal@19
|
340 if not is_red(s.link[not last]) and not is_red(s.link[last]):
|
bgneal@19
|
341 # Color flip
|
bgneal@19
|
342 p.color = BLACK
|
bgneal@19
|
343 s.color = RED
|
bgneal@19
|
344 q.color = RED
|
bgneal@19
|
345 else:
|
bgneal@19
|
346 d2 = g.link[1] is p
|
bgneal@19
|
347
|
bgneal@19
|
348 if is_red(s.link[last]):
|
bgneal@19
|
349 g.link[d2] = self._double_rotate(p, last)
|
bgneal@19
|
350 elif is_red(s.link[not last]):
|
bgneal@19
|
351 g.link[d2] = self._single_rotate(p, last)
|
bgneal@19
|
352
|
bgneal@19
|
353 # Ensure correct coloring
|
bgneal@19
|
354 q.color = g.link[d2].color = RED
|
bgneal@19
|
355 g.link[d2].link[0].color = BLACK
|
bgneal@19
|
356 g.link[d2].link[1].color = BLACK
|
bgneal@19
|
357
|
bgneal@19
|
358 # Replace and remove the saved node
|
bgneal@19
|
359 if f:
|
bgneal@19
|
360 f.key, f.value = q.key, q.value
|
bgneal@19
|
361 p.link[p.link[1] is q] = q.link[q.link[0] is None]
|
bgneal@19
|
362 q.free()
|
bgneal@19
|
363 self._size -= 1
|
bgneal@19
|
364 remove_flag = True
|
bgneal@19
|
365
|
bgneal@19
|
366 # Update root and make it black
|
bgneal@19
|
367 self.root = head.link[1]
|
bgneal@19
|
368 if self.root:
|
bgneal@19
|
369 self.root.color = BLACK
|
bgneal@19
|
370
|
bgneal@19
|
371 return remove_flag
|
bgneal@19
|
372
|
bgneal@17
|
373
|
bgneal@17
|
374 if __name__ == '__main__':
|
bgneal@17
|
375 import random
|
bgneal@17
|
376
|
bgneal@18
|
377 for n in range(30):
|
bgneal@18
|
378 tree = Tree()
|
bgneal@18
|
379 tree.validate()
|
bgneal@17
|
380
|
bgneal@19
|
381 vals = []
|
bgneal@19
|
382 vals_set = set()
|
bgneal@18
|
383 for i in range(25):
|
bgneal@18
|
384 val = random.randint(0, 100)
|
bgneal@19
|
385 while val in vals_set:
|
bgneal@19
|
386 val = random.randint(0, 100)
|
bgneal@19
|
387 vals.append(val)
|
bgneal@19
|
388 vals_set.add(val)
|
bgneal@19
|
389
|
bgneal@18
|
390 tree.insert(val)
|
bgneal@19
|
391 tree.validate()
|
bgneal@17
|
392
|
bgneal@19
|
393 print 'Inserted in this order:', vals
|
bgneal@19
|
394 assert len(vals) == len(vals_set)
|
bgneal@19
|
395 keys = []
|
bgneal@18
|
396 for n in tree:
|
bgneal@18
|
397 print n.key,
|
bgneal@19
|
398 keys.append(n.key)
|
bgneal@19
|
399 print ' - len:', len(tree)
|
bgneal@18
|
400
|
bgneal@19
|
401 # delete in a random order
|
bgneal@19
|
402 random.shuffle(keys)
|
bgneal@19
|
403
|
bgneal@19
|
404 for k in keys:
|
bgneal@19
|
405 print 'Removing', k
|
bgneal@19
|
406 tree.remove(k)
|
bgneal@19
|
407 for n in tree:
|
bgneal@19
|
408 print n.key,
|
bgneal@19
|
409 print ' - len:', len(tree)
|
bgneal@19
|
410 tree.validate()
|