Trees

General Trees

Allowing Hierarchical Relationships

Key Terms

  • Parents / Children
  • Siblings
  • Root / Subtrees
  • Paths
  • Depth / Height
  • Descendants / Ancestors
  • Internal Nodes / Leaves
  • K-ary
  • Full / Complete / Perfect

Implementing General Trees

What should a node contain?

  • Data
  • Parent
  • Array of children

Traversals

A traversal is a method that visits every node in a tree once.

Preorder Traversals

            
              def preorder(p):
                visit(p)
                for child in p.children:
                  preorder(child)
            
          

Postorder Traversals

            
              def postorder(p):
                for child in p.children:
                  postorder(child)
                visit(p)
            
          

Inorder Traversals

            
              def inorder(p):
                for child in p.children[:len(p.children) // 2]:
                  inorder(child)
                visit(p)
                for child in p.children[len(p.children) // 2:]:
                  inorder(child)
            
          

Binary Trees

Fixing k = 2

Implementing Binary Trees

What is the minimum a node could contain?

  • Data
  • Left Child
  • Right Child

Binary Search Trees

Maintaining systematic ordering

Binary Search Trees

Binary Search Trees: Search

Pseudocode


                def search(key):
                  return search(root, key)
                
                def search(node, key):
                  if not node:
                    return False
                  elif node.data == key:
                    return True
                  elif key > node.data:
                    return search(node.right, key)
                  else: # key must be < data
                    return search(node.left, key)
            

Binary Search Trees: Insert

Pseudocode


                def insert(key):
                  insert(root, key)
                def insert(node, key):
                  if not node:
                    return Node(key)
                  elif key < node.data:
                    node.left = insert(
                      node.left, data
                    )
                  elif key > node.data:
                    node.right = insert(
                      node.right, data
                    )
                  return node
              

Binary Search Trees: Remove

What cases must we consider?

  1. Node is a leaf
  2. Node has 1 child
  3. Node has 2 children

Analysis

How does it perform?

Tree Shape is Important!

Worst Case? Best Case? Average Case?

Average Case: $\approx 1.39 \log n = O(\log n)$

Collections as Arrays

Function Unordered List Ordered List BST
Search O(n) O(log n) O(h)
Insert O(n) O(n) O(h)
Delete O(n) O(n) O(h)
Min / Max O(n) O(1) O(h)
Floor / Ceil O(n) O(log n) O(h)
Rank O(n) O(log n) O(h)

Priority Queues

Introducing the Binary Heap

Priority Queue: API

  • Push: Places an element into the queue
  • Pop: Removes and returns the next highest priority item from the queue.

Real-life applications? Hospital Lines!

Cost to implement with ordered lists? Unordered Lists?

An unordered list: $O(1), O(n)$.
Ordered list: $O(n), O(1)$
We can do better.

Priority Queue: Implementation

What if we maintained a somewhat ordered array?

Binary Heaps

Heap-ordered complete binary trees

  • Heap Ordered
  • Array Representation
  • Push
  • Swimming Up
  • Pop
  • Sinking Down

Binary Heap: Further Considerations

  • Underflow: Throw an exception
  • Overflow: Use a dynamic array
  • Max-Oriented: Swap the comparisons
  • Remove: Remove an arbitrary item.
  • Change Priority of an Item
  • Immutability of Keys: Client should not be able to change the keys in the array.

Priority Queue: Costs Summary

Implementation Push Pop Peek
Unordered Array 1 n n
Ordered Array n 1 1
Binary Heap log n log n 1
d-ary Heap $\log_d n$ $\log_d n$ 1
Fibonacci 1 log n 1
Brodal Queue 1 log n 1
Impossible 1 1 1

Balanced Search Trees

Guaranteeing $\log n$ performance

2-3 Trees

  • 2-node: one key, two children
  • 3-node: two keys, three children

This scheme achieves perfect balance, every path from root to null has same length.