\(\) \(%\newcommand{\CLRS}{\href{https://mitpress.mit.edu/books/introduction-algorithms}{Cormen et al}}\) \(\newcommand{\CLRS}{\href{https://mitpress.mit.edu/books/introduction-algorithms}{Introduction to Algorithms, 3rd ed.} (\href{http://libproxy.aalto.fi/login?url=http://site.ebrary.com/lib/aalto/Doc?id=10397652}{online via Aalto lib})}\) \(\newcommand{\SW}{\href{http://algs4.cs.princeton.edu/home/}{Algorithms, 4th ed.}}\) \(%\newcommand{\SW}{\href{http://algs4.cs.princeton.edu/home/}{Sedgewick and Wayne}}\) \(\) \(\newcommand{\Java}{\href{http://java.com/en/}{Java}}\) \(\newcommand{\Python}{\href{https://www.python.org/}{Python}}\) \(\newcommand{\CPP}{\href{http://www.cplusplus.com/}{C++}}\) \(\newcommand{\ST}[1]{{\Blue{\textsf{#1}}}}\) \(\newcommand{\PseudoCode}[1]{{\color{blue}\textsf{#1}}}\) \(\) \(%\newcommand{\subheading}[1]{\textbf{\large\color{aaltodgreen}#1}}\) \(\newcommand{\subheading}[1]{\large{\usebeamercolor[fg]{frametitle} #1}}\) \(\) \(\newcommand{\Blue}[1]{{\color{flagblue}#1}}\) \(\newcommand{\Red}[1]{{\color{aaltored}#1}}\) \(\newcommand{\Emph}[1]{\emph{\color{flagblue}#1}}\) \(\) \(\newcommand{\Engl}[1]{({\em engl.}\ #1)}\) \(\) \(\newcommand{\Pointer}{\raisebox{-1ex}{\huge\ding{43}}\ }\) \(\) \(\newcommand{\Set}[1]{\{#1\}}\) \(\newcommand{\Setdef}[2]{\{{#1}\mid{#2}\}}\) \(\newcommand{\PSet}[1]{\mathcal{P}(#1)}\) \(\newcommand{\Card}[1]{{\vert{#1}\vert}}\) \(\newcommand{\Tuple}[1]{(#1)}\) \(\newcommand{\Implies}{\Rightarrow}\) \(\newcommand{\Reals}{\mathbb{R}}\) \(\newcommand{\Seq}[1]{(#1)}\) \(\newcommand{\Arr}[1]{[#1]}\) \(\newcommand{\Floor}[1]{{\lfloor{#1}\rfloor}}\) \(\newcommand{\Ceil}[1]{{\lceil{#1}\rceil}}\) \(\newcommand{\Path}[1]{(#1)}\) \(\) \(%\newcommand{\Lg}{\lg}\) \(\newcommand{\Lg}{\log_2}\) \(\) \(\newcommand{\BigOh}{O}\) \(%\newcommand{\BigOh}{\mathcal{O}}\) \(\newcommand{\Oh}[1]{\BigOh(#1)}\) \(\) \(\newcommand{\todo}[1]{\Red{\textbf{TO DO: #1}}}\) \(\) \(\newcommand{\NULL}{\textsf{null}}\) \(\) \(\newcommand{\Insert}{\ensuremath{\textsc{insert}}}\) \(\newcommand{\Search}{\ensuremath{\textsc{search}}}\) \(\newcommand{\Delete}{\ensuremath{\textsc{delete}}}\) \(\newcommand{\Remove}{\ensuremath{\textsc{remove}}}\) \(\) \(\newcommand{\Parent}[1]{\mathop{parent}(#1)}\) \(\) \(\newcommand{\ALengthOf}[1]{{#1}.\textit{length}}\) \(\) \(\newcommand{\TRootOf}[1]{{#1}.\textit{root}}\) \(\newcommand{\TLChildOf}[1]{{#1}.\textit{leftChild}}\) \(\newcommand{\TRChildOf}[1]{{#1}.\textit{rightChild}}\) \(\newcommand{\TNode}{x}\) \(\newcommand{\TNodeI}{y}\) \(\newcommand{\TKeyOf}[1]{{#1}.\textit{key}}\) \(\) \(\newcommand{\PEnqueue}[2]{{#1}.\textsf{enqueue}(#2)}\) \(\newcommand{\PDequeue}[1]{{#1}.\textsf{dequeue}()}\) \(\) \(\newcommand{\Def}{\mathrel{:=}}\) \(\) \(\newcommand{\Eq}{\mathrel{=}}\) \(\newcommand{\Asgn}{\mathrel{\leftarrow}}\) \(%\newcommand{\Asgn}{\mathrel{:=}}\) \(\) \(%\) \(% Heaps\) \(%\) \(\newcommand{\Downheap}{\textsc{downheap}}\) \(\newcommand{\Upheap}{\textsc{upheap}}\) \(\newcommand{\Makeheap}{\textsc{makeheap}}\) \(\) \(%\) \(% Dynamic sets\) \(%\) \(\newcommand{\SInsert}[1]{\textsc{insert}(#1)}\) \(\newcommand{\SSearch}[1]{\textsc{search}(#1)}\) \(\newcommand{\SDelete}[1]{\textsc{delete}(#1)}\) \(\newcommand{\SMin}{\textsc{min}()}\) \(\newcommand{\SMax}{\textsc{max}()}\) \(\newcommand{\SPredecessor}[1]{\textsc{predecessor}(#1)}\) \(\newcommand{\SSuccessor}[1]{\textsc{successor}(#1)}\) \(\) \(%\) \(% Union-find\) \(%\) \(\newcommand{\UFMS}[1]{\textsc{make-set}(#1)}\) \(\newcommand{\UFFS}[1]{\textsc{find-set}(#1)}\) \(\newcommand{\UFCompress}[1]{\textsc{find-and-compress}(#1)}\) \(\newcommand{\UFUnion}[2]{\textsc{union}(#1,#2)}\) \(\) \(\) \(%\) \(% Graphs\) \(%\) \(\newcommand{\Verts}{V}\) \(\newcommand{\Vtx}{v}\) \(\newcommand{\VtxA}{v_1}\) \(\newcommand{\VtxB}{v_2}\) \(\newcommand{\VertsA}{V_\textup{A}}\) \(\newcommand{\VertsB}{V_\textup{B}}\) \(\newcommand{\Edges}{E}\) \(\newcommand{\Edge}{e}\) \(\newcommand{\NofV}{\Card{V}}\) \(\newcommand{\NofE}{\Card{E}}\) \(\newcommand{\Graph}{G}\) \(\) \(\newcommand{\SCC}{C}\) \(\newcommand{\GraphSCC}{G^\text{SCC}}\) \(\newcommand{\VertsSCC}{V^\text{SCC}}\) \(\newcommand{\EdgesSCC}{E^\text{SCC}}\) \(\) \(\newcommand{\GraphT}{G^\text{T}}\) \(%\newcommand{\VertsT}{V^\textup{T}}\) \(\newcommand{\EdgesT}{E^\text{T}}\) \(\) \(%\) \(% NP-completeness etc\) \(%\) \(\newcommand{\Poly}{\textbf{P}}\) \(\newcommand{\NP}{\textbf{NP}}\) \(\newcommand{\PSPACE}{\textbf{PSPACE}}\) \(\newcommand{\EXPTIME}{\textbf{EXPTIME}}\)

AVL Trees

AVL trees are one form of height-balanced BSTs. The name “AVL” comes from the names Georgy Adelson-Velsky and Evgenii Landis, who presented the data structure in

G. M. Adel’son-Vel’skii and E. M. Landis, An algorithm for organization of information, Dokl. Akad. Nauk SSSR 146(2):263–266, 1962.

The definition is as follows:

Definition

A binary search tree is an AVL tree if, for each node \( x \) in it, it holds that the heights of the left and right sub-tree of \( x \) differ by at most one.

Example

  • ​ The two BSTs below are AVL trees.

    _images/avl-ex-good.png _images/avl-ex-good2.png
  • ​ The BST below is not an AVL tree as the AVL property is not holding in the node with the key 12: its left sub-tree is empty and has height -1 while the right sub-tree has height 1.

    _images/avl-ex-bad.png
  • ​ The BST below is not an AVL tree as the left child of the root has height 0 and the right one has height 2

    _images/avl-ex-bad2.png

If we have an AVL tree of height \( h \), how many nodes does it at least contain? This lower bound can be expressed as recurrence \( T(h) \) with

  • ​ \( T(-1) = 0 \) and \( T(0) = 1 \)

  • ​ \( T(h) = 1 + T(h-1) + T(h-2) \) for \( h \ge 2 \).

Recall that the Fibonacci numbers \( F_n \) are defined by \( F_0 = 0 \), \( F_1 = 1 \) and \( F_n = F_{n-1}+F_{n-2} \) for \( n \ge 2 \). Therefore, \( T(h) > F_{h+1} \). As \( F_n \) is the closest integer to \( \frac{\varphi ^{n}}{\sqrt {5}} \) with \( \varphi = 1.618… \), an AVL tree of height \( h \) contains an exponential amount of nodes with respect to the height \( h \) of the tree. Thus the height of an AVL tree with \( n \) nodes is logarithmic to \( n \). Because of this fact,

  • ​ searching keys and

  • ​ finding the smallest and largest keys

can be done in logarithmic time in AVL trees. We now only have to show how inserting and removing keys can be done in logarithmic time as well. In both cases, the idea is to rebalance the tree after a local modification (insert or removal) so that the AVL property holds again. The rebalancing proceeds only upwards in the tree and thus works in logarithmic time as well when the tree had the AVL property before the modification.

Inserting Keys

The first phase in inserting keys to an AVL tree works as with BSTs (recall the Section Inserting keys): we search the correct insertion place and insert a new leaf node \( z \) with the new key. But the height of the parent node \( p \) of \( z \), as well as its ancestors, may now have been increased by one and the AVL tree property does not necessarily hold anymore as there may be nodes whose other child has height two more than the other. Thus we have to rebalance the tree so that the AVL tree property holds again. We do this bottom-up, starting from the parent \( g \) of \( p \). (The AVL property already holds for the node \( p \), why?)

In general, we show how to rebalance the sub-tree rooted at a node \( x \) whose child \( y \) has height \( h+2 \) while the other child has height \( h \). It is assumed that the AVL property already holds for the sub-trees rooted at the children of \( x \). After rebalancing, the AVL property holds for the sub-tree. As the height of the sub-tree may have increased by one in the insertion, we then recursively check whether the parent of the sub-tree has to be rebalanced as well. For simplicity, we only present here the case when \( y \) is the right child of \( x \). The case of the left child is symmetric.

Case I: \( y \) is right-heavy

Recall that we assume that the sub-tree rooted at the right child \( y \) of \( x \) has height \( h+2 \) but that at the left child of \( x \) has height \( h \). In this first case, the height of the right sub-tree of \( y \) is \( h+1 \) and that of the left is \( h \). If we rotate \( x \) left, the sub-tree will be rooted now at \( y \) and the AVL property holds for it. This is illustrated in the figure below.

_images/avl-balance-rr.png

Example

  • ​ Consider the AVL tree shown below.

    _images/avl-insert-0.png
  • ​ Insert a new node with the key \( 19 \) and height 0.

    _images/avl-insert-0a.png
  • ​ The parent is balanced, update its height to 1.

    _images/avl-insert-0b.png
  • ​ Observe that the next parent \( x \) is not balanced: its left sub-tree is empty and thus has height -1, while the right one has height 1. The right sub-tree of \( x \) at \( y \) is right-heavy.

    _images/avl-insert-0c.png
  • ​ Thus we rotate \( x \) left to make the sub-tree balanced.

    _images/avl-insert-0d.png
  • ​ We observe that the next parent (the root) is balanced.

    _images/avl-insert-0e.png

Case II: \( y \) is neither left- or right-heavy

Again, the right sub-tree of \( x \) rooted at \( y \) has height \( h+2 \) but that at the left child of \( x \) has height \( h \). But in this case, the heights of the left and right sub-trees of \( y \) are both \( h+1 \). If we rotate \( x \) left, the sub-tree will be rooted now at \( y \) and the AVL property holds for it.

_images/avl-balance-re.png

Case III: \( y \) is left-heavy

Again, the sub-tree of \( x \) rooted at \( y \) has height \( h+2 \) but that at the left child of \( x \) has height \( h \). In this case, the height of the left sub-tree of \( y \) is \( h+1 \) and that of the right is \( h \). To balance the sub-tree at \( x \),

  • ​ we first rotate \( y \) right so that the right sub-tree of \( x \) becomes right-heavy, and

  • ​ then proceed as above by rotating \( x \) left.

_images/avl-balance-rl.png

Example

  • ​ Consider the AVL tree below.

    _images/avl-insert-1a.png
  • ​ Insert a new node with the key \( 15 \) and height 0.

    _images/avl-insert-1b.png
  • ​ The parent is balanced, update its height to 1.

    _images/avl-insert-1c.png
  • ​ Observe that the next parent \( x \) is not balanced: its left sub-tree is empty and thus has height -1, while the right one has height 1. The right sub-tree at \( y \) is left-heavy.

    _images/avl-insert-1d.png
  • ​ We thus first rotate \( y \) right to make the right sub-tree of \( x \) right-heavy…

    _images/avl-insert-1e.png
  • ​ and then the rotate \( x \) left to make the sub-tree balanced. Finally, we observe that the next parent (the root) is balanced.

    _images/avl-insert-1f.png

Example

  • ​ Consider the AVL tree drawn below.

    _images/avl-insert-2a.png
  • ​ We insert a node with the key \( 14 \) and height 0.

    _images/avl-insert-2b.png
  • ​ We observe that the parent and the grandparent are balanced and update their heights. We next observe that the next parent \( x \) is not balanced: its left sub-tree has height 0 while the right one has height 2. Furthermore, the right sub-tree at \( y \) is left-heavy.

    _images/avl-insert-2e.png
  • ​ We rotate \( y \) right to make the right sub-tree of \( x \) right-heavy, and …

    _images/avl-insert-2f.png
  • ​ then the rotate \( x \) left to make the sub-tree (and the whole tree) balanced.

    _images/avl-insert-2g.png

Removing keys

The basic removal process is as with unbalanced BSTs (recall Section Removing keys). But when we remove a node, the height of its parent node and all its ancestors can decrease by one. Again, this creates an inbalance of at most two in the heights of sub-trees and we can use the above rebalancing techniques to restore the AVL property.

Example

  • ​ Consider the AVL tree shown below.

    _images/avl-remove-1a.png
  • ​ When we remove the key \( 20 \), we remove the node \( z \) and substitute \( w \) in its place. We now observe that the parent \( x \) is unbalanced and its left child \( y \) is left-heavy.

    _images/avl-remove-1b.png
  • ​ We thus rotate \( x \) right to regain the balance.

    _images/avl-remove-1c.png