Home

Trees


A tree is a hierarchical data structure consisting of a set of connected nodes where:

Each node may have multiple other nodes as children (depending on the type of tree)

Each node is connected to one parent except the root node

A binary tree is a tree where each node can have up to two child nodes, referred to as the left child and the right child.

Binary Search Trees


A binary search tree is an ordered binary tree, where for each node:

Terminologies


The root node is the node with no parent node.

A leaf node is a node that has no child nodes.

An internal node is a node that has at least one child node.

bstterm

Height of a tree: Maximum path length from the root node to a leaf

bstheight

For a tree with $n$ nodes:

For a given number of nodes, a tree is said to be

bstbalanceddegenerate

BST Operations


The height $h$ of a binary search tree determines the efficiency of many operations, so we will use both $n$ and $h$ in our analyses.

Insertion


Insertion method:

Analysis:


Given a BST $t$ and a value $v$, return true if $v$ is in the BST and false otherwise

Method is similar to insertion, except we don’t create a new node.

Analysis:

Traversal


There are 4 common ways to traverse a binary tree:

  1. Pre-order (NLR):
  1. In-order (LNR):
  1. Post-order (LRN):
  1. Level-order:

Analysis:

Level order traversal pseudocode:

Level Order Traversal(BST t):
  initialise an empty queue
  add t's root node to the queue
  while the queue is not empty do
    remove node x from the front of the queue
    print the value in x
    add the left child (if any) of x to the queue
    add the right child (if any) of x to the queue
  end while

P.S. level order traversal can be seen as a BFS

Join


Given two BSTs $t_1$ and $t_2$ where max($t_1$) < min($t_2$), return a BST containing all items from $t_1$ and $t_2$

Method:

  1. Find the minimum node $min$ in $t_2$
  2. Replace $min$ by its right subtree (if it exists)
  3. Elevate $min$ to be the new root of $t_1$ and $t_2$

Analysis:

Deletion


Given a BST $t$ and a value $v$, delete $v$ from the BST, and return the root of the updated BST

Recursive method:

Analysis:

Balance


Types of balance:

Balancing Operations


Rotation


struct node *rotateRight(struct node *root) {
  if (root == NULL || root->left == NULL) return root;
  struct node *newRoot = root->left;
  root->left = newRoot->right;
  newRoot->right = root;
  return newRoot;
}

struct node *rotateLeft(struct node *root) {
  if (root == NULL || root->right == NULL) return root;
  struct node *newRoot = root->right;
  root->right = newRoot->left;
  newRoot->left = root;
  return newRoot;
}

Time complexity: O(1)

Partition


Rearrange the tree so that the element with index $i$ becomes the root

Method:

partition(t, i):
  Input: tree t, index i
  Output: tree with i-th item moved to root

  leftSize = size(t->left)

  if i < leftSize:
    t->left = partition(t->left, i)
    t = rotateRight(t)
  else if i > leftSize:
    t->right = partition(t->right, i - leftSize - 1)
    t = rotateLeft(t)
  return t

Analysis:

struct node {
    int item;
    struct node *left;
    struct node *right;
    int size;
};

Balancing Methods


Global Rebalancing


Completely rebalance whole tree so it is size-balanced

Method:

rebalance(t):
  Input: tree t
  Output: rebalanced t

  if size(t) < 3:
    return t

  t = partition(t, size(t) / 2)
  t->left = rebalance(t->left)
  t->right = rebalance(t->right)
  return t

Worst-case time complexity: $O(n \log n)$

Periodic Rebalancing:

bstInsert(t, v):
  Input: tree t, value v
  Output: t with v inserted

  t = insertAtLeaf(t, v)

  if size(t) mod k = 0:
    t = rebalance(t)
  
  return t

Local Rebalancing


Perform small, efficient, localised operations in an attempt to improve the overall balance of the tree

Root Insertion

Insert new value normally (at the leaf) and then rotate the new node up to the root.

insertAtRoot(t, v):
  Input: tree t, value v
  Output: t with v inserted at the root

  if t is empty:
    return new node containing v
  else if v < t->item:
    t->left = insertAtRoot(t->left, v)
    t = rotateRight(t)
  else if v > t->item:
    t->right = insertAtRoot(t->right, v)
    t = rotateLeft(t)

  return t

Analysis:

Randomised Insertion

Introduce some randomness into insertion algorithm:

insertRandom(t, v):
  Input: tree t, value v
  Output: t with v inserted
  if t is empty:
    return new node containing v

  // p/q chance of inserting at root
  if random() mod q < p:
    return insertAtRoot(t, v)
  else:
    return insertAtLeaf(t, v)

Randomised insertion creates similar results to inserting items in random order.

Tree is more likely to be balanced (but no guarantee)

Summary

Balancing Summary

AVL Trees


Method:

Since AVL trees are always height-balanced, the height of an AVL tree is guaranteed to be at most $\log n$

Insertion


Insertion process:

  1. if the tree is empty:
  1. insert recursively
  2. check (and fix) balance
  3. return root of updated tree

There are 4 rebalancing cases:

  1. Left Left
  1. Left Right
  1. Right Left
  1. Right Right

Analysis:

Search


Exactly the same as for regular BSTs.

Worst-case time complexity is $O(\log n)$, since AVL trees are height-balanced.

Deletion


Method:

Analysis:

Summary


AVL trees are always height-balanced