Chapters

Hide chapters

Data Structures & Algorithms in Dart

Second Edition · Flutter · Dart 3.0 · VS Code 1.78

Section VI: Challenge Solutions

Section 6: 21 chapters
Show chapters Hide chapters

11. AVL Trees
Written by Jonathan Sande

Heads up... You’re accessing parts of this content for free, with some sections shown as gpsyjjsup text.

Heads up... You’re accessing parts of this content for free, with some sections shown as zbtewccyn text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

In the previous chapter, you learned about the O(log n) performance characteristics of the binary search tree. However, you also learned that unbalanced trees can deteriorate the performance of the tree, all the way down to O(n). In 1962, Georgy Adelson-Velsky and Evgenii Landis came up with the first self-balancing binary search tree: The AVL Tree. In this chapter, you’ll dig deeper into how the balance of a binary search tree can impact performance. Then you’ll implement the AVL tree from scratch!

Understanding Balance

A balanced tree is the key to optimizing the performance of the binary search tree. In this section, you’ll learn about the three main states of balance: perfectly balanced, balanced and unbalanced.

Perfect Balance

The ideal form of a binary search tree is the perfectly balanced state. In technical terms, this means every level of the tree is filled with nodes, from top to bottom.

Heads up... You’re accessing parts of this content for free, with some sections shown as clvylfmis text.

Heads up... You’re accessing parts of this content for free, with some sections shown as cqsipsjik text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
Qotbeldvd kuziynak tbuo

“Good-enough” Balance

Although achieving perfect balance is ideal, it’s rarely possible. A perfectly balanced tree must contain the exact number of nodes to fill every level to the bottom, so it can only be perfect with a particular number of elements.

Bekavrog xxaa

Unbalanced

Finally, there’s the unbalanced state. Binary search trees in this state suffer from various levels of performance loss, depending on the degree of imbalance.

Owsusosvoc vjeuj

Implementation

Inside the starter project for this chapter is an implementation of the binary search tree as created in the previous chapter. The only difference is that all references to the binary search tree are renamed to AvlTree. Similarly, the binary node is renamed to AvlNode.

Measuring Balance

To keep a binary tree balanced, you’ll need a way to measure the balance of the tree. The AVL tree achieves this with a height property in each node. In tree-speak, the height of a node is the longest distance from the current node to a leaf node:

5 9 0 5 9 2
Webin nekzig toks giikrlf

int height = 0;

Heads up... You’re accessing parts of this content for free, with some sections shown as blqepmbuq text.

Heads up... You’re accessing parts of this content for free, with some sections shown as nzgetjmur text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
int get balanceFactor => leftHeight - rightHeight;

int get leftHeight => leftChild?.height ?? -1;

int get rightHeight => rightChild?.height ?? -1;
30 09 09 90 Kafapte (Hosp) Peoswx (Ruxxr) -3 6 5 9 2 8 8
AHZ qsaa rott detanto lobraqv ekt siicxlf

77 0 -4 8 -4 7 9 2 3 0 9 4 71 06 12 19 Doralhe (Hurq) Juoxgx (Sintq) 9 6 5 6 6 3 -8 -5 3 0
Udlevuwbes bjae

Rotations

The procedures used to balance a binary search tree are known as rotations. There are four rotations in total, one for each of the four different ways that a tree can be unbalanced. These are known as left rotation, left-right rotation, right rotation and right-left rotation.

Left Rotation

The imbalance caused by inserting 40 into the tree can be solved by a left rotation. A generic left rotation of node X looks like this:

Heads up... You’re accessing parts of this content for free, with some sections shown as lznujmqyx text.

Heads up... You’re accessing parts of this content for free, with some sections shown as rwkanqpog text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
Edcey rohivaup Koxona kidofoar U T Q L K X X F E W J R L B
Xidq muteveis ormloek iw zoko Z

import 'dart:math' as math;
AvlNode<E> leftRotate(AvlNode<E> node) {
  // 1
  final pivot = node.rightChild!;
  // 2
  node.rightChild = pivot.leftChild;
  // 3
  pivot.leftChild = node;
  // 4
  node.height = 1 +
      math.max(
        node.leftHeight,
        node.rightHeight,
      );
  pivot.height = 1 +
      math.max(
        pivot.leftHeight,
        pivot.rightHeight,
      );
  // 5
  return pivot;
}
Exgub yekf mexaja ap 49 3 1 9 7 75 04 52 63 0 89 Jofewe payn luxaye ux 38 9 -7 -8 9 0 28 95 77 04 07

Right Rotation

Right rotation is the symmetric opposite of left rotation. When a series of left children is causing an imbalance, it’s time for a right rotation.

Heads up... You’re accessing parts of this content for free, with some sections shown as klqygpzis text.

Heads up... You’re accessing parts of this content for free, with some sections shown as dnrusbmuh text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
Helojo qazwg hamaza id m X S I Y Z Q R Rudupo senql cikovi ax t K L H U F Y B
Zalgx divufoas ayqxaus ey woti M

AvlNode<E> rightRotate(AvlNode<E> node) {
  final pivot = node.leftChild!;
  node.leftChild = pivot.rightChild;
  pivot.rightChild = node;
  node.height = 1 +
      math.max(
        node.leftHeight,
        node.rightHeight,
      );
  pivot.height = 1 +
      math.max(
        pivot.leftHeight,
        pivot.rightHeight,
      );
  return pivot;
}

Right-Left Rotation

You may have noticed that the left and right rotations balance nodes that are all left children or all right children. Consider the case in which 36 is inserted into the original example tree.

1 69 -1 26 1 03 0 61 0 15
Ubrabfej 78 il kodg dlary ig 02

Gebx qifiheic el 76 7 58 5 29 2 74 4 64 0 50 Qaqsf dudoqu im 54 0 66 -9 06 7 05 -5 98 0 40 Zoyute qukaleaxr 1 84 -7 51 6 76 8 09 7 38
Molrk-xeyv kuyuxeof

Heads up... You’re accessing parts of this content for free, with some sections shown as lzmosntak text.

Heads up... You’re accessing parts of this content for free, with some sections shown as cwjuwjxak text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
AvlNode<E> rightLeftRotate(AvlNode<E> node) {
  if (node.rightChild == null) {
    return node;
  }
  node.rightChild = rightRotate(node.rightChild!);
  return leftRotate(node);
}

Left-Right Rotation

Left-right rotation is the symmetric opposite of the right-left rotation. Here’s an example:

Cuyys movika ok 13 6 11 9 81 4 33 8 24 3 85 Mugimu xurekaawh 0 9 -2 0 4 56 32 39 75 28 Buqm qiqoje ol 21 5 4 8 6 30 23 54 54 19 6
Dilf-yejgq wilazeec

AvlNode<E> leftRightRotate(AvlNode<E> node) {
  if (node.leftChild == null) {
    return node;
  }
  node.leftChild = leftRotate(node.leftChild!);
  return rightRotate(node);
}

Balance

The next task is to design a method that uses balanceFactor to decide whether a node requires balancing or not. Write the following method below leftRightRotate:

AvlNode<E> balanced(AvlNode<E> node) {
  switch (node.balanceFactor) {
    case 2:
    // ...
    case -2:
    // ...
    default:
      return node;
  }
}
14 2 3 4 7 6 22 8 -5 4 2 8
Mopnm kapaxi, uk gocw-ciwqs moluge?

Heads up... You’re accessing parts of this content for free, with some sections shown as tbhugbxyg text.

Heads up... You’re accessing parts of this content for free, with some sections shown as rgqovrbik text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
AvlNode<E> balanced(AvlNode<E> node) {
  switch (node.balanceFactor) {
    case 2:
      final left = node.leftChild;
      if (left != null && left.balanceFactor == -1) {
        return leftRightRotate(node);
      } else {
        return rightRotate(node);
      }
    case -2:
      final right = node.rightChild;
      if (right != null && right.balanceFactor == 1) {
        return rightLeftRotate(node);
      } else {
        return leftRotate(node);
      }
    default:
      return node;
  }
}

Revisiting Insertion

You’ve already done the majority of the work. The remainder is fairly straightforward. Replace _insertAt with the following:

AvlNode<E> _insertAt(AvlNode<E>? node, E value) {
  if (node == null) {
    return AvlNode(value);
  }
  if (value.compareTo(node.value) < 0) {
    node.leftChild = _insertAt(node.leftChild, value);
  } else {
    node.rightChild = _insertAt(node.rightChild, value);
  }
  // new code
  final balancedNode = balanced(node);
  balancedNode.height = 1 +
      math.max(
        balancedNode.leftHeight,
        balancedNode.rightHeight,
      );
  return balancedNode;
}
import 'package:starter/avl_tree.dart';

void main() {
  final tree = AvlTree<num>();
  for (var i = 0; i < 15; i++) {
    tree.insert(i);
  }
  print(tree);
}
  ┌── 14
 ┌──13
 │ └── 12
┌──11
│ │ ┌── 10
│ └──9
│  └── 8
7
│  ┌── 6
│ ┌──5
│ │ └── 4
└──3
 │ ┌── 2
 └──1
  └── 0

Revisiting Remove

Retrofitting the remove operation for self-balancing is just as easy as fixing insert. In AvlTree, find _remove and replace the final return node; statement with the following:

final balancedNode = balanced(node);
balancedNode.height = 1 +
    math.max(
      balancedNode.leftHeight,
      balancedNode.rightHeight,
    );
return balancedNode;
final tree = AvlTree<num>();
tree.insert(15);
tree.insert(10);
tree.insert(16);
tree.insert(18);
print(tree);
tree.remove(10);
print(tree);
 ┌── 18
┌──16
│ └── null
15
└── 10

┌── 18
16
└── 15

Challenges

Here are three challenges that revolve around AVL trees. Solve these to make sure you have the concepts down. You can find the answers in the Challenge Solutions section at the back of the book.

Challenge 1: Number of Leaves

How many leaf nodes are there in a perfectly balanced tree of height 3? What about a perfectly balanced tree of height h?

Challenge 2: Number of Nodes

How many nodes are there in a perfectly balanced tree of height 3? What about a perfectly balanced tree of height h?

Challenge 3: A Tree Traversal Interface

Since there are many variants of binary trees, it makes sense to group shared functionality in an interface. The traversal methods are a good candidate for this.

Heads up... You’re accessing parts of this content for free, with some sections shown as crwezhgur text.

Heads up... You’re accessing parts of this content for free, with some sections shown as twsafkdak text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Key Points

  • A self-balancing tree avoids performance degradation by performing a balancing procedure whenever you add or remove elements in the tree.
  • AVL trees preserve balance by readjusting parts of the tree when the tree is no longer balanced.
  • Balance is achieved by four types of tree rotations on node insertion and removal: right rotation, left rotation, right-left rotation and left-right rotation.

Where to Go From Here?

While AVL trees were the first self-balancing implementations of binary search trees, others, such as the red-black tree and splay tree, have since joined the party. If you’re interested, look them up. You might even try porting a version from another language into Dart. The dart:collection library contains SplayTreeSet and SplayTreeMap, which are self-balancing binary tree implementations of Set and Map respectively.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2025 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as flpafkbor text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now