Mercurial > public > think_complexity
view redblacktree.py @ 29:65b70b4e12ad
Chapter 5, exercise 3.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Sun, 06 Jan 2013 18:11:17 -0600 |
parents | 0326803882ad |
children |
line wrap: on
line source
""" Copyright (C) 2012 Brian G. Neal. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---- A red-black tree for Section 3.4, Exercise 4 in Allen Downey's _Think Complexity_ book. http://greenteapress.com/complexity Red black trees are described on Wikipedia: http://en.wikipedia.org/wiki/Red-black_tree. This is basically a Python implementation of Julienne Walker's Red Black Trees tutorial found at: http://www.eternallyconfuzzled.com/tuts/datastructures/jsw_tut_rbtree.aspx We implement Julienne's top-down insertion and deletion algorithms here. Some ideas were also borrowed from code by Darren Hart at http://dvhart.com/darren/files/rbtree.py """ BLACK, RED = range(2) LEFT, RIGHT = range(2) class TreeError(Exception): """Base exception class for red-black tree errors.""" pass class Node(object): """A node class for red-black trees. * A node has a color, either RED or BLACK. * A node has a key and an optional value. * The key is used to order the red-black tree by calling the "<" operator when comparing two nodes. * The value is useful for using the red-black tree to implement a map datastructure. * Nodes have exactly 2 link fields which we represent as a list of 2 elements to represent the left and right children of the node. This list representation was borrowed from Julienne Walker as it simplifies some of the code. Element 0 is the LEFT child and element 1 is the RIGHT child. """ def __init__(self, key, value=None, color=RED): self.key = key self.value = value self.color = color self.link = [None, None] def free(self): """Call this function when removing a node from a tree. It updates the links to encourage garbage collection. """ self.link[0] = self.link[1] = None def __str__(self): c = 'B' if self.color == BLACK else 'R' if self.value: return '({}: {} => {})'.format(c, self.key, self.value) else: return '({}: {})'.format(c, self.key) def is_red(n): """Return True if the given Node n is RED and False otherwise. If the node is None, then it is considered to be BLACK, and False is returned. """ return n is not None and n.color == RED class Tree(object): """A red-black Tree class. A red-black tree is a binary search tree with the following properties: 1. A node is either red or black. 2. The root is black. 3. All leaves are black. 4. Both children of every red node are black. 5. Every simple path from a given node to any descendant leaf contains the same number of black nodes. These rules ensure that the path from the root to the furthest leaf is no more than twice as long as the path from the root to the nearest leaf. Thus the tree is roughly height-balanced. """ def __init__(self): self.root = None self._size = 0 def __len__(self): """To support the len() function by returning the number of elements in the tree. """ return self._size def __iter__(self): """Return an iterator to perform an inorder traversal of the tree.""" return self._inorder(self.root) def _inorder(self, node): """A generator to perform an inorder traversal of the nodes in the tree starting at the given node. """ if node: if node.link[0]: for n in self._inorder(node.link[0]): yield n yield node if node.link[1]: for n in self._inorder(node.link[1]): yield n def _single_rotate(self, root, d): """Perform a single rotation about the node 'root' in the given direction 'd' (LEFT or RIGHT). The old root is set to RED and the new root is set to BLACK. Returns the new root. """ nd = not d save = root.link[nd] root.link[nd] = save.link[d] save.link[d] = root root.color = RED save.color = BLACK return save def _double_rotate(self, root, d): """Perform two single rotations about the node root in the direction d. The new root is returned. """ nd = not d root.link[nd] = self._single_rotate(root.link[nd], nd) return self._single_rotate(root, d) def validate(self, root=None): """Checks to see if the red-black tree validates at the given node, i.e. all red-black tree rules are valid. If root is None, the root of the tree is used as the starting point. If any rules are violated, a TreeError is raised. Returns the black height of the tree rooted at root. """ if root is None: root = self.root return self._validate(root) def _validate(self, root): """Internal implementation of the validate() method.""" if root is None: return 1 ln = root.link[0] rn = root.link[1] # Check for consecutive red links if is_red(root) and (is_red(ln) or is_red(rn)): raise TreeError('red violation') lh = self._validate(ln) rh = self._validate(rn) # Check for invalid binary search tree if (ln and ln.key >= root.key) or (rn and rn.key <= root.key): raise TreeError('binary tree violation') # Check for black height mismatch if lh != rh: raise TreeError('black violation') # Only count black links return lh if is_red(root) else lh + 1 def insert(self, key, value=None): """Insert the (key, value) pair into the tree. Duplicate keys are not allowed in the tree. Returns a tuple of the form (node, flag) where node is either the newly inserted tree node or an already existing node that has the same key. The flag member is a Boolean that will be True if the node was inserted and False if a node with the given key already exists in the tree. """ # Check for the empty tree case: if self.root is None: self.root = Node(key=key, value=value, color=BLACK) self._size = 1 return self.root, True # False/dummy tree root head = Node(key=None, value=None, color=BLACK) d = last = LEFT # direction variables # Set up helpers t = head g = p = None q = t.link[1] = self.root # Return values target, insert_flag = (None, False) # Search down the tree while True: if q is None: # Insert new node at the bottom p.link[d] = q = Node(key=key, value=value, color=RED) self._size += 1 insert_flag = True elif is_red(q.link[0]) and is_red(q.link[1]): # Color flip q.color = RED q.link[0].color = BLACK q.link[1].color = BLACK # Fix red violation if is_red(q) and is_red(p): d2 = t.link[1] is g if q is p.link[last]: t.link[d2] = self._single_rotate(g, not last) else: t.link[d2] = self._double_rotate(g, not last) # Stop if found if q.key == key: target = q break last = d d = q.key < key # Update helpers if g is not None: t = g; g = p p = q q = q.link[d] # Update root self.root = head.link[1] self.root.color = BLACK return target, insert_flag def remove(self, key): """Remove the given key from the tree. Returns True if the key was found and removed and False if the key was not found in the tree. """ remove_flag = False # return value if self.root is not None: # False/dummy tree root head = Node(key=None, value=None, color=BLACK) f = None # found item d = RIGHT # direction # Set up helpers q = head g = p = None q.link[d] = self.root # Search and push a red down to fix red violations as we go while q.link[d]: last = d # Move the helpers down g = p p = q q = q.link[d] d = q.key < key # Save the node with the matching data and keep going; we'll do # removal tasks at the end if q.key == key: f = q # Push the red node down with rotations and color flips if not is_red(q) and not is_red(q.link[d]): if is_red(q.link[not d]): p.link[last] = self._single_rotate(q, d) p = p.link[last] elif not is_red(q.link[not d]): s = p.link[not last] if s: if not is_red(s.link[not last]) and not is_red(s.link[last]): # Color flip p.color = BLACK s.color = RED q.color = RED else: d2 = g.link[1] is p if is_red(s.link[last]): g.link[d2] = self._double_rotate(p, last) elif is_red(s.link[not last]): g.link[d2] = self._single_rotate(p, last) # Ensure correct coloring q.color = g.link[d2].color = RED g.link[d2].link[0].color = BLACK g.link[d2].link[1].color = BLACK # Replace and remove the saved node if f: f.key, f.value = q.key, q.value p.link[p.link[1] is q] = q.link[q.link[0] is None] q.free() self._size -= 1 remove_flag = True # Update root and make it black self.root = head.link[1] if self.root: self.root.color = BLACK return remove_flag def find(self, key): """Looks up the key in the tree and returns the corresponding value, or raises a KeyError if it does not exist in the tree. """ p = self.root while p: if p.key == key: return p.value else: d = p.key < key p = p.link[d] raise KeyError if __name__ == '__main__': import random for n in range(30): tree = Tree() tree.validate() vals = [] vals_set = set() for i in range(25): val = random.randint(0, 100) while val in vals_set: val = random.randint(0, 100) vals.append(val) vals_set.add(val) tree.insert(val) tree.validate() print 'Inserted in this order:', vals assert len(vals) == len(vals_set) keys = [] for n in tree: print n.key, keys.append(n.key) print ' - len:', len(tree) # delete in a random order random.shuffle(keys) for k in keys: print 'Removing', k tree.remove(k) for n in tree: print n.key, print ' - len:', len(tree) tree.validate()