4 Dynamic Information Structures 1 Recursive Data Types
4.5. Balanced Trees
4.5.2. Balanced Tree Deletion
Our experience with tree deletion suggests that in the case of balanced trees deletion will also be more complicated than insertion. This is indeed true, although the rebalancing operation remains essentially the same as for insertion. In particular, rebalancing consists again of either single or a double rotations of nodes.
The basis for balanced tree deletion is the ordinary tree deletion algorithm. The easy cases are terminal nodes and nodes with only a single descendant. If the node to be deleted has two subtrees, we will again replace it by the rightmost node of its left subtree. As in the case of insertion, a Boolean variable parameter h is added with the meaning “the height of the subtree has been reduced”. Rebalancing has to be considered only when h is true. h is made true upon finding and deleting a node, or if rebalancing itself reduces the height of a subtree. We now introduce the two (symmetric) balancing operations in the form of procedures, because they have to be invoked from more than one point in the deletion algorithm. Note that balanceL is applied when the left, balanceR after the right branch had been reduced in height.
Fig. 4.35. Deletions in balanced tree
The operation of the procedure is illustrated in Fig. 4.35. Given the balanced tree (a), successive deletion of the nodes with keys 4, 8, 6, 5, 2, 1, and 7 results in the trees (b) ... (h). Deletion of key 4 is simple in itself, because it represents a terminal node. However, it results in an unbalanced node 3. Its rebalancing operation invoves an LL single rotation. Rebalancing becomes again necessary after the deletion of node 6. This time the right subtree of the root (7) is rebalanced by an RR single rotation. Deletion of node 2, although in itself straightforward since it has only a single descendant, calls for a complicated RL double rotation. The fourth case, an LR double rotation, is finally invoked after the removal of node 7, which at first was replaced by the rightmost element of its left subtree, i.e., by the node with key 3.
PROCEDURE balanceL(VAR p: Node; VAR h: BOOLEAN); VAR p1, p2: Node;
BEGIN (*h; left branch has shrunk*) IF p.bal = -1 THEN p.bal := 0
ELSIF p.bal = 0 THEN p.bal := 1; h := FALSE
1 9 8 7 10 6 9 11 a) 5 38 2 4 1 8 7 10 6 9 11 b) 5 2 1 3 7 6 10 9 11 c) 5 2 1 3 10 7 11 d) 5 2 1 3 9 10 7 11 e) 3 2 1 10 9 11 f) 7 3 10 9 11 g) 7 3 11 9 h) 10 3
ELSE (*bal = 1, rebalance*) p1 := p.right; IF p1.bal >= 0 THEN (*single RR rotation*) p.right := p1.left; p1.left := p;
IF p1.bal = 0 THEN p.bal := 1; p1.bal := -1; h := FALSE ELSE p.bal := 0; p1.bal := 0
END ; p := p1
ELSE (*double RL rotation*) p2 := p1.left;
p1.left := p2.right; p2.right := p1; p.right := p2.left; p2.left := p;
IF p2.bal = +1 THEN p.bal := -1 ELSE p.bal := 0 END ; IF p2.bal = -1 THEN p1.bal := 1 ELSE p1.bal := 0 END ; p := p2; p2.bal := 0
END END
END balanceL;
PROCEDURE balanceR(VAR p: Node; VAR h: BOOLEAN); VAR p1, p2: Node;
BEGIN (*h; right branch has shrunk*) IF p.bal = 1 THEN p.bal := 0
ELSIF p.bal = 0 THEN p.bal := -1; h := FALSE ELSE (*bal = -1, rebalance*) p1 := p.left; IF p1.bal <= 0 THEN (*single LL rotation*) p.left := p1.right; p1.right := p;
IF p1.bal = 0 THEN p.bal := -1; p1.bal := 1; h := FALSE ELSE p.bal := 0; p1.bal := 0
END ; p := p1
ELSE (*double LR rotation*) p2 := p1.right; b2 := p2.bal; p1.right := p2.left; p2.left := p1; p.left := p2.right; p2.right := p;
IF p2.bal = -1 THEN p.bal := 1 ELSE p.bal := 0 END ; IF p2.bal = +1 THEN p1.bal := -1 ELSE p1.bal := 0 END ; p := p2; p2.bal := 0
END END
END balanceR;
PROCEDURE delete(x: INTEGER; VAR p: Node; VAR h: BOOLEAN); VAR q: Node;
PROCEDURE del(VAR r: Node; VAR h: BOOLEAN); BEGIN (*~h*)
IF r.right # NIL THEN del(r.right, h);
IF h THEN balanceR(r, h) END ELSE q.key := r.key; q.count := r.count; q := r; r := r.left; h := TRUE
END END del; BEGIN (*~h*)
IF p = NIL THEN (*key not in tree*) ELSIF p.key > x THEN
IF h THEN balanceL(p, h) END ELSIF p.key < x THEN
delete(x, p.right, h);
IF h THEN balanceR(p, h) END ELSE (*delete p^*) q := p;
IF q.right = NIL THEN p := q.left; h := TRUE ELSIF q.left = NIL THEN p := q.right; h := TRUE ELSE del(q.left, h);
IF h THEN balanceL(p, h) END END
END END delete
Fortunately, deletion of an element in a balanced tree can also be performed with -- in the worst case -- O(log n) operations. An essential difference between the behaviour of the insertion and deletion procedures must not be overlooked, however. Whereas insertion of a single key may result in at most one rotation (of two or three nodes), deletion may require a rotation at every node along the search path. Consider, for instance, deletion of the rightmost node of a Fibonacci-tree. In this case the deletion of any single node leads to a reduction of the height of the tree; in addition, deletion of its rightmost node requires the maximum number of rotations. This therefore represents the worst choice of node in the worst case of a balanced tree, a rather unlucky combination of chances. How probable are rotations, then, in general?
The surprising result of empirical tests is that whereas one rotation is invoked for approximately every two insertions, one is required for every five deletions only. Deletion in balanced trees is therefore about as easy -- or as complicated -- as insertion.