Welcome to the daily solutions of our PROBLEM OF THE DAY (POTD). We will discuss the entire problem step-by-step and work towards developing an optimized solution. This will not only help you brush up on your concepts of AVL Trees but will also help you build up problem-solving skills.
POTD 23 November: AVL Tree Insertion:
Given an AVL tree and N values to be inserted in the tree. Write a function to insert elements into the given AVL tree.
Note:
- The tree will be checked after each insertion.
- If it violates the properties of balanced BST, an error message will be printed followed by the inorder traversal of the tree at that moment.
- If instead all insertions are successful, inorder traversal of the tree will be printed.
Example:
Input:
N = 3
Values to be inserted = {5,1,4}
Output:
1 4 5
Explanation:
Value to be inserted = 5
5
Value to be inserted = 1
5
/
1
Value to be inserted = 4
5 4
/ LR rotation / \
1 ———–> 1 5
\
4
Therefore the inorder of the final tree will be 1, 4, 5.Input:
N = 7
Values to be inserted = {21,26,30,9,4,14,28}
Output:
4 9 14 21 26 28 30
Explanation:
Value to be inserted = 21
21
Value to be inserted = 26
21
\
26
Value to be inserted = 30
21 26
\ LL rotation / \
26 ———–> 21 30
\
30
Value to be inserted = 9
26
/ \
21 30
/
9
Value to be inserted = 4
26 26
/ \ / \
21 30 9 30
/ RR rotation / \
9 ———–> 4 21
/
4
Value to be inserted = 14
26 21
/ \ / \
9 30 9 26
/ \ LR rotation / \ \
4 21 ———–> 4 14 30
/
14
Value to be inserted = 28
21 21
/ \ / \
9 26 9 28
/ \ \ RL rotation / \ / \
4 14 30 ———–> 4 14 26 30
/
28
Therefore the inorder of the final tree will be 4, 9, 14, 21, 26, 28, 30.
Insertion in an AVL Tree:
Steps to follow for insertion:
Let the newly inserted node be w
- Perform standard BST insert for w.
- Starting from w, travel up and find the first unbalanced node. Let z be the first unbalanced node, y be the child of z that comes on the path from w to z and x be the grandchild of z that comes on the path from w to z.
- Re-balance the tree by performing appropriate rotations on the subtree rooted with z. There can be 4 possible cases that need to be handled as x, y and z can be arranged in 4 ways.
-
Following are the possible 4 arrangements:
- y is the left child of z and x is the left child of y (Left Left Case)
- y is the left child of z and x is the right child of y (Left Right Case)
- y is the right child of z and x is the right child of y (Right Right Case)
- y is the right child of z and x is the left child of y (Right Left Case)
Following are the operations to be performed in above mentioned 4 cases. In all of the cases, we only need to re-balance the subtree rooted with z and the complete tree becomes balanced as the height of the subtree (After appropriate rotations) rooted with z becomes the same as it was before insertion.
1. Left Left Case
T1, T2, T3 and T4 are subtrees.
z y
/ \ / \
y T4 Right Rotate (z) x z
/ \ – – – – – – – – -> / \ / \
x T3 T1 T2 T3 T4
/ \
T1 T2
2. Left Right Case
z z x
/ \ / \ / \
y T4 Left Rotate (y) x T4 Right Rotate(z) y z
/ \ – – – – – – – – -> / \ – – – – – – – -> / \ / \
T1 x y T3 T1 T2 T3 T4
/ \ / \
T2 T3 T1 T2
3. Right Right Case
z y
/ \ / \
T1 y Left Rotate(z) z x
/ \ – – – – – – – -> / \ / \
T2 x T1 T2 T3 T4
/ \
T3 T4
4. Right Left Case
z z x
/ \ / \ / \
T1 y Right Rotate (y) T1 x Left Rotate(z) z y
/ \ – – – – – – – – -> / \ – – – – – – – -> / \ / \
x T4 T2 y T1 T2 T3 T4
/ \ / \
T2 T3 T3 T4
Illustration of Insertion at AVL Tree:
Approach:
The idea is to use recursive BST insert, after insertion, we get pointers to all ancestors one by one in a bottom-up manner. So we don’t need a parent pointer to travel up. The recursive code itself travels up and visits all the ancestors of the newly inserted node.
Follow the steps mentioned below to implement the idea:
- Perform the normal BST insertion.
- The current node must be one of the ancestors of the newly inserted node. Update the height of the current node.
- Get the balance factor (left subtree height – right subtree height) of the current node.
- If the balance factor is greater than 1, then the current node is unbalanced and we are either in the Left Left case or left Right case. To check whether it is left left case or not, compare the newly inserted key with the key in the left subtree root.
- If the balance factor is less than -1, then the current node is unbalanced and we are either in the Right Right case or Right-Left case. To check whether it is the Right Right case or not, compare the newly inserted key with the key in the right subtree root.
Below is the implementation of the above approach:
class Solution{
public :
// Function to get the height of a node
int height( struct Node *N)
{
if (N == NULL)
return 0;
return N->height;
}
// Function to perform right rotation
Node *rightRotate(Node *y)
{
Node *x = y->left;
Node *T2 = x->right;
// Perform rotation
x->right = y;
y->left = T2;
// Update heights
y->height = max(height(y->left), height(y->right))+1;
x->height = max(height(x->left), height(x->right))+1;
return x;
}
// Function to perform left rotation
Node *leftRotate( struct Node *x)
{
struct Node *y = x->right;
struct Node *T2 = y->left;
// Perform rotation
y->left = x;
x->right = T2;
// Update heights
x->height = max(height(x->left), height(x->right))+1;
y->height = max(height(y->left), height(y->right))+1;
return y;
}
// Function to get the balance factor of a node
int getBalance( struct Node *N)
{
if (N == NULL)
return 0;
return height(N->left) - height(N->right);
}
// Function to check if a tree is height-balanced
bool __getBalance__( struct Node *root)
{
int lh; // for height of left subtree
int rh; // for height of right subtree
// If tree is empty, return true
if (root == NULL)
return 1;
// Get the height of left and right subtrees
lh = height(root->left);
rh = height(root->right);
// Check if the difference in heights is <= 1
// and recursively check for all nodes in the tree
if ( abs (lh-rh) <= 1 &&
__getBalance__(root->left) &&
__getBalance__(root->right))
return 1;
// If we reach here, the tree is not height-balanced
return 0;
}
// Function to insert a new node into an AVL tree
Node* insertToAVL(Node* node, int data)
{
// If the node is null, create a new node with the data
if (node == NULL)
return new Node(data);
// If the data is less than the current node's data,
// insert it into the left subtree
if (data < node->data)
node->left = insertToAVL(node->left, data);
// If the data is greater than the current node's data,
// insert it into the right subtree
else if (data > node->data)
node->right = insertToAVL(node->right, data);
// If the data is equal to the current node's data,
// return the node (no insertion needed)
else
return node;
// Update the height of the current node
node->height = max(height(node->left), height(node->right)) + 1;
// Check the balance factor of the current node
int balance = getBalance(node);
// Perform rotations based on the balance factor
// Left Left Case
if (balance > 1 && data < node->left->data)
return rightRotate(node);
// Right Right Case
if (balance < -1 && data > node->right->data)
return leftRotate(node);
// Left Right Case
if (balance > 1 && data > node->left->data)
{
node->left = leftRotate(node->left);
return rightRotate(node);
}
// Right Left Case
if (balance < -1 && data < node->right->data)
{
node->right = rightRotate(node->right);
return leftRotate(node);
}
return node;
}
}; |
class Solution {
// Function to get the height of a node
int height(Node N)
{
if (N == null )
return 0 ;
return N.height;
}
// Function to perform right rotation
Node rightRotate(Node y)
{
Node x = y.left;
Node T2 = x.right;
// Perform rotation
x.right = y;
y.left = T2;
// Update heights
y.height
= Math.max(height(y.left), height(y.right)) + 1 ;
x.height
= Math.max(height(x.left), height(x.right)) + 1 ;
return x;
}
// Function to perform left rotation
Node leftRotate(Node x)
{
Node y = x.right;
Node T2 = y.left;
// Perform rotation
y.left = x;
x.right = T2;
// Update heights
x.height
= Math.max(height(x.left), height(x.right)) + 1 ;
y.height
= Math.max(height(y.left), height(y.right)) + 1 ;
return y;
}
// Function to get the balance factor of a node
int getBalance(Node N)
{
if (N == null )
return 0 ;
return height(N.left) - height(N.right);
}
// Function to check if a tree is height-balanced
boolean isBalanced(Node root)
{
int lh, rh;
// If tree is empty, return true
if (root == null )
return true ;
// Get the height of left and right subtrees
lh = height(root.left);
rh = height(root.right);
// Check if the difference in heights is <= 1
// and recursively check for all nodes in the tree
if (Math.abs(lh - rh) <= 1 && isBalanced(root.left)
&& isBalanced(root.right))
return true ;
// If we reach here, the tree is not height-balanced
return false ;
}
// Function to insert a new node into an AVL tree
Node insertToAVL(Node node, int data)
{
// If the node is null, create a new node with the
// data
if (node == null )
return new Node(data);
// If the data is less than the current node's data,
// insert it into the left subtree
if (data < node.data)
node.left = insertToAVL(node.left, data);
// If the data is greater than the current node's
// data, insert it into the right subtree
else if (data > node.data)
node.right = insertToAVL(node.right, data);
// If the data is equal to the current node's data,
// return the node (no insertion needed)
else
return node;
// Update the height of the current node
node.height = Math.max(height(node.left),
height(node.right))
+ 1 ;
// Check the balance factor of the current node
int balance = getBalance(node);
// Perform rotations based on the balance factor
// Left Left Case
if (balance > 1 && data < node.left.data)
return rightRotate(node);
// Right Right Case
if (balance < - 1 && data > node.right.data)
return leftRotate(node);
// Left Right Case
if (balance > 1 && data > node.left.data) {
node.left = leftRotate(node.left);
return rightRotate(node);
}
// Right Left Case
if (balance < - 1 && data < node.right.data) {
node.right = rightRotate(node.right);
return leftRotate(node);
}
return node;
}
} |
class Solution:
def height( self , N):
if N is None :
return 0
return N.height
# Function to perform right rotation
def rightRotate( self , y):
x = y.left
T2 = x.right
# Perform rotation
x.right = y
y.left = T2
# Update heights
y.height = max ( self .height(y.left), self .height(y.right)) + 1
x.height = max ( self .height(x.left), self .height(x.right)) + 1
return x
# Function to perform left rotation
def leftRotate( self , x):
y = x.right
T2 = y.left
# Perform rotation
y.left = x
x.right = T2
# Update heights
x.height = max ( self .height(x.left), self .height(x.right)) + 1
y.height = max ( self .height(y.left), self .height(y.right)) + 1
return y
# Function to get the balance factor of a node
def getBalance( self , N):
if N is None :
return 0
return self .height(N.left) - self .height(N.right)
# Function to check if a tree is height-balanced
def isBalanced( self , root):
if root is None :
return True
# Get the height of left and right subtrees
lh = self .height(root.left)
rh = self .height(root.right)
# Check if the difference in heights is <= 1
# and recursively check for all nodes in the tree
if abs (lh - rh) < = 1 and self .isBalanced(root.left) and self .isBalanced(root.right):
return True
# If we reach here, the tree is not height-balanced
return False
# Function to insert a new node into an AVL tree
def insertToAVL( self , node, data):
# If the node is None, create a new node with the data
if node is None :
return Node(data)
# If the data is less than the current node's data,
# insert it into the left subtree
if data < node.data:
node.left = self .insertToAVL(node.left, data)
# If the data is greater than the current node's data,
# insert it into the right subtree
elif data > node.data:
node.right = self .insertToAVL(node.right, data)
# If the data is equal to the current node's data,
# return the node (no insertion needed)
else :
return node
# Update the height of the current node
node.height = max ( self .height(node.left), self .height(node.right)) + 1
# Check the balance factor of the current node
balance = self .getBalance(node)
# Perform rotations based on the balance factor
# Left Left Case
if balance > 1 and data < node.left.data:
return self .rightRotate(node)
# Right Right Case
if balance < - 1 and data > node.right.data:
return self .leftRotate(node)
# Left Right Case
if balance > 1 and data > node.left.data:
node.left = self .leftRotate(node.left)
return self .rightRotate(node)
# Right Left Case
if balance < - 1 and data < node.right.data:
node.right = self .rightRotate(node.right)
return self .leftRotate(node)
return node
|
Time Complexity: O(log(n)), For Insertion
Auxiliary Space: O(1)