Esempio n. 1
0
    def __array_to_bst(self, data: list) -> BinaryTreeNode:
        """
        Converts a sorted array into a height-balanced BST

        Parameters:
            data (list): sorted data array

        Returns:
            BinaryTreeNode: root node of built BST
        """
        # length of data in current recursion
        n = len(data)

        # no input data -> no tree
        if n == 0:
            return None
        # single input data element -> node with value
        elif n == 1:
            return BinaryTreeNode(data[0])
        # multiple data elements are recursively split by the median
        else:
            # root node is the median
            mid = n // 2
            root = BinaryTreeNode(data[mid])

            # left and right subtrees are built from left and right partition
            root.left = self.__array_to_bst(data[:mid])
            root.right = self.__array_to_bst(data[mid + 1:])
            return root
Esempio n. 2
0
    def __delete(self, root: BinaryTreeNode, key: int) -> BinaryTreeNode:
        """
        Delete value from BST by recursively traversing the tree

        Parameters:
            root (BinaryTreeNode): root node of subtree
            val (int): key to delete

        Returns:
            BinaryTreeNode: root of node of subtree
        """
        # if root does not exist, there is no more to traverse
        if root is None:
            return root

        # if key is smaller than root, the key must be in the left subtree
        if key < root.data:
            root.left = self.__delete(root.left, key)
        # if key is greater than root, the key must be in the right subtree
        elif key > root.data:
            root.right = self.__delete(root.right, key)

        # else the key is equal to the root, and the root must be deleted
        else:
            # if root has no children it is deleted; if root has only one child it becomes the new root
            if root.left is None:
                child_node = root.right
                root = None
                return child_node
            elif root.right is None:
                child_node = root.left
                root = None
                return child_node

            # if node has two children, the inorder succesor is chosen as new root
            # inorder-succesor is the smallest in the right subtree
            temp = self.__get_min_value(root.right)

            # the value of the new root is copied to the current root
            root.key = temp.key

            # the inorder succesor is then deleted
            root.right = self.__delete(root.right, temp.key)

        return root
Esempio n. 3
0
    def __invert_tree(self, root: BinaryTreeNode) -> BinaryTreeNode:
        """
        Inverts the tree by recusively inverting the subtrees

        Parameters:
            root (BinaryTreeNode): root node of BST

        Returns:
            BinaryTreeNode: the root node of the inverted BST
        """
        if root is None:
            return None
        else:
            # recursively invert left subtree
            left = self.__invert_tree(root.left)

            # recursively invert right subtree
            right = self.__invert_tree(root.right)

            # invert children
            root.left = right
            root.right = left
            return root
Esempio n. 4
0
    def __insert(self, root: BinaryTreeNode, val: int) -> BinaryTreeNode:
        """
        Inserts value into BST by recursively traversing the tree

        Parameters:
            root (BinaryTreeNode): root node of subtree
            val (int): value to insert

        Returns:
            BinaryTreeNode: root of node of subtree
        """
        # if no root exists, the new node is the root
        if root is None:
            return BinaryTreeNode(val)

        # if value to insert is smaller than root, traverse down left subtree
        if val < root.data:
            root.left = self.__insert(root.left, val)
        # otherwise, traverse down right subtree
        else:
            root.right = self.__insert(root.right, val)

        return root
Esempio n. 5
0
    stack = [(root, -math.inf, math.inf)]

    while True:

        current, lower, upper = stack.pop()

        if (current.value <= lower) or (current.value >= upper):
            return False

        if current.left:
            stack.append((current.left, lower, current.value))

        if current.right:
            stack.append((current.right, current.value, upper))

        if not stack:
            break

    return True


tree = BinaryTreeNode(6)
tree.left = BinaryTreeNode(3)
tree.right = BinaryTreeNode(9)
tree.left.left = BinaryTreeNode(1)
tree.left.right = BinaryTreeNode(7)
tree.right.left = BinaryTreeNode(7)
tree.right.right = BinaryTreeNode(10)

print(valid_bst(tree))
Esempio n. 6
0
    def __delete(self, root: BinaryTreeNode, key: int) -> BinaryTreeNode:
        """
        Delete value from BST by recursively traversing the tree.

        The tree is balanced by traveling up the tree from a newly inserted node until
        an unbalance node (z) is encountered. The subtree of the unbalanced node is then balanced
        by doing one of four possible operations that rotate the node and its child nodes.
        Selecting the right operation is a matter of finding the height difference between
        the two subtrees to determine whether they are balanced, and finding the two tallest children
        of z (meaning the children that have highest subtress themselves) to determine whether 
        they are left-left, left-right, right-right or right-left children.

        Parameters:
            root (BinaryTreeNode): root node of subtree
            val (int): key to delete

        Returns:
            BinaryTreeNode: root of node of subtree
        """
        # if root does not exist, there is no more to traverse
        if root is None:
            return root

        # if key is smaller than root, the key must be in the left subtree
        if key < root.data:
            root.left = self.__delete(root.left, key)
        # if key is greater than root, the key must be in the right subtree
        elif key > root.data:
            root.right = self.__delete(root.right, key)

        # else the key is equal to the root, and the root must be deleted
        else:
            # if root has no children it is deleted; if root has only one child it becomes the new root
            if root.left is None:
                child_node = root.right
                root = None
                return child_node
            elif root.right is None:
                child_node = root.left
                root = None
                return child_node

            # if node has two children, the inorder succesor is chosen as new root
            # inorder-succesor is the smallest in the right subtree
            temp = self.__get_min_value(root.right)

            # the value of the new root is copied to the current root
            root.data = temp.data

            # the inorder succesor is then deleted
            root.right = self.__delete(root.right, temp.data)

        # update height attribute of parent node
        root.height = 1 + max(self.__get_height(root.left),
                              self.__get_height(root.right))

        # get 'balance factor' (height difference between left and right subtrees)
        balance = self.get_height_diff(root)

        # balance subtree if unbalanced
        # Case 1: left-left rotation
        # z is the unbalance node, y is the taller child of z, x is the taller child of y
        #                      -    z    -
        #                     |           |
        #                 -   y   -       T4
        #                |         |
        #              - x -       T3
        #             |     |
        #             T1    T2
        # balance factor is greater than 1, while x and y are both left children
        if balance > 1 and self.get_height_diff(root.left) >= 0:
            return self.right_rotate(root)

        # Case 2: left-right rotation
        # z is the unbalance node, y is the taller child of z, x is the taller child of y
        #                      -    z    -
        #                     |           |
        #                 -   y   -       T4
        #                |         |
        #                T1      - x -
        #                       |     |
        #                       T1    T2
        # balance factor is greater than 1, while y is a left child and x is a right child
        if balance > 1 and self.get_height_diff(root.left) < 0:
            root.left = self.left_rotate(root.left)
            return self.right_rotate(root)

        # Case 3: right-right rotation
        # z is the unbalance node, y is the taller child of z, x is the taller child of y
        #                      -    z    -
        #                     |           |
        #                     T1      -   y   -
        #                            |         |
        #                            T2      - x -
        #                                   |     |
        #                                   T3    T4
        # balance factor is smaller than 1, while x and y are both right children
        if balance < -1 and self.get_height_diff(root.right) <= 0:
            return self.left_rotate(root)

        # Case 4: right-left rotation
        # z is the unbalance node, y is the taller child of z, x is the taller child of y
        #                      -    z    -
        #                     |           |
        #                     T1      -   y   -
        #                            |         |
        #                          - x -       T4
        #                         |     |
        #                         T2    T3
        # balance factor is smaller than 1, while y is a right child and x is a left child
        if balance < -1 and self.get_height_diff(root.right) > 0:
            root.right = self.right_rotate(root.right)
            return self.left_rotate(root)

        return root
Esempio n. 7
0
    def __insert(self, root: BinaryTreeNode, data: int):
        """
        Inserts a node into the AVL tree while ensuring the tree remains balanced.

        The tree is balanced by traveling up the tree from a newly inserted node until
        an unbalance node (z) is encountered. The subtree of the unbalanced node is then balanced
        by doing one of four possible operations that rotate the node and its child nodes.
        Selecting the right operation is a matter of finding the height difference between
        the two subtrees to determine whether they are balanced, and comparing the values of 
        the last two children (x and y) along the traveled path to determine whether they are 
        left-left, left-right, right-right or right-left children.

        Parameters:
            root (BinaryTreeNode): root node of subtree
            data (int): data value of node to be inserted
        """
        # insert new node into tree as in a traditional BST
        if not root:
            return BinaryTreeNode(data)
        elif data < root.data:
            root.left = self.__insert(root.left, data)
        else:
            root.right = self.__insert(root.right, data)

        # update height attribute of parent node
        root.height = 1 + max(self.__get_height(root.left),
                              self.__get_height(root.right))

        # get 'balance factor' (height difference between left and right subtrees)
        balance = self.get_height_diff(root)

        # balance subtree if unbalanced
        # Case 1: left-left rotation
        # z is the unbalance node, y is the first child, and x is the grandchild
        #                      -    z    -
        #                     |           |
        #                 -   y   -       T4
        #                |         |
        #              - x -       T3
        #             |     |
        #             T1    T2
        # balance factor is greater than 1, while x and y are both left children
        if balance > 1 and data < root.left.data:
            return self.right_rotate(root)

        # Case 2: left-right rotation
        # z is the unbalance node, y is the first child, and x is the grandchild
        #                      -    z    -
        #                     |           |
        #                 -   y   -       T4
        #                |         |
        #                T1      - x -
        #                       |     |
        #                       T1    T2
        # balance factor is greater than 1, while y is a left child and x is a right child
        if balance > 1 and data > root.left.data:
            root.left = self.left_rotate(root.left)
            return self.right_rotate(root)

        # Case 3: right-right rotation
        # z is the unbalance node, y is the first child, and x is the grandchild
        #                      -    z    -
        #                     |           |
        #                     T1      -   y   -
        #                            |         |
        #                            T2      - x -
        #                                   |     |
        #                                   T3    T4
        # balance factor is greater than 1, while x and y are both right children
        if balance < -1 and data > root.right.data:
            return self.left_rotate(root)

        # Case 4: right-left rotation
        # z is the unbalance node, y is the first child, and x is the grandchild
        #                      -    z    -
        #                     |           |
        #                     T1      -   y   -
        #                            |         |
        #                          - x -       T4
        #                         |     |
        #                         T2    T3
        # balance factor is smaller than 1, while y is a right child and x is a left child
        if balance < -1 and data < root.right.data:
            root.right = self.right_rotate(root.right)
            return self.left_rotate(root)

        return root