AVL Tree Rebalancing in C: Pointer Techniques
This article examines the essential pointer manipulation techniques
required to restore balance in an AVL tree implemented in C. It breaks
down how memory addresses and child links are adjusted during single and
double rotations, explores the use of return pointers versus double
pointers (Node **) to update parent-child relationships,
and outlines the correct sequence of operations necessary to maintain
binary search tree invariant properties without causing memory leaks or
dangling references.
Node Representation in C
An AVL tree node in C contains pointers to its left and right children, alongside stored data and an integer tracking node height:
typedef struct Node {
int key;
struct Node *left;
struct Node *right;
int height;
} Node;Rebalancing is triggered when the balance factor—the height
difference between the left and right subtrees
(height(left) - height(right))—becomes greater than
1 or less than -1. Restoring this balance
requires specific pointer reassignment patterns known as rotations.
Single Rotations
Single rotations resolve linear imbalances: the Left-Left (LL) case and the Right-Right (RR) case.
Right Rotation (LL Imbalance)
A right rotation occurs when a node becomes unbalanced due to an insertion into the left subtree of its left child. Three pointer references must be updated:
- Identify the pivot: Let
ybe the unbalanced node, and letx = y->leftbe the pivot. - Preserve the inner subtree: Store
x->rightinto a temporary pointerT2(Node *T2 = x->right). - Reassign pointers:
- Set
x->right = y(makingythe right child ofx). - Set
y->left = T2(adoptingT2as the left child ofy).
- Set
Node *rightRotate(Node *y) {
Node *x = y->left;
Node *T2 = x->right;
// Pointer manipulation
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 of the subtree
return x;
}Left Rotation (RR Imbalance)
A left rotation is the exact mirror of the right rotation and resolves an insertion into the right subtree of a right child:
- Let
xbe the unbalanced node, and lety = x->rightbe the pivot. - Store
y->leftinto a temporary pointerT2. - Reassign pointers:
- Set
y->left = x. - Set
x->right = T2.
- Set
Node *leftRotate(Node *x) {
Node *y = x->right;
Node *T2 = y->left;
// Pointer manipulation
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 of the subtree
return y;
}Double Rotations
Zig-zag imbalances require double rotations: a rotation of the child node followed by a rotation of the unbalanced root.
Left-Right Rotation (LR Imbalance)
An LR imbalance occurs when a node y is left-heavy, but
its left child x is right-heavy.
- Perform a left rotation on the left child:
y->left = leftRotate(y->left);. - Perform a right rotation on the unbalanced root:
return rightRotate(y);.
Right-Left Rotation (RL Imbalance)
An RL imbalance occurs when a node x is right-heavy, but
its right child y is left-heavy.
- Perform a right rotation on the right child:
x->right = rightRotate(x->right);. - Perform a left rotation on the unbalanced root:
return leftRotate(x);.
Parent Pointer Updating Strategies
When a rotation changes which node serves as the root of a subtree, the parent must be updated to reference the new root. There are two primary techniques in C to accomplish this:
1. Recursive Value-Returning Pattern
The most common approach returns the updated node pointer up the recursion stack:
Node* insert(Node* node, int key) {
// Normal BST insertion omitted for brevity...
int balance = getBalance(node);
// LL Case
if (balance > 1 && key < node->left->key)
return rightRotate(node);
// RR Case
if (balance < -1 && key > node->right->key)
return leftRotate(node);
// LR Case
if (balance > 1 && key > node->left->key) {
node->left = leftRotate(node->left);
return rightRotate(node);
}
// RL Case
if (balance < -1 && key < node->right->key) {
node->right = rightRotate(node->right);
return leftRotate(node);
}
return node;
}The caller reassigns its own pointer
(node->left = ... or node->right = ...)
using the returned address, naturally propagating pointer adjustments up
the tree.
2. Double-Pointer
(Node **) Manipulation
In iterative implementations or scenarios without recursion, a
pointer-to-pointer (Node **) is used. This enables direct
modification of the parent's pointer to the current subtree without
needing to backtrack or return values:
void rightRotateDoublePtr(Node **rootRef) {
Node *y = *rootRef;
Node *x = y->left;
Node *T2 = x->right;
x->right = y;
y->left = T2;
y->height = max(height(y->left), height(y->right)) + 1;
x->height = max(height(x->left), height(x->right)) + 1;
// Dereference to update the parent's child pointer directly
*rootRef = x;
}This technique modifies the memory address stored in the parent node (or the tree root) in place, avoiding assignment operations further up the call chain.