diff --git a/pydatastructs/trees/__init__.py b/pydatastructs/trees/__init__.py index d7f978921..6b9df8a22 100644 --- a/pydatastructs/trees/__init__.py +++ b/pydatastructs/trees/__init__.py @@ -15,7 +15,8 @@ BinaryIndexedTree, CartesianTree, Treap, - SplayTree + SplayTree, + RedBlackTree ) __all__.extend(binary_trees.__all__) diff --git a/pydatastructs/trees/binary_trees.py b/pydatastructs/trees/binary_trees.py index b6713fb30..6020ec1d9 100644 --- a/pydatastructs/trees/binary_trees.py +++ b/pydatastructs/trees/binary_trees.py @@ -1,4 +1,4 @@ -from pydatastructs.utils import TreeNode, CartesianTreeNode +from pydatastructs.utils import TreeNode, CartesianTreeNode, RedBlackTreeNode from pydatastructs.miscellaneous_data_structures import Stack from pydatastructs.linear_data_structures import ( OneDimensionalArray, DynamicOneDimensionalArray) @@ -15,7 +15,8 @@ 'BinaryIndexedTree', 'CartesianTree', 'Treap', - 'SplayTree' + 'SplayTree', + 'RedBlackTree' ] class BinaryTree(object): @@ -1057,6 +1058,349 @@ def split(self, x): self.tree[self.root_idx].right = None return other +class RedBlackTree(SelfBalancingBinaryTree): + """ + Represents Red Black trees. + + Examples + ======== + + >>> from pydatastructs.trees import RedBlackTree as RB + >>> b = RB() + >>> b.insert(1, 1) + >>> b.insert(2, 2) + >>> child = b.tree[b.root_idx].right + >>> b.tree[child].data + 2 + >>> b.search(1) + 0 + + References + ========== + + .. [1] https://en.wikipedia.org/wiki/Red%E2%80%93black_tree + + See Also + ======== + + pydatastructs.trees.binary_tree.SelfBalancingBinaryTree + """ + + @classmethod + def methods(cls): + return ['insert', 'delete'] + + def _get_parent(self, node_idx): + return self.tree[node_idx].parent + + def _get_grand_parent(self, node_idx): + parent_idx=self._get_parent(node_idx) + return self.tree[parent_idx].parent + + def _get_sibling(self, node_idx): + parent_idx=self._get_parent(node_idx) + if parent_idx is None: + return None + node = self.tree[parent_idx] + if node_idx==node.left: + sibling_idx=node.right + return sibling_idx + else: + sibling_idx=node.left + return sibling_idx + + def _get_uncle(self, node_idx): + parent_idx=self._get_parent(node_idx) + return self._get_sibling(parent_idx) + + def _is_onleft(self, node_idx): + parent = self._get_parent(node_idx) + if self.tree[parent].left == node_idx: + return True + return False + + def _is_onright(self, node_idx): + if self._is_onleft(node_idx) is False: + return True + return False + + def __fix_insert(self, node_idx): + while self._get_parent(node_idx) is not None and \ + self.tree[self._get_parent(node_idx)].color == 1 and self.tree[node_idx].color==1: + parent_idx=self._get_parent(node_idx) + grand_parent_idx=self._get_grand_parent(node_idx) + uncle_idx = self._get_uncle(node_idx) + if uncle_idx is not None and self.tree[uncle_idx].color == 1: + self.tree[uncle_idx].color = 0 + self.tree[parent_idx].color = 0 + self.tree[grand_parent_idx].color = 1 + node_idx= grand_parent_idx + else: + self.tree[self.root_idx].is_root=False + if self._is_onright(parent_idx): + if self._is_onleft(node_idx): + self._right_rotate(parent_idx, node_idx) + node_idx=parent_idx + parent_idx=self._get_parent(node_idx) + node_idx=parent_idx + parent_idx=self._get_parent(node_idx) + self._left_rotate(parent_idx, node_idx) + elif self._is_onleft(parent_idx): + if self._is_onright(node_idx): + self._left_rotate(parent_idx, node_idx) + node_idx=parent_idx + parent_idx=self._get_parent(node_idx) + node_idx=parent_idx + parent_idx=self._get_parent(node_idx) + self._right_rotate(parent_idx, node_idx) + self.tree[node_idx].color = 0 + self.tree[parent_idx].color = 1 + self.tree[self.root_idx].is_root=True + if self.tree[node_idx].is_root: + break + self.tree[self.root_idx].color=0 + + def insert(self, key, data=None): + super(RedBlackTree, self).insert(key, data) + node_idx = super(RedBlackTree, self).search(key) + node = self.tree[node_idx] + new_node = RedBlackTreeNode(key, data) + new_node.parent = node.parent + new_node.left = node.left + new_node.right = node.right + self.tree[node_idx] = new_node + if node.is_root: + self.tree[node_idx].is_root = True + self.tree[node_idx].color=0 + elif self.tree[self.tree[node_idx].parent].color==1: + self.__fix_insert(node_idx) + + def _find_predecessor(self, node_idx): + while self.tree[node_idx].right is not None: + node_idx = self.tree[node_idx].right + return node_idx + + def _transplant_values(self, node_idx1, node_idx2): + parent = self.tree[node_idx1].parent + if self.tree[node_idx1].is_root and self._has_one_child(node_idx1): + self.tree[self.root_idx].key = self.tree[node_idx2].key + self.tree[self.root_idx].data = self.tree[node_idx2].data + self.tree[self.root_idx].left = self.tree[node_idx2].left + self.tree[self.root_idx].right = self.tree[node_idx2].right + self.tree[node_idx1].parent = None + return self.tree[self.root_idx].key + else: + self.tree[node_idx1].key = self.tree[node_idx2].key + self.tree[node_idx1].data = self.tree[node_idx2].data + + def _has_one_child(self, node_idx): + if self._is_leaf(node_idx) is False and self._has_two_child(node_idx) is False: + return True + return False + + def _is_leaf(self, node_idx): + if self.tree[node_idx].left is None and self.tree[node_idx].right is None: + return True + return False + + def _has_two_child(self, node_idx): + if self.tree[node_idx].left is not None and self.tree[node_idx].right is not None: + return True + return False + + def __has_red_child(self, node_idx): + left_idx = self.tree[node_idx].left + right_idx = self.tree[node_idx].right + if (left_idx is not None and self.tree[left_idx].color == 1) or \ + (right_idx is not None and self.tree[right_idx].color == 1): + return True + return False + + def _replace_node(self, node_idx): + if self._is_leaf(node_idx): + return None + elif self._has_one_child(node_idx): + if self.tree[node_idx].left is not None: + child = self.tree[node_idx].left + else: + child = self.tree[node_idx].right + return child + else: + return self._find_predecessor(self.tree[node_idx].left) + + def __walk1_walk_isblack(self, color, node_idx1): + if (node_idx1 is None or self.tree[node_idx1].color == 0) and (color == 0): + return True + return False + + def __left_left_siblingcase(self, node_idx): + left_idx = self.tree[node_idx].left + parent = self._get_parent(node_idx) + parent_color = self.tree[parent].color + self.tree[left_idx].color = self.tree[node_idx].color + self.tree[node_idx].color = parent_color + self._right_rotate(parent, node_idx) + + def __right_left_siblingcase(self, node_idx): + left_idx = self.tree[node_idx].left + parent = self._get_parent(node_idx) + parent_color = self.tree[parent].color + self.tree[left_idx].color = parent_color + self._right_rotate(node_idx, left_idx) + child = self._get_parent(node_idx) + self._left_rotate(parent, child) + + def __left_right_siblingcase(self, node_idx): + right_idx = self.tree[node_idx].right + parent = self._get_parent(node_idx) + parent_color = self.tree[parent].color + self.tree[right_idx].color = parent_color + self._left_rotate(node_idx, right_idx) + child = self._get_parent(node_idx) + self._right_rotate(parent, child) + + def __right_right_siblingcase(self, node_idx): + right_idx = self.tree[node_idx].right + parent = self._get_parent(node_idx) + parent_color = self.tree[parent].color + self.tree[right_idx].color = self.tree[node_idx].color + self.tree[node_idx].color = parent_color + self._left_rotate(parent, node_idx) + + def __fix_deletion(self, node_idx): + node = self.tree[node_idx] + color = node.color + while node_idx!= self.root_idx and color == 0: + sibling_idx = self._get_sibling(node_idx) + parent_idx = self._get_parent(node_idx) + if sibling_idx is None: + node_idx = parent_idx + continue + else: + if self.tree[sibling_idx].color == 1: + self.tree[self.root_idx].is_root = False + self.tree[parent_idx].color = 1 + self.tree[sibling_idx].color = 0 + if self._is_onleft(sibling_idx): + self._right_rotate(parent_idx, sibling_idx) + else: + self._left_rotate(parent_idx, sibling_idx) + self.tree[self.root_idx].is_root = True + continue + else: + if self.__has_red_child(sibling_idx): + self.tree[self.root_idx].is_root = False + left_idx = self.tree[sibling_idx].left + if self.tree[sibling_idx].left is not None and \ + self.tree[left_idx].color == 1: + if self._is_onleft(sibling_idx): + self.__left_left_siblingcase(sibling_idx) + else: + self.__right_left_siblingcase(sibling_idx) + else: + if self._is_onleft(sibling_idx): + self.__left_right_siblingcase(sibling_idx) + else: + self.__right_right_siblingcase(sibling_idx) + self.tree[self.root_idx].is_root = True + self.tree[parent_idx].color = 0 + else: + self.tree[sibling_idx].color = 1 + if self.tree[parent_idx].color == 0: + node_idx = parent_idx + continue + else: + self.tree[parent_idx].color = 0 + color = 1 + + def _remove_node(self, node_idx): + parent = self._get_parent(node_idx) + a = parent + if self._is_leaf(node_idx): + par_key, root_key = (self.tree[parent].key, self.tree[self.root_idx].key) + new_indices = self.tree.delete(node_idx) + if new_indices is not None: + a = new_indices[par_key] + self.root_idx = new_indices[root_key] + elif self._has_one_child(node_idx): + child = self._replace_node(node_idx) + parent = self._get_parent(node_idx) + par_key, root_key = (self.tree[parent].key, self.tree[self.root_idx].key) + new_indices = self.tree.delete(node_idx) + self._update_size(a) + + def _delete_root(self, node_idx, node_idx1): + if self._is_leaf(node_idx): + self.tree[self.root_idx].data = None + self.tree[self.root_idx].key = None + elif self._has_one_child(node_idx): + root_key = self._transplant_values(node_idx, node_idx1) + new_indices = self.tree.delete(node_idx1) + if new_indices is not None: + self.root_idx = new_indices[root_key] + return + + def __leaf_case(self, node_idx, node_idx1): + walk = node_idx + walk1 = node_idx1 + parent = self._get_parent(node_idx) + color = self.tree[walk].color + if parent is None: + self._delete_root(walk, walk1) + else: + if self.__walk1_walk_isblack(color, walk1): + self.__fix_deletion(walk) + else: + sibling_idx = self._get_sibling(walk) + if sibling_idx is not None: + self.tree[sibling_idx].color = 1 + if self._is_onleft(walk): + self.tree[parent].left = None + else: + self.tree[parent].right = None + self._remove_node(walk) + + def __one_child_case(self, node_idx, node_idx1): + walk = node_idx + walk1 = node_idx1 + walk_original_color = self.tree[walk].color + parent = self._get_parent(node_idx) + if parent is None: + self._delete_root(walk, walk1) + else: + if self._is_onleft(walk): + self.tree[parent].left = walk1 + else: + self.tree[parent].right = walk1 + self.tree[walk1].parent = parent + a = self._remove_node(walk) + if self.__walk1_walk_isblack(walk_original_color, walk1): + self.__fix_deletion(walk1) + else: + self.tree[walk1].color = 0 + + def __two_child_case(self, node_idx): + walk = node_idx + successor = self._replace_node(walk) + self._transplant_values(walk, successor) + walk = successor + walk1 = self._replace_node(walk) + return walk, walk1 + + def delete(self, key, **kwargs): + walk = super(RedBlackTree, self).search(key) + if walk is not None: + walk1 = self._replace_node(walk) + if self._has_two_child(walk): + walk, walk1 = self.__two_child_case(walk) + if self._is_leaf(walk): + self.__leaf_case(walk, walk1) + elif self._has_one_child(walk): + self.__one_child_case(walk, walk1) + return True + else: + return None + class BinaryTreeTraversal(object): """ Represents the traversals possible in diff --git a/pydatastructs/trees/tests/test_binary_trees.py b/pydatastructs/trees/tests/test_binary_trees.py index 22339e005..230cd2a1f 100644 --- a/pydatastructs/trees/tests/test_binary_trees.py +++ b/pydatastructs/trees/tests/test_binary_trees.py @@ -1,6 +1,6 @@ from pydatastructs.trees.binary_trees import ( BinarySearchTree, BinaryTreeTraversal, AVLTree, - ArrayForTrees, BinaryIndexedTree, SelfBalancingBinaryTree, SplayTree, CartesianTree, Treap) + ArrayForTrees, BinaryIndexedTree, SelfBalancingBinaryTree, SplayTree, CartesianTree, Treap, RedBlackTree) from pydatastructs.utils.raises_util import raises from pydatastructs.utils.misc_util import TreeNode from copy import deepcopy @@ -436,3 +436,184 @@ def test_SplayTree(): assert str(s) == ("[(1, 2000, 2000, None), (None, 1000, 1000, None)]") assert str(t) == ("[(None, 100, 100, None), '', (6, 200, 200, None), (4, 50, 50, None), " "(5, 30, 30, None), (None, 20, 20, None), (3, 55, 55, 0), '', '', '']") + +def test_RedBlackTree(): + tree = RedBlackTree() + tree.insert(10, 10) + tree.insert(18, 18) + tree.insert(7, 7) + tree.insert(15, 15) + tree.insert(16, 16) + tree.insert(30, 30) + tree.insert(25, 25) + tree.insert(40, 40) + tree.insert(60, 60) + tree.insert(2, 2) + tree.insert(17, 17) + tree.insert(6, 6) + + trav = BinaryTreeTraversal(tree) + in_order = trav.depth_first_search(order='in_order') + pre_order = trav.depth_first_search(order='pre_order') + assert [node.key for node in in_order] == [2, 6, 7, 10, 15, 16, 17, 18, 25, 30, 40, 60] + assert [node.key for node in pre_order] == [16, 10, 6, 2, 7, 15, 25, 18, 17, 40, 30, 60] + + tree = RedBlackTree() + tree.insert(10) + tree.insert(20) + tree.insert(30) + tree.insert(40) + tree.insert(50) + tree.insert(60) + tree.insert(70) + tree.insert(80) + tree.insert(90) + tree.insert(100) + tree.insert(110) + tree.insert(120) + tree.insert(130) + tree.insert(140) + tree.insert(150) + tree.insert(160) + tree.insert(170) + tree.insert(180) + + assert tree._get_sibling(7) is None + + trav = BinaryTreeTraversal(tree) + in_order = trav.depth_first_search(order='in_order') + pre_order = trav.depth_first_search(order='pre_order') + assert [node.key for node in in_order] == [10, 20, 30, 40, 50, 60, 70, 80, 90, + 100, 110, 120, 130, 140, 150, 160, 170, 180] + assert [node.key for node in pre_order] == [80, 40, 20, 10, 30, 60, 50, 70, 120, 100, + 90, 110, 140, 130, 160, 150, 170, 180] + + tree.delete(180) + tree.delete(130) + tree.delete(110) + tree.delete(190) + in_order = trav.depth_first_search(order='in_order') + pre_order = trav.depth_first_search(order='pre_order') + assert [node.key for node in in_order] == [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, + 120, 140, 150, 160, 170] + assert [node.key for node in pre_order] == [80, 40, 20, 10, 30, 60, 50, 70, 120, 100, + 90, 160, 140, 150, 170] + + tree.delete(170) + tree.delete(100) + tree.delete(60) + in_order = trav.depth_first_search(order='in_order') + pre_order = trav.depth_first_search(order='pre_order') + assert [node.key for node in in_order] == [10, 20, 30, 40, 50, 70, 80, 90, 120, 140, 150, 160] + assert [node.key for node in pre_order] == [80, 40, 20, 10, 30, 50, 70, 120, 90, 150, 140, 160] + + tree.delete(70) + tree.delete(140) + + in_order = trav.depth_first_search(order='in_order') + pre_order = trav.depth_first_search(order='pre_order') + assert [node.key for node in in_order] == [10, 20, 30, 40, 50, 80, 90, 120, 150, 160] + assert [node.key for node in pre_order] == [80, 40, 20, 10, 30, 50, 120, 90, 150, 160] + + tree.delete(150) + tree.delete(120) + in_order = trav.depth_first_search(order='in_order') + pre_order = trav.depth_first_search(order='pre_order') + assert [node.key for node in in_order] == [10, 20, 30, 40, 50, 80, 90, 160] + assert [node.key for node in pre_order] == [40, 20, 10, 30, 80, 50, 90, 160] + + tree.delete(50) + tree.delete(80) + in_order = trav.depth_first_search(order='in_order') + pre_order = trav.depth_first_search(order='pre_order') + assert [node.key for node in in_order] == [10, 20, 30, 40, 90, 160] + assert [node.key for node in pre_order] == [40, 20, 10, 30, 90, 160] + + tree.delete(30) + tree.delete(20) + in_order = trav.depth_first_search(order='in_order') + pre_order = trav.depth_first_search(order='pre_order') + assert [node.key for node in in_order] == [10, 40, 90, 160] + assert [node.key for node in pre_order] == [40, 10, 90, 160] + + tree.delete(10) + in_order = trav.depth_first_search(order='in_order') + pre_order = trav.depth_first_search(order='pre_order') + assert [node.key for node in in_order] == [40, 90, 160] + assert [node.key for node in pre_order] == [90, 40, 160] + + tree.delete(40) + tree.delete(90) + in_order = trav.depth_first_search(order='in_order') + pre_order = trav.depth_first_search(order='pre_order') + assert [node.key for node in in_order] == [160] + assert [node.key for node in pre_order] == [160] + + tree.delete(160) + in_order = trav.depth_first_search(order='in_order') + pre_order = trav.depth_first_search(order='pre_order') + assert [node.key for node in in_order if node.key is not None] == [] + assert [node.key for node in pre_order if node.key is not None] == [] + + tree = RedBlackTree() + tree.insert(50) + tree.insert(40) + tree.insert(30) + tree.insert(20) + tree.insert(10) + tree.insert(5) + + trav = BinaryTreeTraversal(tree) + in_order = trav.depth_first_search(order='in_order') + pre_order = trav.depth_first_search(order='pre_order') + assert [node.key for node in in_order] == [5, 10, 20, 30, 40, 50] + assert [node.key for node in pre_order] == [40, 20, 10, 5, 30, 50] + + tree.delete(50) + tree.delete(20) + tree.delete(30) + + in_order = trav.depth_first_search(order='in_order') + pre_order = trav.depth_first_search(order='pre_order') + assert [node.key for node in in_order] == [5, 10, 40] + assert [node.key for node in pre_order] == [10, 5, 40] + + tree = RedBlackTree() + tree.insert(10) + tree.insert(5) + tree.insert(20) + tree.insert(15) + + trav = BinaryTreeTraversal(tree) + in_order = trav.depth_first_search(order='in_order') + pre_order = trav.depth_first_search(order='pre_order') + assert [node.key for node in in_order] == [5, 10, 15, 20] + assert [node.key for node in pre_order] == [10, 5, 20, 15] + + tree.delete(5) + + in_order = trav.depth_first_search(order='in_order') + pre_order = trav.depth_first_search(order='pre_order') + assert [node.key for node in in_order] == [10, 15, 20] + assert [node.key for node in pre_order] == [15, 10, 20] + + tree = RedBlackTree() + tree.insert(10) + tree.insert(5) + tree.insert(20) + tree.insert(15) + tree.insert(2) + tree.insert(6) + + trav = BinaryTreeTraversal(tree) + in_order = trav.depth_first_search(order='in_order') + pre_order = trav.depth_first_search(order='pre_order') + assert [node.key for node in in_order] == [2, 5, 6, 10, 15, 20] + assert [node.key for node in pre_order] == [10, 5, 2, 6, 20, 15] + + tree.delete(10) + + in_order = trav.depth_first_search(order='in_order') + pre_order = trav.depth_first_search(order='pre_order') + assert [node.key for node in in_order] == [2, 5, 6, 15, 20] + assert [node.key for node in pre_order] == [6, 5, 2, 20, 15] diff --git a/pydatastructs/utils/__init__.py b/pydatastructs/utils/__init__.py index 5ed15e991..f8f2d8c46 100644 --- a/pydatastructs/utils/__init__.py +++ b/pydatastructs/utils/__init__.py @@ -10,6 +10,7 @@ AdjacencyMatrixGraphNode, GraphEdge, Set, - CartesianTreeNode + CartesianTreeNode, + RedBlackTreeNode ) __all__.extend(misc_util.__all__) diff --git a/pydatastructs/utils/misc_util.py b/pydatastructs/utils/misc_util.py index f7c492bc8..54b801d29 100644 --- a/pydatastructs/utils/misc_util.py +++ b/pydatastructs/utils/misc_util.py @@ -7,7 +7,8 @@ 'AdjacencyMatrixGraphNode', 'GraphEdge', 'Set', - 'CartesianTreeNode' + 'CartesianTreeNode', + 'RedBlackTreeNode' ] _check_type = lambda a, t: isinstance(a, t) @@ -85,6 +86,32 @@ def __str__(self): """ return str((self.left, self.key, self.priority, self.data, self.right)) +class RedBlackTreeNode(TreeNode): + """ + Represents node in red-black trees. + + Parameters + ========== + + key + Required for comparison operations. + data + Any valid data to be stored in the node. + color + 0 for black and 1 for red. + + """ + __slots__ = ['key', 'data', 'color'] + + @classmethod + def methods(cls): + return ['__new__'] + + def __new__(cls, key, data=None): + obj = TreeNode.__new__(cls, key, data) + obj.color = 1 + return obj + class BinomialTreeNode(TreeNode): """ Represents node in binomial trees. diff --git a/pydatastructs/utils/tests/test_code_quality.py b/pydatastructs/utils/tests/test_code_quality.py index 0c56a9997..bd3e7f21d 100644 --- a/pydatastructs/utils/tests/test_code_quality.py +++ b/pydatastructs/utils/tests/test_code_quality.py @@ -96,7 +96,7 @@ def _apis(): pyds.DisjointSetForest, pyds.BinomialTree, pyds.TreeNode, pyds.MAryTreeNode, pyds.LinkedListNode, pyds.BinomialTreeNode, pyds.AdjacencyListGraphNode, pyds.AdjacencyMatrixGraphNode, pyds.GraphEdge, pyds.Set, pyds.BinaryIndexedTree, - pyds.CartesianTree, pyds.CartesianTreeNode, pyds.Treap] + pyds.CartesianTree, pyds.CartesianTreeNode, pyds.Treap, pyds.RedBlackTreeNode, pyds.RedBlackTree] def test_public_api(): pyds = pydatastructs diff --git a/pydatastructs/utils/tests/test_misc_util.py b/pydatastructs/utils/tests/test_misc_util.py index f548caa90..a390b54e2 100644 --- a/pydatastructs/utils/tests/test_misc_util.py +++ b/pydatastructs/utils/tests/test_misc_util.py @@ -1,5 +1,5 @@ from pydatastructs.utils import (AdjacencyListGraphNode, AdjacencyMatrixGraphNode, - GraphEdge, BinomialTreeNode, MAryTreeNode, CartesianTreeNode) + GraphEdge, BinomialTreeNode, MAryTreeNode, CartesianTreeNode,RedBlackTreeNode) from pydatastructs.utils.raises_util import raises def test_AdjacencyListGraphNode(): @@ -42,3 +42,7 @@ def test_MAryTreeNode(): def test_CartesianTreeNode(): c = CartesianTreeNode(1, 1, 1) assert str(c) == "(None, 1, 1, 1, None)" + +def test_RedBlackTreeNode(): + c = RedBlackTreeNode(1, 1) + assert str(c) == "(None, 1, 1, None)"