Open In App

AVL Tree in Python

Last Updated : 16 Apr, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

The AVL tree in Python is a self–balancing binary search tree that guarantees the difference of the heights of the left and right subtrees of a node is at most 1. The algorithm is named after its inventors, Georgy Adelson-Velsky, and Evgenii Landis who published their paper in 1962.

The AVL tree keeps its balance through rotations subsequently after adding or removing nodes. This rotation mechanism balances the tree and allocates resources for the best depth search, insertion, and removal procedures.

In Python, AVL trees are implemented usually through classes. The structure respectively contains nodes representing individual elements and methods for insertion, deletion, and rotation to preserve the balance. This data structure shows its usage in the fields where fast search, insertion, and deleting operations are needed to maintain equilibrium and avoid problems.

AVL

AVL Tree

The above tree is AVL because the differences between the heights of left and right subtrees for every node are less than or equal to 1.

Operations on AVL Tree in Python

  1. Insertion: For insertion, we first perform the BST insertion and check the balance factor. If any side of the node gets unbalanced, the BST gets unset and then rotations are done to rebalance the tree.
  2. Deletion: The process of deletion in an AVL node is based on a standard method utilized for the BST (binary search tree) node deletion. The balancing of each formed node is performed after deletion and required rotations are maintained if any imbalance occurs.
  3. Searching: Searching in an AVL tree does just the work that a standard BST does as its search method. We evaluate what we are seeking against what the nodes hold and then move left or right accordingly and stop when we find the right value or hit a leaf node.
  4. Rotating the subtrees in an AVL Tree
    1. Left Rotation: When a node is inserted into the right subtree of the right subtree, and if the tree gets skewed a balance than we apply a single left rotation.
    2. Right Rotation: If the node is added to the left subtree of the left subtree, the new AVL tree may not be balanced, so we do a single right rotation.
    3. Left-Right Rotation: A rotation that begins with a left rotation and then is finished by a right one is referred to as a Left-Right rotation.
    4. Right-Left Rotation: A rotation that begins with a right rotation and then is finished by a left one is referred to as a right-left rotation.

Step-by-step approach for AVL Tree in Python:

  • Node Class: We begin by designing a unique Node class which is used to define individual Nodes of the AVL Tree. The node has a value, its left child, its right child and the height as attribute.
  • AVL Tree Class: We are going to construct “AVL Tree” class that will manage the AVL tree implementation. This class will entail methods for the insertions, deletion, searching and balancing.
  • Insertion: To add a new node, standard BST tree insertion is done. We make it happen by updating the height of each node from the inserted node to the root. For instance, in case any particular node not in balance, we use rotation to make tree balanced once again.
  • Deletion: Deletion of an AVL tree is usually made through the common BST deletion principle. Post deletion we update the height of the each node from the deleted node to the root. We apply a rotation if any node is unbalanced.
  • Rotations: We utilize left rotation, right rotation, left & right rotation and right-left rotation techniques to balance AVL tree in a proper manner.

Below is the implementation of the above approach:

Python
class Node:
    def __init__(self, value):
        self.value = value
        self.left = None
        self.right = None
        self.height = 1


class AVLTree:
    def __init__(self):
        self.root = None

    def height(self, node):
        if not node:
            return 0
        return node.height

    def balance(self, node):
        if not node:
            return 0
        return self.height(node.left) - self.height(node.right)

    def insert(self, root, value):
        if not root:
            return Node(value)
        elif value < root.value:
            root.left = self.insert(root.left, value)
        else:
            root.right = self.insert(root.right, value)

        root.height = 1 + max(self.height(root.left), self.height(root.right))
        balance = self.balance(root)

        # Left rotation
        if balance > 1 and value < root.left.value:
            return self.right_rotate(root)

        # Right rotation
        if balance < -1 and value > root.right.value:
            return self.left_rotate(root)

        # Left-Right rotation
        if balance > 1 and value > root.left.value:
            root.left = self.left_rotate(root.left)
            return self.right_rotate(root)

        # Right-Left rotation
        if balance < -1 and value < root.right.value:
            root.right = self.right_rotate(root.right)
            return self.left_rotate(root)

        return root

    def delete(self, root, value):
        if not root:
            return root

        if value < root.value:
            root.left = self.delete(root.left, value)
        elif value > root.value:
            root.right = self.delete(root.right, value)
        else:
            if not root.left:
                temp = root.right
                root = None
                return temp
            elif not root.right:
                temp = root.left
                root = None
                return temp

            temp = self.min_value_node(root.right)
            root.value = temp.value
            root.right = self.delete(root.right, temp.value)

        if not root:
            return root

        root.height = 1 + max(self.height(root.left), self.height(root.right))
        balance = self.balance(root)

        # Left rotation
        if balance > 1 and self.balance(root.left) >= 0:
            return self.right_rotate(root)

        # Right rotation
        if balance < -1 and self.balance(root.right) <= 0:
            return self.left_rotate(root)

        # Left-Right rotation
        if balance > 1 and self.balance(root.left) < 0:
            root.left = self.left_rotate(root.left)
            return self.right_rotate(root)

        # Right-Left rotation
        if balance < -1 and self.balance(root.right) > 0:
            root.right = self.right_rotate(root.right)
            return self.left_rotate(root)

        return root

    def left_rotate(self, z):
        y = z.right
        T2 = y.left

        y.left = z
        z.right = T2

        z.height = 1 + max(self.height(z.left), self.height(z.right))
        y.height = 1 + max(self.height(y.left), self.height(y.right))

        return y

    def right_rotate(self, z):
        y = z.left
        T3 = y.right

        y.right = z
        z.left = T3

        z.height = 1 + max(self.height(z.left), self.height(z.right))
        y.height = 1 + max(self.height(y.left), self.height(y.right))

        return y

    def min_value_node(self, root):
        current = root
        while current.left:
            current = current.left
        return current

    def search(self, root, value):
        if not root or root.value == value:
            return root
        if root.value < value:
            return self.search(root.right, value)
        return self.search(root.left, value)

    def insert_value(self, value):
        self.root = self.insert(self.root, value)

    def delete_value(self, value):
        self.root = self.delete(self.root, value)

    def search_value(self, value):
        return self.search(self.root, value)


# Example usage:
if __name__ == "__main__":
    tree = AVLTree()
    tree.insert_value(10)
    tree.insert_value(20)
    tree.insert_value(30)
    tree.insert_value(40)
    tree.insert_value(50)

    print("Tree after insertion:")
    # In-order traversal to print the tree
    def inorder_traversal(root):
        if root:
            inorder_traversal(root.left)
            print(root.value),
            inorder_traversal(root.right)

    inorder_traversal(tree.root)
    print()

    tree.delete_value(20)
    print("Tree after deletion of 20:")
    inorder_traversal(tree.root)
    print()

    result = tree.search_value(30)
    if result:
        print("Node found")
    else:
        print("Node not found")

Output
Tree after insertion:
10 20 30 40 50 ()
Tree after deletion of 20:
10 30 40 50 ()
Node found

Time Complexity :

  • Insertion: O(log n) average case, O(n) worst case
  • Deletion: O(log n) average case, O(n) worst case
  • Search: O(log n) average case, O(n) worst case

Space Complexity: O(n)

Related Articles



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads