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@17
|
74 def __str__(self):
|
bgneal@17
|
75 c = 'B' if self.color == BLACK else 'R'
|
bgneal@17
|
76 if self.value:
|
bgneal@17
|
77 return '({}: {} => {})'.format(c, self.key, self.value)
|
bgneal@17
|
78 else:
|
bgneal@17
|
79 return '({}: {})'.format(c, self.key)
|
bgneal@17
|
80
|
bgneal@17
|
81
|
bgneal@18
|
82 def is_red(n):
|
bgneal@18
|
83 """Return True if the given Node n is RED and False otherwise.
|
bgneal@18
|
84
|
bgneal@18
|
85 If the node is None, then it is considered to be BLACK, and False is
|
bgneal@18
|
86 returned.
|
bgneal@18
|
87
|
bgneal@18
|
88 """
|
bgneal@18
|
89 return n is not None and n.color == RED
|
bgneal@18
|
90
|
bgneal@18
|
91
|
bgneal@17
|
92 class Tree(object):
|
bgneal@17
|
93 """A red-black Tree class.
|
bgneal@17
|
94
|
bgneal@17
|
95 A red-black tree is a binary search tree with the following properties:
|
bgneal@17
|
96
|
bgneal@17
|
97 1. A node is either red or black.
|
bgneal@17
|
98 2. The root is black.
|
bgneal@17
|
99 3. All leaves are black.
|
bgneal@17
|
100 4. Both children of every red node are black.
|
bgneal@17
|
101 5. Every simple path from a given node to any descendant leaf contains
|
bgneal@17
|
102 the same number of black nodes.
|
bgneal@17
|
103
|
bgneal@17
|
104 These rules ensure that the path from the root to the furthest leaf is no
|
bgneal@17
|
105 more than twice as long as the path from the root to the nearest leaf. Thus
|
bgneal@17
|
106 the tree is roughly height-balanced.
|
bgneal@17
|
107
|
bgneal@17
|
108 """
|
bgneal@17
|
109
|
bgneal@17
|
110 def __init__(self):
|
bgneal@17
|
111 self.root = None
|
bgneal@17
|
112
|
bgneal@17
|
113 def __iter__(self):
|
bgneal@17
|
114 return self._inorder(self.root)
|
bgneal@17
|
115
|
bgneal@17
|
116 def _inorder(self, node):
|
bgneal@17
|
117 """A generator to perform an inorder traversal of the nodes in the
|
bgneal@17
|
118 tree starting at the given node.
|
bgneal@17
|
119
|
bgneal@17
|
120 """
|
bgneal@18
|
121 if node.link[0]:
|
bgneal@18
|
122 for n in self._inorder(node.link[0]):
|
bgneal@17
|
123 yield n
|
bgneal@17
|
124
|
bgneal@17
|
125 yield node
|
bgneal@17
|
126
|
bgneal@18
|
127 if node.link[1]:
|
bgneal@18
|
128 for n in self._inorder(node.link[1]):
|
bgneal@17
|
129 yield n
|
bgneal@17
|
130
|
bgneal@18
|
131 def _single_rotate(self, root, d):
|
bgneal@18
|
132 """Perform a single rotation about the node 'root' in the given
|
bgneal@18
|
133 direction 'd' (LEFT or RIGHT).
|
bgneal@18
|
134
|
bgneal@18
|
135 The old root is set to RED and the new root is set to BLACK.
|
bgneal@18
|
136
|
bgneal@18
|
137 Returns the new root.
|
bgneal@18
|
138
|
bgneal@18
|
139 """
|
bgneal@18
|
140 nd = not d
|
bgneal@18
|
141 save = root.link[nd]
|
bgneal@18
|
142
|
bgneal@18
|
143 root.link[nd] = save.link[d]
|
bgneal@18
|
144 save.link[d] = root
|
bgneal@18
|
145
|
bgneal@18
|
146 root.color = RED
|
bgneal@18
|
147 save.color = BLACK
|
bgneal@18
|
148
|
bgneal@18
|
149 return save
|
bgneal@18
|
150
|
bgneal@18
|
151 def _double_rotate(self, root, d):
|
bgneal@18
|
152 """Perform two single rotations about the node root in the direction d.
|
bgneal@18
|
153
|
bgneal@18
|
154 The new root is returned.
|
bgneal@18
|
155
|
bgneal@18
|
156 """
|
bgneal@18
|
157 nd = not d
|
bgneal@18
|
158 root.link[nd] = self._single_rotate(root.link[nd], nd)
|
bgneal@18
|
159 return self._single_rotate(root, d)
|
bgneal@18
|
160
|
bgneal@18
|
161 def validate(self, root=None):
|
bgneal@18
|
162 """Checks to see if the red-black tree validates at the given node, i.e.
|
bgneal@18
|
163 all red-black tree rules are valid. If root is None, the root of the
|
bgneal@18
|
164 tree is used as the starting point.
|
bgneal@18
|
165
|
bgneal@18
|
166 If any rules are violated, a TreeError is raised.
|
bgneal@18
|
167
|
bgneal@18
|
168 Returns the black height of the tree rooted at root.
|
bgneal@18
|
169
|
bgneal@18
|
170 """
|
bgneal@18
|
171 if root is None:
|
bgneal@18
|
172 root = self.root
|
bgneal@18
|
173
|
bgneal@18
|
174 return self._validate(root)
|
bgneal@18
|
175
|
bgneal@18
|
176 def _validate(self, root):
|
bgneal@18
|
177 """Internal implementation of the validate() method."""
|
bgneal@18
|
178
|
bgneal@18
|
179 if root is None:
|
bgneal@18
|
180 return 1
|
bgneal@18
|
181
|
bgneal@18
|
182 ln = root.link[0]
|
bgneal@18
|
183 rn = root.link[1]
|
bgneal@18
|
184
|
bgneal@18
|
185 # Check for consecutive red links
|
bgneal@18
|
186
|
bgneal@18
|
187 if is_red(root) and (is_red(ln) or is_red(rn)):
|
bgneal@18
|
188 raise TreeError('red violation')
|
bgneal@18
|
189
|
bgneal@18
|
190 lh = self._validate(ln)
|
bgneal@18
|
191 rh = self._validate(rn)
|
bgneal@18
|
192
|
bgneal@18
|
193 # Check for invalid binary search tree
|
bgneal@18
|
194
|
bgneal@18
|
195 if (ln and ln.key >= root.key) or (rn and rn.key <= root.key):
|
bgneal@18
|
196 raise TreeError('binary tree violation')
|
bgneal@18
|
197
|
bgneal@18
|
198 # Check for black height mismatch
|
bgneal@18
|
199 if lh != rh:
|
bgneal@18
|
200 raise TreeError('black violation')
|
bgneal@18
|
201
|
bgneal@18
|
202 # Only count black links
|
bgneal@18
|
203
|
bgneal@18
|
204 return lh if is_red(root) else lh + 1
|
bgneal@18
|
205
|
bgneal@17
|
206 def insert(self, key, value=None):
|
bgneal@18
|
207 """Insert the (key, value) pair into the tree."""
|
bgneal@17
|
208
|
bgneal@18
|
209 # Check for the empty tree case:
|
bgneal@17
|
210 if self.root is None:
|
bgneal@18
|
211 self.root = Node(key=key, value=value, color=BLACK)
|
bgneal@17
|
212 return
|
bgneal@17
|
213
|
bgneal@18
|
214 # False/dummy tree root
|
bgneal@18
|
215 head = Node(key=None, value=None, color=BLACK)
|
bgneal@18
|
216 d = LEFT
|
bgneal@18
|
217 last = LEFT
|
bgneal@17
|
218
|
bgneal@18
|
219 t = head
|
bgneal@18
|
220 g = p = None
|
bgneal@18
|
221 t.link[1] = self.root
|
bgneal@18
|
222 q = self.root
|
bgneal@17
|
223
|
bgneal@18
|
224 # Search down the tree
|
bgneal@18
|
225 while True:
|
bgneal@18
|
226 if q is None:
|
bgneal@18
|
227 # Insert new node at the bottom
|
bgneal@18
|
228 p.link[d] = q = Node(key=key, value=value, color=RED)
|
bgneal@18
|
229 elif is_red(q.link[0]) and is_red(q.link[1]):
|
bgneal@18
|
230 # Color flip
|
bgneal@18
|
231 q.color = RED
|
bgneal@18
|
232 q.link[0].color = BLACK
|
bgneal@18
|
233 q.link[1].color = BLACK
|
bgneal@17
|
234
|
bgneal@18
|
235 # Fix red violation
|
bgneal@18
|
236 if is_red(q) and is_red(p):
|
bgneal@18
|
237 d2 = t.link[1] is g
|
bgneal@17
|
238
|
bgneal@18
|
239 if q is p.link[last]:
|
bgneal@18
|
240 t.link[d2] = self._single_rotate(g, not last)
|
bgneal@18
|
241 else:
|
bgneal@18
|
242 t.link[d2] = self._double_rotate(g, not last)
|
bgneal@17
|
243
|
bgneal@18
|
244 # Stop if found
|
bgneal@18
|
245 if q.key == key:
|
bgneal@17
|
246 break
|
bgneal@17
|
247
|
bgneal@18
|
248 last = d
|
bgneal@18
|
249 d = q.key < key
|
bgneal@17
|
250
|
bgneal@18
|
251 # Update helpers
|
bgneal@18
|
252 if g is not None:
|
bgneal@18
|
253 t = g;
|
bgneal@18
|
254 g = p
|
bgneal@18
|
255 p = q
|
bgneal@18
|
256 q = q.link[d]
|
bgneal@17
|
257
|
bgneal@18
|
258 # Update root
|
bgneal@18
|
259 self.root = head.link[1]
|
bgneal@18
|
260 self.root.color = BLACK
|
bgneal@17
|
261
|
bgneal@17
|
262
|
bgneal@17
|
263 if __name__ == '__main__':
|
bgneal@17
|
264 import random
|
bgneal@17
|
265
|
bgneal@18
|
266 for n in range(30):
|
bgneal@18
|
267 tree = Tree()
|
bgneal@18
|
268 tree.validate()
|
bgneal@17
|
269
|
bgneal@18
|
270 for i in range(25):
|
bgneal@18
|
271 val = random.randint(0, 100)
|
bgneal@18
|
272 tree.insert(val)
|
bgneal@17
|
273
|
bgneal@18
|
274 for n in tree:
|
bgneal@18
|
275 print n.key,
|
bgneal@18
|
276 print
|
bgneal@18
|
277
|
bgneal@18
|
278 tree.validate()
|