# Binary Search Trees --- CS 137 // 2021-09-15 ## Administrivia - You should have turned in: + Your reflection for daily exercise 3 + Your solution to daily exercise 4 # Questions ## ...about anything? # Daily Exercise # Review ## Sequential Data Structures | **Operation** | **Array** | **Linked** | |-----------------|-----------|------------| | `add to front` | O(1)* | O(1) | | `add to back` | O(1)* | O(1) | | `remove front` | O(1) | O(1) | | `remove back` | O(1) | O(1) | | `random access` | O(1) | O(n) | - O(1)* means "amortized" constant time ## Dictionary Data Structure | **Operation** | **Unsorted** | **Sorted** | |-----------------|--------------|------------| | `search` | O(n) | O(log n) | | `insert` | O(1) | O(n) | | `delete` | O(n)* | O(n) | - Assumes using a sequential container implementation (e.g. array or linked list) - The O(n)* can be improved to O(1) for deletion if we know the index of the element # Binary Search Trees ## Review: Linked Lists - Linked lists consists of a sequence of "nodes" ![Linked List](https://media.geeksforgeeks.org/wp-content/cdn-uploads/gq/2013/03/Linkedlist.png) - Nodes contain at least two things: 1. A **value** that is stored in the node 2. A **pointer** to the next node in the sequence ## Review: Linked Lists - An implementation of the `Node` type typically looks something like the following: ```java class Node
{ E value; // data stored inside the node Node next; // pointer to the next element } ``` ## Trees - A **tree** is a linked data structure where each node has a reference to **zero or more** other nodes
dfa
A
A
B
B
A->B
C
C
A->C
D
D
A->D
F
F
B->F
G
G
B->G
E
E
C->E
- Trees are **acyclic**, so the arrows only go "top down" ## Trees
dfa
A
A
B
B
A->B
C
C
A->C
D
D
A->D
F
F
B->F
G
G
B->G
E
E
C->E
- The "top" of the tree is called the **root** - Every node (except the root) has a **parent** - Nodes with zero children are called **leaves** - A tree with no nodes is called **empty** # Examples of Trees ## Thinking Recursively - A tree is a **recursive** data structure since every tree has one of two forms: + The tree could be **empty** (the root is empty) + The tree is rooted with a **node** that contains: 1. A value stored within the node 2. A list of other trees (its **children**) ## Binary Trees - A **binary tree** has nodes with at most two children
dfa
A
A
B
B
A->B
C
C
A->C
E
E
B->E
F
F
B->F
G
G
C->G
H
H
C->H
## Binary Trees - The `Node` type for a binary tree would look something like this: ```java class Node
{ E value; // data stored in the node Node left; // left subtree Node right; // right subtree } ``` ## Binary Trees - A **perfect** binary tree is one where all the leaves share the same level
dfa
A
A
B
B
A->B
C
C
A->C
E
E
B->E
F
F
B->F
G
G
C->G
H
H
C->H
- The **height** of a tree is the number of nodes in the longest path from the root to some leaf ## Exercise
dfa
A
A
B
B
A->B
C
C
A->C
E
E
B->E
F
F
B->F
G
G
C->G
H
H
C->H
- How many nodes does a *perfect binary tree* have if its height is $h$? - We can sum up the nodes in each level... + $1 + 2 + 4 + ...$ ## Binary Tree Traversals - How can we iterate over the elements of a tree? + Recursively! - Given a root node, we need to do: 1. Process the root 2. Recursively iterate the left subtree 3. Recursively iterate the right subtree ## In-Order Traversals - An **in-order** traversal is one that iterates over the left subtree first, then the root, then the right subtree ```java void iterateInOrder(Node
root) { if (root != null) { iterateInOrder(root.left); // do something with root.value here iterateInOrder(root.right); } } ``` ## Pre-Order Traversals - A **pre-order** traversal is one that visits the root, then the left subtree, then the right subtree ```java void iteratePreOrder(Node
root) { if (root != null) { // do something with root.value here iteratePreOrder(root.left); iteratePreOrder(root.right); } } ``` ## Post-Order Traversals - A **post-order** traversal is one that iterates over the left subtree, then the right subtree, then the root ```java void iteratePostOrder(Node
root) { if (root != null) { iteratePostOrder(root.left); iteratePostOrder(root.right); // do something with root.value here } } ``` # Binary Search Trees ## Binary Search Trees - Store key/value pairs just like a dictionary - Keys follow the BST property: + For each node in the tree: * Every key in the left subtree is **less than** the node's key * All keys in the right subtree are **greater than** the nodes's key + For now, assume no duplicates, but we could allow them in the left subtree ## Binary Search Tree ```java class Node
{ K key; // key stored in the node V value; // value stored in the node Node left; // left subtree Node right; // right subtree } ``` ## Binary Search Tree
dfa
4 // A
4 // A
2 // B
2 // B
4 // A->2 // B
6 // C
6 // C
4 // A->6 // C
1 // D
1 // D
2 // B->1 // D
3 // E
3 // E
2 // B->3 // E
5 // F
5 // F
6 // C->5 // F
7 // G
7 // G
6 // C->7 // G
- If I traverse a binary search tree **in-order**, then the keys will come up in ascending order ## Implementing a Dictionary with a BST - Recall that common dictionary operations include + `search(key)` + `insert(key, value)` + `delete(key)` - How would we implement these in a BST? ## Searching in a BST ```java V search(Node
node, K key) { if (node == null) return null; else if (key == node.key) return node.value; else if (key < node.key) return search(node.left, key); else return search(node.right, key); } ``` ## Inserting/Deleting in a BST - Similar to searching - For inserting, we search for the location the node *should be* and then add a new node there - For deleting, we search for the node that contains the key and then remove it ## Runtime Complexity of BST - If we implement a dictionary using a BST, then what is the complexity of each of these operations? - $O(h)$ where $h$ is the height of the tree - If a tree has $n$ nodes, what is the height of the tree? + In the worst-case, $O(n)$! ## Self-Balancing Trees - It is possible to implement BSTs that are **self-balancing**, ensuring that $h = O(\log n)$ - Two approaches are AVL trees and Red-Black trees ## Self-Balancing Trees - With self-balancing trees, it is possible to implement a dictionary with the following complexity | **Operation** | **BST** | |-----------------|--------------| | `search` | O(log n) | | `insert` | O(log n) | | `delete` | O(log n) |