Skip to content

Commit

Permalink
...
Browse files Browse the repository at this point in the history
  • Loading branch information
abetusk committed Feb 15, 2024
1 parent 25f6b26 commit 79ed81d
Show file tree
Hide file tree
Showing 3 changed files with 15,433 additions and 38 deletions.
173 changes: 136 additions & 37 deletions wiki/AVL-Tree.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,28 @@ of tree rotations.
Tree rotations restore the BF locally, recursively applying any
height changes up the tree, issuing tree rotations as necessary.

Insertion
Tree Updates
---

Initially, insertion is done as normal into a binary tree.

The tree can be left in a state that violates to the AVL
delta height condition.
To fix up, a series of rotations are done.
Tree insertion and deletions are initially done as normal
for a binary tree with tree rotations done afterwards
to fix nodes that violate the AVL condition.
Addition is done through a binary search and node addition
as a leaf whereas deletion is done through a surgery operation
which will be described in more detail below.

![AVL Tree Rotations](img/avl_rot.svg)
Tree rotations, while conceptually simple, are a bit
subtle as the balance factor needs to be potentially updated
for each of the nodes in addition to communicating
how the height of the sub-tree has changed to the parent.

The following diagram can be used as a reference
for how insertion and deletions change a tree
and how nodes need to be updated in relation to the
rotations:

![AVL Tree Rotations](img/avl_rot1.svg)

Here, the `--`, `-`, `+`, `==` and $\rlap{0}/$ signify
whether the node is left heavy requiring a rotation, left
Expand All @@ -37,63 +49,94 @@ the need for a rotation, right
heavy with the need for a rotation or balanced,
respectively.

The last condition row in can be represented as a double rotation,
with $z$ rotated with $y$, then $z$ rotated
with $x$.

If the height of the altered subtree changes, nodes above will
need to be recursively updated in the same way.
Note that the $\oplus$ and $\ominus$ represent
the addition or deletion of a node respectively.

The first two rows represent a simple rotation whereas
the last row is often represented by two simple rotations.

The tree height before the insertion or deletion
can be seen on the appropriate nodes or sub-trees
with the height followed by the $\oplus$ or $\ominus$
symbol.
The arrow pointing up from the sub-tree root node
indicates codes for the height change message passed
to the parent node.

For example, the configuration where $x$ is unbalanced
and left heavy ($--$) with $y$ balanced but left heavy ($-$)
would have been the result of an addition
of a node to the $\alpha$ sub-tree or a removal of
a node from the $\gamma$ sub-tree.
In this case, the sub-tree's height rooted at $x$
would have been $h+2$ before the addition or
$h+1$ before the deletion,
following from the sub-tree $\alpha$ being at
height $h-1$ or the sub-tree $\gamma$ being
at height $h$ respectively.
After the insertion or deletion, the sub-tree
rooted at $y'$ would be $h+1$, leaving the
total sub-tree height unchanged in the case
of an addition or being reduced by one, in
the case of a deletion.


The following tables can help understand the state
of each of the nodes' balance factor, messages
passed to the parent and the various heights of sub-trees:

#### Single Rotation

let $x _ 0$ and $y _ 0$ be the nodes before the insertion,
where $h _ {x _ 0} = h$.

| | | | | |
|---|---|---|---|---|
| $\Delta h _ {x}$ | $-2$ | $-2$ | $2$ | $2$ |
| $\Delta h _ {y}$ | $-1$ | $0$ | $1$ | $0$ |
| $\Delta h _ {x'}$ | $0$ | $-1$ | $0$ | $1$ |
| $\Delta h _ {y'}$ | $0$ | $1$ | $0$ | $-1$ |
| $h _ { x' }$ | $h$ | $h+1$ | $h$ | $h+1$ |
| $h _ { y' }$ | $h+1$ | $h+2$ | $h+1$ | $h+2$ |
| $\Delta h _ {p'}$ | $0$ | $1$ | $0$ | $1$ |

When doing a simple rotate where the appropriate child
of the current subtree's root is not completely balanced,
a height change needs to be communicated recursively up
the tree.

| $\Delta h _ {x}$ | $-2$ | $-2$ | $2$ | $2$ |
| $\Delta h _ {y}$ | $-1$ | $0$ | $1$ | $0$ |
| $\Delta h _ {x'}$ | $0$ | $-1$ | $0$ | $1$ |
| $\Delta h _ {y'}$ | $0$ | $1$ | $0$ | $-1$ |
| $h _ { x' }$ | $h$ | $h+1$ | $h$ | $h+1$ |
| $h _ { y' }$ | $h+1$ | $h+2$ | $h+1$ | $h+2$ |
| $\Delta h _ {p'} \oplus$ | $0$ | $1$ | $0$ | $1$ |
| $\Delta h _ {p'} \ominus$ | $-1$ | $0$ | $-1$ | $0$ |

#### Double Rotation

In the case of a double rotation, the rotated nodes' balance factor, $x'$, $y'$ and $z'$,
can be determined from the $z$'s balance factor before rotation:


| $\Delta h _ z$ | $-1$ | $\rlap{0}/$ | $1$ | $-1$ | $\rlap{0}/$ | $1$ |
|---|---|---|---|---|---|---|
| $\Delta h _ {x}$ | $-2$ | $-2$ | $-2$ | $2$ | $2$ | $2$ |
| $\Delta h _ {y}$ | $1$ | $1$ | $1$ | $-1$ | $-1$ | $-1$ |
| $h _ \beta$ | $h _ z -1$ | $h _ z -1$ | $h _ z -2$ | $h _ z -1$ | $h _ z -1$ | $h _ z -2$ |
| $h _ \gamma$ | $h _ z -2$ | $h _ z -1$ | $h _ z -1$ | $h _ z -2$ | $h _ z -1$ | $h _ z -1$ |
| $h _ \beta$ | $h -1$ | $h -1$ | $h -2$ | $h -1$ | $h -1$ | $h -2$ |
| $h _ \gamma$ | $h -2$ | $h -1$ | $h -1$ | $h -2$ | $h -1$ | $h -1$ |
| $\Delta h _ {x'}$ | $1$ | $\rlap{0}/$ | $\rlap{0}/$ | $\rlap{0}/$ | $\rlap{0}/$ | $-1$ |
| $\Delta h _ {y'}$ | $\rlap{0}/$ | $\rlap{0}/$ | $-1$ | $1$ | $\rlap{0}/$ | $\rlap{0}/$ |
| $\Delta h _ {z'}$ | $\rlap{0}/$ | $\rlap{0}/$ | $\rlap{0}/$ | $\rlap{0}/$ | $\rlap{0}/$ | $\rlap{0}/$ |
| $\Delta h _ {p'}$ | $\rlap{0}/$ | $\rlap{0}/$ | $\rlap{0}/$ | $\rlap{0}/$ | $\rlap{0}/$ | $\rlap{0}/$ |
| $\Delta h _ {p'} \oplus$ | $\rlap{0}/$ | $\rlap{0}/$ | $\rlap{0}/$ | $\rlap{0}/$ | $\rlap{0}/$ | $\rlap{0}/$ |
| $\Delta h _ {p'} \ominus$ | $-1$ | $-1$ | $-1$ | $-1$ | $-1$ | $-1$ |

For brevity, here is a restriction of the table to highlight the nodes we care about most:

| $\Delta h _ z$ | $-1$ | $\rlap{0}/$ | $1$ | $-1$ | $\rlap{0}/$ | $1$ |
| $\Delta h$ | $-1$ | $\rlap{0}/$ | $1$ | $-1$ | $\rlap{0}/$ | $1$ |
|---|---|---|---|---|---|---|
| $\Delta h _ {x'}$ | $1$ | $\rlap{0}/$ | $\rlap{0}/$ | $\rlap{0}/$ | $\rlap{0}/$ | $-1$ |
| $\Delta h _ {y'}$ | $\rlap{0}/$ | $\rlap{0}/$ | $-1$ | $1$ | $\rlap{0}/$ | $\rlap{0}/$ |
| $\Delta h _ {p'} \oplus$ | $\rlap{0}/$ | $\rlap{0}/$ | $\rlap{0}/$ | $\rlap{0}/$ | $\rlap{0}/$ | $\rlap{0}/$ |
| $\Delta h _ {p'} \ominus$ | $-1$ | $-1$ | $-1$ | $-1$ | $-1$ | $-1$ |

In the case of an addition, the sub-tree would have had height $h+1$ before
the addition, with the resulting
sub-tree after the addition remaining at height $h+1$, needing no height change to the parent.
In the case of an deletion, the sub-tree would have had height $h+2$ before the deletion,
with the resulting sub-tree being at height $h-1$, requiring a message passed to the parent
of the updated height change.

---

When doing rotations, care has to be taken to make sure the privileged root pointer is updated
if it's involved in any rotations.

Note that in the case of a double rotation, before the addition of a node, the height $h _ x = h _ z + 1$.
Regardless of which double rotation is done, what value $\Delta h _ {x}$, $\Delta h _ {y}$, $h _ {\beta}$
or $h _ {\gamma}$, the resulting height of $h _ {z'}$ will always be $h _ z + 1$, requiring no height
change to to communicated back up to the parent tree corresponding to $\Delta h _ {p'} = \rlap{0}/$ in the
above table.

Deletion
---
Expand All @@ -109,5 +152,61 @@ Deletion of the node $z$ has three main cases (as per [CLR](https://en.wikipedia
removed and replaced with $z$. $y$ must have a `null` child so the initial
removal can be done as per above

The last case requires surgery, stitching $y$ into $x$'s position and fixing up
the pointers for the node $y$ was removed from in $\beta$ and $\gamma$.
Special care has to be taken should the successor of $x$ be $y$.
In this special case, $y$ will
have $x$ as its parent and be $x$'s right child.

For example, the following code will work:

```
dT=0;
y->dh = x->dh;
y = succ(x);
// fix up y's parent
//
p = y->p;
if (p) {
dh = p->dh;
if (lchild(y)) { p->l = y->r; dT = 1; }
else { p->r = y->r; dT = -1; }
if (y->r) { y->r->p = p; }
}
// remove x from tree, putting y in its place
//
if (x->p) {
if (lchild(x)) { x->p->l = y; }
else { x->p->r = y; }
}
y->p = x->p;
y->r = x->r;
y->l = x->l;
if (y->r) { y->r->p = y; }
if (y->l) { y->l->p = y; }
if (root == x) { root = y; }
if (p == x) { p = y; }
if (p) { p->dh = dh + dT; }
free(x);
retrace(p, dT);
```

Should the parent of $y$ initially be $x$, we not
not only need to keep a copy of $x$'s balance factor,
as it will be discarded when we remove $x$ from the tree,
but we need to explicitly account for it when updating
the original parent of $y$'s balance factor in the
last two lines of the above.

In the above, root is also updated appropriately.


###### 2024-02-13
2 changes: 1 addition & 1 deletion wiki/img/avl_rot.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 79ed81d

Please sign in to comment.