UNPKG

data-structures

Version:

JavaScript data structures written in CoffeeScript.

373 lines (332 loc) 12.7 kB
### Credit to Wikipedia's article on [Red-black tree](http://en.wikipedia.org/wiki/Red–black_tree) **Note:** doesn't handle duplicate entries, undefined and null. This is by design. ## Overview example: ```js var rbt = new RedBlackTree([7, 5, 1, 8]); rbt.add(2); // => 2 rbt.add(10); // => 10 rbt.has(5); // => true rbt.peekMin(); // => 1 rbt.peekMax(); // => 10 rbt.removeMin(); // => 1 rbt.removeMax(); // => 10 rbt.remove(8); // => 8 ``` ## Properties: - size: The total number of items. ### NODE_FOUND = 0 NODE_TOO_BIG = 1 NODE_TOO_SMALL = 2 STOP_SEARCHING = 3 # For debugging. # RED = 'RED' # BLACK = 'BLACK' RED = 1 BLACK = 2 # Property of a red-black tree, taken from Wikipedia: # 1. A node is either red or black. # 2. Root is black. # 3. Leaves are all null and considered black. # 4. Both children of a red node are black. # 5. Every path from a node to any of its descendent leaves contains the same # number of black nodes. # **In our implementation, leaves are simply undefined.** class RedBlackTree constructor: (valuesToAdd = []) -> ### Pass an optional array to be turned into binary tree. **Note:** does not accept duplicate, undefined and null. ### @_root @size = 0 @add value for value in valuesToAdd when value? add: (value) -> ### Again, make sure to not pass a value already in the tree, or undefined, or null. _Returns:_ value added. ### if not value? then return @size++ nodeToInsert = value: value _color: RED if not @_root then @_root = nodeToInsert else foundNode = _findNode @_root, (node) -> if value is node.value then NODE_FOUND else if value < node.value if node._left then NODE_TOO_BIG else nodeToInsert._parent = node node._left = nodeToInsert STOP_SEARCHING # Inserting 'undefined' will go to right child. Important to keep this # conditional in sync with has(). else if node._right then NODE_TOO_SMALL else nodeToInsert._parent = node node._right = nodeToInsert STOP_SEARCHING if foundNode? then return # After adding the node, we need to operate on it to preserve the tree's # properties by filtering it through a series of cases. It'd be easier if # there's tail recursion in JavaScript, as some cases fix the node but # restart the cases on the node's ancestor. We'll have to use loops for now. currentNode = nodeToInsert loop # Case 1: node is root. Violates 1. Paint it black. if currentNode is @_root currentNode._color = BLACK break # Case 2: parent black. No properties violated. After that, parent is sure # to be red. if currentNode._parent._color is BLACK break # Case 3: if node's parent and uncle are red, they are painted black. # Their parent (node's grandparent) should be painted red, and the # grandparent red. Note that node certainly has a grandparent, since at # this point, its parent's red, which can't be the root. # After the painting, the grandparent might violate 2 or 4. if _uncleOf(currentNode)?._color is RED currentNode._parent._color = BLACK _uncleOf(currentNode)._color = BLACK _grandParentOf(currentNode)._color = RED currentNode = _grandParentOf currentNode continue # At this point, uncle is either black or doesn't exist. # Case 4: parent red, uncle black, node is right child, parent is left # child. Do a left rotation. Then, former parent passes through case 5. if not _isLeft(currentNode) and _isLeft(currentNode._parent) @_rotateLeft currentNode._parent currentNode = currentNode._left else if _isLeft(currentNode) and not _isLeft(currentNode._parent) @_rotateRight currentNode._parent currentNode = currentNode._right # Case 5: parent red, uncle black, node is left child, parent is left # child. Right rotation. Switch parent and grandparent's color. currentNode._parent._color = BLACK _grandParentOf(currentNode)._color = RED if _isLeft currentNode @_rotateRight _grandParentOf currentNode else @_rotateLeft _grandParentOf currentNode break return value has: (value) -> ### _Returns:_ true or false. ### foundNode = _findNode @_root, (node) -> if value is node.value then NODE_FOUND # Keep the conditional this way; node.value > value wouldn't work. The # insertion uses the same comparison to add 'undefined' (to the right # child). else if value < node.value then NODE_TOO_BIG else NODE_TOO_SMALL if foundNode then yes else no peekMin: -> ### Check the minimum value without removing it. _Returns:_ the minimum value. ### _peekMinNode(@_root)?.value peekMax: -> ### Check the maximum value without removing it. _Returns:_ the maximum value. ### _peekMaxNode(@_root)?.value remove: (value) -> ### _Returns:_ the value removed, or undefined if the value's not found. ### foundNode = _findNode @_root, (node) -> if value is node.value then NODE_FOUND # Keep the conditional this way; node.value > value wouldn't work. The # insertion uses the same comparison to add 'undefined' (to the right # child). else if value < node.value then NODE_TOO_BIG else NODE_TOO_SMALL if not foundNode then return @_removeNode @_root, foundNode @size-- return value removeMin: -> ### _Returns:_ smallest item removed, or undefined if tree's empty. ### nodeToRemove = _peekMinNode @_root if not nodeToRemove then return # Store in. Might destroy the node during removal in the future, so can't # just return nodeToRemove.value. valueToReturn = nodeToRemove.value @_removeNode @_root, nodeToRemove return valueToReturn removeMax: -> ### _Returns:_ biggest item removed, or undefined if tree's empty. ### nodeToRemove = _peekMaxNode @_root if not nodeToRemove then return # Store in. Might destroy the node during removal in the future, so can't # just return nodeToRemove.value. valueToReturn = nodeToRemove.value @_removeNode @_root, nodeToRemove return valueToReturn # To simplify removal cases, we can notice this: # 1. Node has no child. # 2. Node has two children. Select the smallest child on the right branch # (leftmost) and copy its value into the node to delete. This replacement node # certainly has less than two children or it wouldn't be the smallest. Then # delete this replacement node. # 3. Node has one child. # They all come down to removing a node with maximum one child. _removeNode: (root, node) -> if node._left and node._right successor = _peekMinNode node._right node.value = successor.value node = successor # At this point, the node to remove has only one child. successor = node._left or node._right if not successor # Hard code a leaf in, to make case checking easier. successor = color: BLACK _right: undefined _left: undefined isLeaf: yes successor._parent = node._parent node._parent?[_leftOrRight node] = successor # We're done if node's red. If it's black and its child that took its place # is red, change it to black. If both are black, we do cases checking like # in insert. if node._color is BLACK if successor._color is RED successor._color = BLACK if not successor._parent then @_root = successor else loop # Case 1: node is root. Done. if not successor._parent if not successor.isLeaf then @_root = successor else @_root = undefined break # Case 2: sibling red. Flip color of P and S. Left rotate P. sibling = _siblingOf successor if sibling?._color is RED successor._parent._color = RED sibling._color = BLACK if _isLeft successor @_rotateLeft successor._parent else @_rotateRight successor._parent # Case 3: parent, sibling and sibling children all black. Paint # sibling red. Rebalance parent. sibling = _siblingOf successor if successor._parent._color is BLACK and (not sibling or (sibling._color is BLACK and (not sibling._left or sibling._left._color is BLACK) and (not sibling._right or sibling._right._color is BLACK))) sibling?._color = RED if successor.isLeaf successor._parent[_leftOrRight successor] = undefined successor = successor._parent continue # Case 4: sibling and sibling children black. Node parent red. Swap # color of sibling and node parent. if successor._parent._color is RED and (not sibling or (sibling._color is BLACK and (not sibling._left or sibling._left?._color is BLACK) and (not sibling._right or sibling._right?._color is BLACK))) sibling?._color = RED successor._parent._color = BLACK break # Case 5: sibling black, sibling left child red, right child black, # node is left child. Rotate right sibling. Swap color of sibling and # its new parent. if sibling?._color is BLACK if _isLeft(successor) and (not sibling._right or sibling._right._color is BLACK) and sibling._left?._color is RED sibling._color = RED sibling._left?._color = BLACK @_rotateRight sibling else if not _isLeft(successor) and (not sibling._left or sibling._left._color is BLACK) and sibling._right?._color is RED sibling._color = RED sibling._right?._color = BLACK @_rotateLeft sibling break # Case 6: sibling black, sibling right child red, node is left child. # Rotate left node parent. Swap color of parent and sibling. Paint # sibling right child black. sibling = _siblingOf successor sibling._color = successor._parent._color if _isLeft successor sibling._right._color = BLACK @_rotateRight successor._parent else sibling._left._color = BLACK @_rotateLeft successor._parent # Don't forget to detatch the artificially created leaf. if successor.isLeaf successor._parent?[_leftOrRight successor] = undefined _rotateLeft: (node) -> node._parent?[_leftOrRight node] = node._right node._right._parent = node._parent node._parent = node._right node._right = node._right._left node._parent._left = node node._right?._parent = node if not node._parent._parent? then @_root = node._parent _rotateRight: (node) -> node._parent?[_leftOrRight node] = node._left node._left._parent = node._parent node._parent = node._left node._left = node._left._right node._parent._right = node node._left?._parent = node if not node._parent._parent? then @_root = node._parent _isLeft = (node) -> node is node._parent._left _leftOrRight = (node) -> # No need to check if parent exist. It's never used this way. if _isLeft node then '_left' else '_right' _findNode = (startingNode, comparator) -> currentNode = startingNode foundNode = undefined while currentNode comparisonResult = comparator currentNode if comparisonResult is NODE_FOUND foundNode = currentNode break if comparisonResult is NODE_TOO_BIG currentNode = currentNode._left else if comparisonResult is NODE_TOO_SMALL currentNode = currentNode._right else if comparisonResult is STOP_SEARCHING break return foundNode _peekMinNode = (startingNode) -> _findNode startingNode, (node) -> if node._left then NODE_TOO_BIG else NODE_FOUND _peekMaxNode = (startingNode) -> _findNode startingNode, (node) -> if node._right then NODE_TOO_SMALL else NODE_FOUND _grandParentOf = (node) -> node._parent?._parent _uncleOf = (node) -> if not _grandParentOf node then return if _isLeft node._parent _grandParentOf(node)._right else _grandParentOf(node)._left _siblingOf = (node) -> if _isLeft node then node._parent._right else node._parent._left module.exports = RedBlackTree