Python icon indicating copy to clipboard operation
Python copied to clipboard

Python/data_structures/binary_tree/red_black_tree.py remove error

Open yulunyue opened this issue 3 years ago • 9 comments

''' remove can not change root '''

def test_self(): rb = [10, 11, 12, 6, 7, 8, 9, 2, 1, 18, 13, 14, 15, 16] tree = RedBlackTree(5) for d in rb: tree = tree.insert(d)

def u(t: RedBlackTree):
    print(t.label, t.color)
    if t.left:
        u(t.left)
    if t.right:
        u(t.right)

def u1(t):
    print("---")
    u(tree)
    print("---")
u1(tree)
for d in [10, 9, 8, 7, 6, 5, 2]:
    tree.remove(d)
    u1(tree)

yulunyue avatar May 21 '22 07:05 yulunyue

Hi, thank you for your report! Feel free to make a PR to fix this

poyea avatar May 26 '22 14:05 poyea

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Jul 10 '22 15:07 stale[bot]

Is this fixed?

poyea avatar Jul 11 '22 14:07 poyea

Are we supposed to get errors running this code? Because I think it has been fixed.

Bjiornulf avatar Jul 14 '22 06:07 Bjiornulf

maybe not

def print_tree(node: RedBlackTree):
    rt = ['---']

    def util(nd: RedBlackTree, indent: int):

        if nd.right:
            util(nd.right, indent+4)
        text = '%s%s%s' % (indent*" ", ['b', 'r'][nd.color], nd.label)
        rt.append(text)
        if nd.left:
            util(nd.left, indent+4)
    util(node, 0)
    rt.append('---')
    print("\n".join(rt))


def test_insertion_speed() -> bool:
    """Test that the tree balances inserts to O(log(n)) by doing a lot
    of them.
    """
    t = RedBlackTree(0)
    for i in range(1, 5):
        t = t.insert(i)
    print_tree(t)
    print("insert finish")
    t = t.remove(1)
    print_tree(t)
    print("remove finish")
    return True

i test with code get result red_black_bug

yulunyue avatar Jul 14 '22 07:07 yulunyue

maybe you need a root refer 算法导论

class TreeManage:
    nil = None

    def __init__(self, cls) -> None:
        self.name = cls.__name__
        self.nil: TreeBase = cls(None, None, self)
        self.nil.set_color_black()
        self.root: TreeBase = cls(None, self.nil, self)
        self.root.set_color_black()

    def set_root(self, node):
        self.root = node
        node.parent = self.nil
        node.set_color_black()

    def insert(self, val):
        self.root.insert(val)

    def remove(self, val):
        self.root.remove(val)

yulunyue avatar Jul 14 '22 07:07 yulunyue

from typing import List
from common import function


class TreeBase:
    RED = 1
    BLACK = 0

    def __init__(self, val, parent, manage) -> None:
        self.val = val
        self.parent: TreeBase = parent
        self.childs: List[TreeBase] = [None]*2
        self.manage: TreeManage = manage

    @property
    def text(self):
        return "%s" % self.val

    @property
    def left(self):
        return self.childs[0]

    @property
    def right(self):
        return self.childs[len(self.childs)-1]

    @property
    def brother(self):
        if self.is_left():
            return self.parent.right
        if self.is_right():
            return self.parent.left

    @property
    def uncle(self):
        return self.parent.brother

    def is_left(self):
        return self.parent and self.parent.left == self

    def is_right(self):
        return self.parent and self.parent.right == self

    def set_left(self, node):
        self.childs[0] = node
        if node:
            node.set_parent(self)

    def set_right(self, node):
        self.childs[len(self.childs)-1] = node
        if node:
            node.set_parent(self)

    def repair(self):
        pass

    def set_parent(self, node):
        self.parent = node
        if self.parent.is_nil():
            self.manage.set_root(self)

    def replace_by(self, node):
        if self.is_left():
            self.parent.set_left(node)
        elif self.is_right():
            self.parent.set_right(node)
        if node:
            node.set_parent(self.parent)

    def rotate_left(self):
        node = self.right
        self.set_right(node.left)
        self.replace_by(node)
        node.set_left(self)

    def rotate_right(self):
        '''
      5                 3
   3     6            2   5
 2  4        ->      0 1 4 6
0 1
        '''
        node = self.left
        self.set_left(node.right)
        self.replace_by(node)
        node.set_right(self)

    def get_min(self):
        if self.left is None or self.left.is_nil():
            return self
        return self.left.get_min()

    def get_max(self):
        if self.right and not self.right.is_nil():
            return self
        return self.right.get_max()

    def big(self, val):
        return self.val > val

    def small(self, val):
        return self.val < val

    def is_nil(self):
        return self.val is None

    def is_red(self):
        return self.color == TreeBase.RED

    def set_color_red(self):
        if self.is_nil():
            return
        self.color = TreeBase.RED

    def set_color_black(self):
        self.color = TreeBase.BLACK

    def is_black(self):
        return self.color == TreeBase.BLACK

    def to_str(self):
        split_line = '-------------------'
        rt = [split_line]

        def util(node: TreeBase, indent):
            if node is None or node.is_nil():
                return
            util(node.right, indent+4)
            rt.append("%s%s" %
                      (' '*indent, node.text))
            util(node.left, indent+4)
        util(self, 0)
        rt.append(split_line)
        return "\n".join(rt)

    def get_err_msg(self):
        return ""

    def insert(self, val):
        pass

    def remove(self, val):
        pass


class TreeManage:
    nil = None

    def __init__(self, cls) -> None:
        self.name = cls.__name__
        self.nil: TreeBase = cls(None, None, self)
        self.nil.set_color_black()
        self.root: TreeBase = cls(None, self.nil, self)
        self.root.set_color_black()

    def set_root(self, node):
        self.root = node
        node.parent = self.nil
        node.set_color_black()

    def insert(self, val):
        self.root.insert(val)

    def remove(self, val):
        self.root.remove(val)


def tree_insert_remove(m: TreeManage, rb_insert_array, rb_remove_array):

    def util(method, array):
        for i, value in enumerate(array):
            last_state = m.root.to_str()
            if method == "insert":
                m.root.insert(value)
            else:
                m.root.remove(value)
            err_msg = m.root.get_err_msg()
            if err_msg:
                print(last_state)
                print("%s v:%s e:%s" %
                      (method, value, err_msg))
                print(m.root.to_str())
                print(rb_insert_array, rb_remove_array)
                exit()
    util("insert", rb_insert_array)
    util("remove", rb_remove_array)


def test_random_tree(m: TreeManage, n):
    from random import randint

    def rand_array(array):
        rt = []
        while len(array):
            index = randint(0, len(array)-1)
            rt.append(array.pop(index))
        return rt
    rb_insert_array = rand_array(list(range(0, n)))
    rb_remove_array = rand_array(list(range(0, n)))
    tree_insert_remove(m, rb_insert_array, rb_remove_array)


def test_all(m: TreeManage):
    input_case = [
        [7, 16, 9, 2, 8, 0, 6, 12, 11, 3, 13, 10, 15, 5, 4, 14, 1],
        [14, 13, 2, 10, 6, 16, 11, 7, 1, 0, 8, 3, 4, 5, 15, 12, 9],
        [10, 5, 12, 7, 9, 13, 1, 4, 8, 2, 6, 3, 0, 11, 14],
        [7, 0, 6, 10, 2, 3, 11, 4, 14, 5, 13, 1, 8, 9, 12],
        [0, 3, 1, 4, 5, 2, 6, 7, 9, 8, 10, 12, 11, 13, 14],
        [4, 0, 8, 9, 1, 11, 2, 3,  12, 13, 5, 6, 7, 10, 14],
        [1, 3, 10, 12, 6, 8, 9, 2, 4, 14, 11, 0, 5, 7, 13],
        [9, 6, 5, 3, 8, 12, 7, 13, 11, 10, 1, 4, 2, 14, 0]
    ]
    for i in range(0, len(input_case), 2):
        tree_insert_remove(m, input_case[i], input_case[i+1])
    for i in range(50):
        test_random_tree(m, i)
from .tree_base import TreeBase


class RedBlackTree(TreeBase):
    def __init__(self, val, parent, manage) -> None:
        super().__init__(val, parent, manage)
        if self.manage.nil:
            self.set_left(self.manage.nil)
            self.set_right(self.manage.nil)
        self.color = RedBlackTree.RED

    @property
    def text(self):
        return "%s:%s" % (['b', 'r'][self.color], 'n' if self.is_nil() else self.val)

    def insert_repair(self):
        # print(self.text, self.parent.text)
        if self.parent.is_nil():
            self.manage.set_root(self)
            return
        if self.parent.is_red():
            if self.uncle and self.uncle.is_red():
                self.parent.set_color_black()
                self.uncle.set_color_black()
                self.parent.parent.set_color_red()
                self.parent.parent.insert_repair()
            else:
                if self.parent.is_right():
                    if self.is_left():
                        self.parent.rotate_right()
                        self.right.insert_repair()
                    elif self.is_right():
                        self.parent.set_color_black()
                        self.parent.parent.set_color_red()
                        self.parent.parent.rotate_left()
                elif self.parent.is_left():
                    if self.is_right():
                        self.parent.rotate_left()
                        self.left.insert_repair()
                    elif self.is_left():
                        self.parent.set_color_black()
                        self.parent.parent.set_color_red()
                        self.parent.parent.rotate_right()

    def insert(self, val):
        if self.is_nil():
            self.val = val
        elif self.small(val):
            if self.right is None or self.right.is_nil():
                self.set_right(RedBlackTree(val, self, self.manage))
                self.right.insert_repair()
            else:
                self.right.insert(val)
        elif self.big(val):
            if self.left is None or self.left.is_nil():
                self.set_left(RedBlackTree(val, self, self.manage))
                self.left.insert_repair()
            else:
                self.left.insert(val)

    def remove_repair(self):
        if self.brother is None:
            return
        if self.brother.is_black():
            if self.brother.left.is_black() and self.brother.right.is_black():
                self.brother.set_color_red()
                if self.parent.is_red():
                    self.parent.set_color_black()
                else:
                    self.parent.remove_repair()  # 最难的代码
            elif self.brother.is_right():
                if self.brother.right.is_red():
                    self.brother.color = self.parent.color
                    self.brother.right.set_color_black()
                    self.parent.set_color_black()
                    self.parent.rotate_left()
                elif self.brother.left.is_red() and self.brother.right.is_black():
                    self.brother.left.set_color_black()
                    self.brother.set_color_red()
                    self.brother.rotate_right()
                    self.remove_repair()
            elif self.brother.is_left():
                if self.brother.left.is_red():
                    self.brother.color = self.parent.color
                    self.brother.left.set_color_black()
                    self.parent.set_color_black()
                    self.parent.rotate_right()
                elif self.brother.right.is_red() and self.brother.left.is_black():
                    self.brother.right.set_color_black()
                    self.brother.set_color_red()
                    self.brother.rotate_left()
                    self.remove_repair()

        elif self.brother.is_red():
            self.brother.set_color_black()
            self.parent.set_color_red()
            if self.brother.is_right():
                self.parent.rotate_left()
            elif self.brother.is_left():
                self.parent.rotate_right()
            self.remove_repair()

    def remove(self, val):
        # print("remove", self.text, val)
        if self.is_nil():
            return
        elif self.val == val:
            if not self.left.is_nil() and not self.right.is_nil():
                z = self.right.get_min()
                self.val = z.val
                self.right.remove(z.val)
            elif not self.left.is_nil():
                self.replace_by(self.left)
                self.left.set_color_black()
            elif not self.right.is_nil():
                self.replace_by(self.right)
                self.right.set_color_black()
            else:
                if self == self.manage.root:
                    self.val = None
                    return
                if self.is_black():
                    self.remove_repair()
                self.replace_by(self.manage.nil)

        elif self.small(val):
            self.right.remove(val)
        elif self.big(val):
            self.left.remove(val)

    def get_err_msg(self):
        if self.manage and self.manage.root.is_red():
            return "root red"

        class Rt:
            error_msg = ""
            last_leaf_depth = None

        def util(node: RedBlackTree, depth):
            if Rt.error_msg or node is None:
                return
            if node.parent.is_red() and node.is_red():
                Rt.error_msg = "parent and son is red"
                return
            if node.is_nil():
                depth += 1
                if Rt.last_leaf_depth is not None and Rt.last_leaf_depth != depth:
                    Rt.error_msg = "depth err %s==%s" % (
                        Rt.last_leaf_depth, depth)
                Rt.last_leaf_depth = depth
            else:
                if node.is_black():
                    depth += 1
                util(node.left, depth)
                util(node.right, depth)
        util(self, 0)
        return Rt.error_msg

yulunyue avatar Jul 14 '22 07:07 yulunyue

Are we supposed to get errors running this code? Because I think it has been fixed.

I'm not sure because I see there are some tests around remove. How did you figure out there's an error?

poyea avatar Jul 14 '22 14:07 poyea

I don't know, I didn't fully understand the problem in the ticket but wanted to help. I am not the one who posted the issue.

Bjiornulf avatar Jul 14 '22 15:07 Bjiornulf

The issue still exists. I tried using this library (Red Black Tree) and ran into this error - cannot delete root with class remove method.

# Minimal code to reproduce:
$ python
>>> from red_black_tree import RedBlackTree
>>> tree = RedBlackTree()
>>> tree.insert(1)
'1 blk'
>>> print(tree)
'1 blk'
>>> tree.remove(1)
'None blk'
>>> print(tree)
'1 blk'
# How it should work using TheAlgorithms AVL Tree as an example:
$ python
>>> from avl_tree import AVLtree
>>> tree = AVLtree()
>>> tree.insert(1)
insert:1
>>> print(tree)
 1
*************************************
>>> tree.del_node(1)
delete:1
>>> print(tree)

>>>

I will take a stab at producing a PR...

sockduct avatar Oct 18 '22 03:10 sockduct

Created PR #7439 to fix above issue and corresponding test. Any comments welcome.

sockduct avatar Oct 20 '22 01:10 sockduct