Mercurial > public > think_complexity
diff redblacktree.py @ 18:92e2879e2e33
Rework the red-black tree based on Julienne Walker's tutorial.
Insertion is implemented now. Deletion will come next.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Wed, 26 Dec 2012 19:59:17 -0600 |
parents | 977628018b4b |
children | 3c74185c5047 |
line wrap: on
line diff
--- a/redblacktree.py Wed Dec 19 22:29:09 2012 -0600 +++ b/redblacktree.py Wed Dec 26 19:59:17 2012 -0600 @@ -1,14 +1,4 @@ -"""A red-black tree for Section 3.4, Exercise 4 in Allen Downey's _Think -Complexity_ book. - -http://greenteapress.com/complexity - -This code is based on the description of the red-black tree at -http://en.wikipedia.org/wiki/Red-black_tree. - -Some code and ideas were taken from code by Darren Hart at -http://dvhart.com/darren/files/rbtree.py - +""" Copyright (C) 2012 Brian G. Neal. Permission is hereby granted, free of charge, to any person obtaining a copy of @@ -28,48 +18,58 @@ 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 an optional parent, and optional left and right children. Each - node also 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 keys. The optional value is useful for using the - red-black tree to implement a map datastructure. + * A node has a color, either RED or BLACK. + * A node has a key and an optional value. - In a red-black tree, nil children are always considered black. + * 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, color=RED, value=None): + def __init__(self, key, value=None, color=RED): self.key = key self.value = value self.color = color - - self.parent = None - self.left = None - self.right = None - - @property - def grandparent(self): - """Return the grandparent of this node, or None if it does not exist.""" - return self.parent.parent if self.parent else None - - @property - def uncle(self): - """Return this node's uncle if it exists, or None if not. - - An uncle is a parent's sibling. - - """ - g = self.grandparent - if g: - return g.left if g.right is self.parent else g.right - return None + self.link = [None, None] def __str__(self): c = 'B' if self.color == BLACK else 'R' @@ -79,6 +79,16 @@ 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. @@ -108,172 +118,161 @@ tree starting at the given node. """ - if node.left: - for n in self._inorder(node.left): + if node.link[0]: + for n in self._inorder(node.link[0]): yield n yield node - if node.right: - for n in self._inorder(node.right): + 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): - node = Node(key=key, value=value, color=RED) + """Insert the (key, value) pair into the tree.""" - # trivial case of inserting into an empty tree: + # Check for the empty tree case: if self.root is None: - self.root = node - self.root.color = BLACK + self.root = Node(key=key, value=value, color=BLACK) return - # Find a spot to insert the new red node: + # False/dummy tree root + head = Node(key=None, value=None, color=BLACK) + d = LEFT + last = LEFT - x = self.root - while x is not None: - p = x - if key < x.key: - x = x.left - else: - x = x.right + t = head + g = p = None + t.link[1] = self.root + q = self.root - node.parent = p # p is the new node's parent + # 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) + 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 - # p now has 1 child; decide if the new node goes on the left or right + # Fix red violation + if is_red(q) and is_red(p): + d2 = t.link[1] is g - if key < p.key: - p.left = node - else: - p.right = node + 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) - # ensure the new tree follows the red-black rules: - - while True: - p = node.parent - - # Case 1: root node - if p is None: - node.color = BLACK + # Stop if found + if q.key == key: break - # Case 2: parent is black - if p.color == BLACK: - break + last = d + d = q.key < key - # Case 3: parent and uncle are red - u = node.uncle - if u and u.color == RED: - # repaint parent & uncle black; grandparent becomes red: - p.color = BLACK - u.color = BLACK - gp = node.grandparent - gp.color = RED + # Update helpers + if g is not None: + t = g; + g = p + p = q + q = q.link[d] - # enforce the rules at the grandparent level - node = gp - continue - - # gp is the grandparent; it can't be None because we know the parent - # is red at this point - gp = p.parent - - # Case 4: At this point we know the parent is red and the uncle is - # black. - # If the new node is a right child of the parent, and the - # parent is on the left of the grandparent => left rotation on the - # parent. - # If the new node is a left child of the parent, and the parent is - # on the right of the grandparent => right rotation on the parent. - - if node is p.right and p is gp.left: - self._rotate_left(p) - node = node.left - elif node is p.left and p is gp.right: - self._rotate_right(p) - node = node.right - - # Note that case 4 must fall through to case 5 to fix up the former parent - # node, which is now the child of the new red node. - - # Case 5: The parent is red and the uncle is black. - # If the new node is the left child of the parent and the parent is - # the left child of the grandparent => rotate right on the - # grandparent. - # If the new node is the right child of the parent and the parent - # is the right child of the grandparent => rotate left on the - # grandparent. - - p = node.parent - gp = p.parent - - p.color = BLACK - gp.color = RED - - if node is p.left and p is gp.left: - self._rotate_right(gp) - elif node is p.right and p is gp.right: #TODO: can this be an else? - self._rotate_left(gp) - break - - def _rotate_right(self, node): - """Rotate the tree right on the given node.""" - - p = node.parent - left = node.left - - if p: - if node is p.right: - p.right = left - else: - p.left = left - - left.parent = p - node.left = left.right - - if node.left: - node.left.parent = node - - left.right = node - node.parent = left - - # fix up the root if necessary - if self.root is node: - self.root = left - - def _rotate_left(self, node): - """Rotate the tree left on the given node.""" - - p = node.parent - right = node.right - - if p: - if node is p.right: - p.right = right - else: - p.left = right - - right.parent = p - node.right = right.left - - if node.right: - node.right.parent = node - - right.left = node - node.parent = right - - # fix up the root if necessary - if self.root is node: - self.root = right + # Update root + self.root = head.link[1] + self.root.color = BLACK if __name__ == '__main__': import random - tree = Tree() + for n in range(30): + tree = Tree() + tree.validate() - for i in range(20): - val = random.randint(0, 100) - tree.insert(val) + for i in range(25): + val = random.randint(0, 100) + tree.insert(val) - for n in tree: - print n.key, + for n in tree: + print n.key, + print + + tree.validate()