AVL Trees- Height balanced Trees
So What’s the Problem?
An arbitrary series of insertions and deletions can produce a “skinny” BST
where h = n-1, leading to a BST for which search and insertion, which are
always O(h), are therefore O(n), not the O(lg n) we want
insert(M)
insert(T)
insert(W)
insert(V)
V
W
T
M
The problem is that we need the almost perfect shape, or something very
similar, with h = log2 n, to get the O(h) performance to be O(lg n)
But our insertion algorithm did not consider shape at all in determining
where to insert a new node; so in general, unless we get lucky, we won’t
wind up with an almost perfect shape after we do an insertion
“No problem”, you say; first, we’ll do the simple insertion and then
afterwards rebuild the almost perfect shape
Unfortunately, the “rebuilding” could easily be Ω(n), hardly what we want
This is in fact a real BST,
albeit it a “skinny” one
that doesn’t look
particularly “binaryish”
And here’s the only possible arrangement
of the nodes that ensures the BST property
is preserved for the new almost perfect BST
The Almost Perfect Shape is The Problem
It’s Too Stringent, Too Expensive to Maintain
Here’s an almost perfect binary
search tree with n=5 nodes
M
B H
T
F
C
And here’s the tree after
the insertion of a node
with the key of ‘C’
It’s still a BST, but it’s no
longer almost perfect
After we rebuild to almost perfect
shape, it will have to look like this …
M
B H
T
F
C
… since there’s only one possible
shape for an almost perfect
binary tree with 6 nodes
Note that every single
parent-child relationship
had to be changed to
restore the BST property, an
Ω(n) operation at best !
Conclusions (vis a vis Binary Search trees)
Although BST insertion is O(h) and an almost perfect shape
guarantees that h=log2n, we can’t usually keep the almost
perfect shape after a BST insertion without rebuilding, an
operation that can often be Ω(n), which surely defeats the goal
of making the complete insertion operation itself O(lg n)
The almost perfect shape constraint is the problem; it’s too
difficult to maintain — i.e., it usually can’t be done O(lg n)
What we need is an “easier” shape constraint, one that can
always be restored with just O(lg n) operations after an insertion
The shape constraint we’re after is called the “height bounded
with differential 1” or HB(1) property; and it’s what makes AVL
trees possible
AVL Trees: Motivation
AVL trees, named after the two mathematicians Adelson-Velsky and
Landis who invented/discovered them, are of great theoretic, practical,
and historic interest in computer science as they were the first data
structure to provide efficient — i.e., O(lg n) — support for the big three
of data structure operations: search, insertion, and deletion
The proof of their O(lg n) behavior illuminates the complex and beautiful
topological properties that an abstract data structure can have
This presentation provided an overview of that proof — along with a
decent review of some of the basics of binary trees along the way ..
V
A D
P W
K
C
L
F
T
E
J
M
G
AVL Trees: BSTs With the HB(1) Property
An AVL tree is a binary search tree in which each node also
obeys the HB(1) property: The heights of its left and right
subtrees must differ by no more than 1
Both the BST and HB(1) properties must hold for every node
The tree above is an AVL tree
V
A D
P W
K
C
L
F
T
E
J
M
The tree to the right is a BST that is not
HB(1) and so is not an AVL tree
M
J
I
L
V
B W
F
P
R
T
This node, F, is not
in HB(1) balance
G
V
Insertion Into an AVL Tree
A D
P W
K
C
L
F
T
E
J
M
Insertion into an AVL tree occurs in two steps:
1. Do a normal BST insertion so as to preserve the BST property
without regard for the HB(1) property
2. Determine if the BST insertion broke the HB(1) property anywhere,
and, if so, fix it somehow while satisfying two constraints:
a. The fixing operation must be O(lg n) or better
b. The fixing operation must preserve the BST property
The AVL tree above was built from an initially empty tree by
the sequence of BST insertions shown to the left
This sequence was carefully chosen so that the insertions
never broke the HB(1) property anywhere
We won’t always be so lucky; let’s try another insertion
B<M
B<J
V
insert(B)
insert(D)
insert(M)
insert(T)
insert(J)
insert(P)
insert(L)
insert(E)
insert(W)
insert(C)
insert(F)
insert(K)
insert(A)
An HB(1) or Balance factor Breaking Insertion
Although it naturally preserved the BST property, this
last insertion destroyed the HB(1) balance of multiple nodes
We’re going to need to restore the HB(1) balance somehow
B<C
insert(V)
Order of
Insertions
B<E
A D
P W
K
C
L
F
T
B>A
G
E
J
M
B
nodes now
out of balance
J
M
insert(G)
For implementation purpose we use the term
Balance factor(BF) for each node
So the AVL node will look like
class avlnode
{
avlnode left;
int data;
avlnode right;
int bal_fac;
}
• Note: height of leaf=0 and height of null node after
leaf is -1 – for implementation purpose
1.LL CASE- DO Right rotation
LL CASE - If an imbalance caused to a node (J) by an insertion
at the left subtree of the leftchild of the node
X= j.left
j.left=X.right
P.left=X
X.right=j
Eg.
INSERT 2
CRITICAL NODE?
After Right rotation
Insert 6
2. RR case- Do LEFT rotation
RR CASE - If an imbalance caused to a node (J)
by an insertion
at the right subtree of the
right child of the node
T=x.right;
RR case- Do LEFT rotation
T=x.right;
x.right=T.left
T.Left =x
x
T
Eg..
Eg.
Insert 9 or 29 into an AVL tree
20 is the critical node (RR case – Left
rotation)
3.RL CASE -
3. RL Case - Heavyness caused by Left Subtree of Right child -
Do Right rotation at the right child level and Left rotation at the critical node (-1>BF >1)
TRICKY WAY – IN ONE MOVE FOR DOUBLE
ROTATION
4.LR CASE -
4. LR case- Heavyness due to INSERTION AT right subtree of left child
Do Left rotation at the leftchild followed by Right rotation at the critical node level
TRICKY WAY - LR CASE
Examples insert - 46
Rearrangement required
LL case- Right rotation
Implementation
• class Node {
• int key, height;
• Node left, right;
•
• Node(int d) {
• key = d;
• height = 1;
• }
• }
• class AVLTree {
• Node root;
• // A utility function to get the height of
the tree
• int height(Node N) {
• if (N == null)
• return 0;
•
• return N.height;
• }
// A utility function to get maximum of two
integers
int max(int a, int b) {
return (a > b) ? a : b;
}
// A utility function to right rotate subtree rooted
with y
// See the diagram given above.
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 new root
return x;
}
// A utility function to left rotate subtree rooted
with x
// See the diagram given above.
Node leftRotate(Node x) {
Node y = x.right;
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 new root
return y;
}
// Get Balance factor of node N
int getBalance(Node N) {
if (N == null)
return 0;
return height(N.left) - height(N.right);
}
Node insert(Node node, int key) {
/* 1. Perform the normal BST insertion */
if (node == null)
return (new Node(key));
if (key < node.key)
node.left = insert(node.left, key);
else if (key > node.key)
node.right = insert(node.right, key);
else // Duplicate keys not allowed
return node;
/* 2. Update height of this ancestor node */
node.height = 1 + max(height(node.left),
height(node.right));
/* 3. Get the balance factor of this ancestor
node to check whether this node became
unbalanced */
int balance = getBalance(node);
// If this node becomes unbalanced, then there
// are 4 cases Left Left Case
if (balance > 1 && key < node.left.key)
return rightRotate(node);
// Right Right Case
if (balance < -1 && key > node.right.key)
return leftRotate(node);
• // Left Right Case
• if (balance > 1 && key >
node.left.key) {
• node.left = leftRotate(node.left);
• return rightRotate(node);
• }
• // Right Left Case
• if (balance < -1 && key <
node.right.key) {
• node.right =
rightRotate(node.right);
• return leftRotate(node);
• }
•
• /* return the (unchanged) node
pointer */
• return node;
• }
•
Implementation -Bal Fac is calculated then
and there
class AVLNode
{
AVLNode left, right;
int data;
int height;
public AVLNode()
{
left = null;
right = null;
data = 0;
height = 0;
}
public AVLNode(int n)
{
left = null;
right = null;
data = n;
height = 0;
}
}
class AVLTree
{
AVLNode root;
AVLTree()
{
root = null;
}
private AVLNode insert(int x, AVLNode t)
{
if (t == null)
t = new AVLNode(x);
else if (x < t.data)
{
t.left = insert( x, t.left );
if( height( t.left ) - height( t.right ) == 2 )
if( x < t.left.data )
t = rotateWithLeftChild( t );
else
t = doubleWithLeftChild( t );
}
else if( x > t.data )
{t.right = insert( x, t.right );
if( height( t.right ) - height( t.left ) == 2 )
if( x > t.right.data)
t = rotateWithRightChild( t );
else
t = doubleWithRightChild( t );
}
else
; // Duplicate; do nothing
t.height = max( height( t.left ), height( t.right ) ) + 1;
return t;
}
/* Rotate binary tree node with left child */
private AVLNode rotateWithLeftChild(AVLNode k2)
{
AVLNode k1 = k2.left;
k2.left = k1.right;
k1.right = k2;
k2.height = max( height( k2.left ), height( k2.right ) ) +
1;
k1.height = max( height( k1.left ), k2.height ) + 1;
return k1;
}
•
• /* Rotate binary tree node with right child */
• private AVLNode rotateWithRightChild(AVLNode k1)
• {
• AVLNode k2 = k1.right;
• k1.right = k2.left;
• k2.left = k1;
• k1.height = max( height( k1.left ), height( k1.right ) ) + 1;
• k2.height = max( height( k2.right ), k1.height ) + 1;
• return k2;
• }
• /**
• * Double rotate binary tree node: first left child
• * with its right child; then node k3 with new left child
*/
• private AVLNode doubleWithLeftChild(AVLNode k3)
• {
• k3.left = rotateWithRightChild( k3.left );
• return rotateWithLeftChild( k3 );
• }
• /**
• * Double rotate binary tree
node: first right child
• * with its left child; then node
k1 with new right child */
• private AVLNode
doubleWithRightChild(AVLNod
e k1)
• {
• k1.right =
rotateWithLeftChild( k1.right );
• return
rotateWithRightChild( k1 );
• }

Avl tress in data structures and algorithms

  • 1.
    AVL Trees- Heightbalanced Trees
  • 2.
    So What’s theProblem? An arbitrary series of insertions and deletions can produce a “skinny” BST where h = n-1, leading to a BST for which search and insertion, which are always O(h), are therefore O(n), not the O(lg n) we want insert(M) insert(T) insert(W) insert(V) V W T M The problem is that we need the almost perfect shape, or something very similar, with h = log2 n, to get the O(h) performance to be O(lg n) But our insertion algorithm did not consider shape at all in determining where to insert a new node; so in general, unless we get lucky, we won’t wind up with an almost perfect shape after we do an insertion “No problem”, you say; first, we’ll do the simple insertion and then afterwards rebuild the almost perfect shape Unfortunately, the “rebuilding” could easily be Ω(n), hardly what we want This is in fact a real BST, albeit it a “skinny” one that doesn’t look particularly “binaryish”
  • 3.
    And here’s theonly possible arrangement of the nodes that ensures the BST property is preserved for the new almost perfect BST The Almost Perfect Shape is The Problem It’s Too Stringent, Too Expensive to Maintain Here’s an almost perfect binary search tree with n=5 nodes M B H T F C And here’s the tree after the insertion of a node with the key of ‘C’ It’s still a BST, but it’s no longer almost perfect After we rebuild to almost perfect shape, it will have to look like this … M B H T F C … since there’s only one possible shape for an almost perfect binary tree with 6 nodes Note that every single parent-child relationship had to be changed to restore the BST property, an Ω(n) operation at best !
  • 4.
    Conclusions (vis avis Binary Search trees) Although BST insertion is O(h) and an almost perfect shape guarantees that h=log2n, we can’t usually keep the almost perfect shape after a BST insertion without rebuilding, an operation that can often be Ω(n), which surely defeats the goal of making the complete insertion operation itself O(lg n) The almost perfect shape constraint is the problem; it’s too difficult to maintain — i.e., it usually can’t be done O(lg n) What we need is an “easier” shape constraint, one that can always be restored with just O(lg n) operations after an insertion The shape constraint we’re after is called the “height bounded with differential 1” or HB(1) property; and it’s what makes AVL trees possible
  • 5.
    AVL Trees: Motivation AVLtrees, named after the two mathematicians Adelson-Velsky and Landis who invented/discovered them, are of great theoretic, practical, and historic interest in computer science as they were the first data structure to provide efficient — i.e., O(lg n) — support for the big three of data structure operations: search, insertion, and deletion The proof of their O(lg n) behavior illuminates the complex and beautiful topological properties that an abstract data structure can have This presentation provided an overview of that proof — along with a decent review of some of the basics of binary trees along the way .. V A D P W K C L F T E J M
  • 6.
    G AVL Trees: BSTsWith the HB(1) Property An AVL tree is a binary search tree in which each node also obeys the HB(1) property: The heights of its left and right subtrees must differ by no more than 1 Both the BST and HB(1) properties must hold for every node The tree above is an AVL tree V A D P W K C L F T E J M The tree to the right is a BST that is not HB(1) and so is not an AVL tree M J I L V B W F P R T This node, F, is not in HB(1) balance
  • 7.
    G V Insertion Into anAVL Tree A D P W K C L F T E J M Insertion into an AVL tree occurs in two steps: 1. Do a normal BST insertion so as to preserve the BST property without regard for the HB(1) property 2. Determine if the BST insertion broke the HB(1) property anywhere, and, if so, fix it somehow while satisfying two constraints: a. The fixing operation must be O(lg n) or better b. The fixing operation must preserve the BST property
  • 8.
    The AVL treeabove was built from an initially empty tree by the sequence of BST insertions shown to the left This sequence was carefully chosen so that the insertions never broke the HB(1) property anywhere We won’t always be so lucky; let’s try another insertion B<M B<J V insert(B) insert(D) insert(M) insert(T) insert(J) insert(P) insert(L) insert(E) insert(W) insert(C) insert(F) insert(K) insert(A) An HB(1) or Balance factor Breaking Insertion Although it naturally preserved the BST property, this last insertion destroyed the HB(1) balance of multiple nodes We’re going to need to restore the HB(1) balance somehow B<C insert(V) Order of Insertions B<E A D P W K C L F T B>A G E J M B nodes now out of balance J M insert(G)
  • 9.
    For implementation purposewe use the term Balance factor(BF) for each node
  • 10.
    So the AVLnode will look like class avlnode { avlnode left; int data; avlnode right; int bal_fac; } • Note: height of leaf=0 and height of null node after leaf is -1 – for implementation purpose
  • 15.
    1.LL CASE- DORight rotation LL CASE - If an imbalance caused to a node (J) by an insertion at the left subtree of the leftchild of the node X= j.left j.left=X.right P.left=X X.right=j
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
    2. RR case-Do LEFT rotation RR CASE - If an imbalance caused to a node (J) by an insertion at the right subtree of the right child of the node T=x.right;
  • 24.
    RR case- DoLEFT rotation T=x.right; x.right=T.left T.Left =x x T
  • 25.
  • 26.
  • 27.
    Insert 9 or29 into an AVL tree
  • 28.
    20 is thecritical node (RR case – Left rotation)
  • 30.
  • 31.
    3. RL Case- Heavyness caused by Left Subtree of Right child - Do Right rotation at the right child level and Left rotation at the critical node (-1>BF >1)
  • 32.
    TRICKY WAY –IN ONE MOVE FOR DOUBLE ROTATION
  • 33.
  • 34.
    4. LR case-Heavyness due to INSERTION AT right subtree of left child Do Left rotation at the leftchild followed by Right rotation at the critical node level
  • 35.
    TRICKY WAY -LR CASE
  • 36.
  • 39.
  • 40.
  • 43.
    Implementation • class Node{ • int key, height; • Node left, right; • • Node(int d) { • key = d; • height = 1; • } • } • class AVLTree { • Node root; • // A utility function to get the height of the tree • int height(Node N) { • if (N == null) • return 0; • • return N.height; • } // A utility function to get maximum of two integers int max(int a, int b) { return (a > b) ? a : b; } // A utility function to right rotate subtree rooted with y // See the diagram given above. 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 new root return x; }
  • 44.
    // A utilityfunction to left rotate subtree rooted with x // See the diagram given above. Node leftRotate(Node x) { Node y = x.right; 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 new root return y; } // Get Balance factor of node N int getBalance(Node N) { if (N == null) return 0; return height(N.left) - height(N.right); } Node insert(Node node, int key) { /* 1. Perform the normal BST insertion */ if (node == null) return (new Node(key)); if (key < node.key) node.left = insert(node.left, key); else if (key > node.key) node.right = insert(node.right, key); else // Duplicate keys not allowed return node; /* 2. Update height of this ancestor node */ node.height = 1 + max(height(node.left), height(node.right)); /* 3. Get the balance factor of this ancestor node to check whether this node became unbalanced */ int balance = getBalance(node); // If this node becomes unbalanced, then there // are 4 cases Left Left Case if (balance > 1 && key < node.left.key) return rightRotate(node); // Right Right Case if (balance < -1 && key > node.right.key) return leftRotate(node);
  • 45.
    • // LeftRight Case • if (balance > 1 && key > node.left.key) { • node.left = leftRotate(node.left); • return rightRotate(node); • } • // Right Left Case • if (balance < -1 && key < node.right.key) { • node.right = rightRotate(node.right); • return leftRotate(node); • } • • /* return the (unchanged) node pointer */ • return node; • } •
  • 46.
    Implementation -Bal Facis calculated then and there class AVLNode { AVLNode left, right; int data; int height; public AVLNode() { left = null; right = null; data = 0; height = 0; } public AVLNode(int n) { left = null; right = null; data = n; height = 0; } } class AVLTree { AVLNode root; AVLTree() { root = null; }
  • 47.
    private AVLNode insert(intx, AVLNode t) { if (t == null) t = new AVLNode(x); else if (x < t.data) { t.left = insert( x, t.left ); if( height( t.left ) - height( t.right ) == 2 ) if( x < t.left.data ) t = rotateWithLeftChild( t ); else t = doubleWithLeftChild( t ); } else if( x > t.data ) {t.right = insert( x, t.right ); if( height( t.right ) - height( t.left ) == 2 ) if( x > t.right.data) t = rotateWithRightChild( t ); else t = doubleWithRightChild( t ); } else ; // Duplicate; do nothing t.height = max( height( t.left ), height( t.right ) ) + 1; return t; } /* Rotate binary tree node with left child */ private AVLNode rotateWithLeftChild(AVLNode k2) { AVLNode k1 = k2.left; k2.left = k1.right; k1.right = k2; k2.height = max( height( k2.left ), height( k2.right ) ) + 1; k1.height = max( height( k1.left ), k2.height ) + 1; return k1; }
  • 48.
    • • /* Rotatebinary tree node with right child */ • private AVLNode rotateWithRightChild(AVLNode k1) • { • AVLNode k2 = k1.right; • k1.right = k2.left; • k2.left = k1; • k1.height = max( height( k1.left ), height( k1.right ) ) + 1; • k2.height = max( height( k2.right ), k1.height ) + 1; • return k2; • } • /** • * Double rotate binary tree node: first left child • * with its right child; then node k3 with new left child */ • private AVLNode doubleWithLeftChild(AVLNode k3) • { • k3.left = rotateWithRightChild( k3.left ); • return rotateWithLeftChild( k3 ); • } • /** • * Double rotate binary tree node: first right child • * with its left child; then node k1 with new right child */ • private AVLNode doubleWithRightChild(AVLNod e k1) • { • k1.right = rotateWithLeftChild( k1.right ); • return rotateWithRightChild( k1 ); • }

Editor's Notes