CSE-250 Fall 2022 - Section B - Heaps, Sets, Bags, and Ordered Trees Oct 26, 2022

## Heaps (contd...)

#### Analysis

Enqueue
Append to ArrayBuffer: $O(n)$, Amortized $O(1)$
fixUp: $O(\log(n))$ fixes, each $O(1)$ = $O(\log(n))$
Total: Amortized $O(\log(n))$, Worst-case $O(n)$
Dequeue
Remove End of ArrayBuffer: $O(1)$
fixDown: $O(\log(n))$ fixes, each $O(1)$ = $O(\log(n))$
Total: Worst-case $O(\log(n))$

#### Heap Sort

• Insert items into priority queue with enqueue
• Reconstruct sequence (in reverse order) with dequeue

#### Heap Sort

Enqueue Element $i$
$O(\log(i))$
Dequeue Element $i$
$O(\log(n-i))$

$$\left(\sum_{i=1}^n O(\log(i))\right) + \left(\sum_{i=1}^n O(\log(n-i))\right)$$

$$< O(n\log(n))$$

#### Updating Heap Elements

After updating current, fixUp or fixDown.

If new current > parent, current moves up.
If new current < parent, current moves down.

How do we know where the value appears in the heap?

#### Heapify

Input: Array

Output: Array reorderd to be a heap

#### Heapify

Level $\log(n)$
fixDown all $\frac{n}{2}$ nodes at this level (max $0$ swaps each)
Level $\log(n)-1$
fixDown all $\frac{n}{4}$ nodes at this level (max $1$ swaps each)
Level $\log(n)-2$
fixDown all $\frac{n}{8}$ nodes at this level (max $2$ swaps each)

#### Heapify

$$O\left(\sum_{i = 1}^{\log(n)} \frac{n}{2^{i}} \cdot (i+1) \right)$$

$$O\left(n \sum_{i = 1}^{\log(n)} \frac{i}{2^{i}} + \frac{1}{2^i}\right)$$

$$O\left(n \sum_{i = 1}^{\log(n)} \frac{i}{2^{i}}\right)$$

$$O\left(n \sum_{i = 1}^{\infty} \frac{i}{2^{i}}\right)$$

$\sum_{i = 1}^{\infty} \frac{i}{2^{i}}$ is known to converge to a constant.

$$O\left(n\right)$$

#### Heapify

We can go from an unsorted array to a heap in $O(n)$

(but heap sort still requires $n \log(n)$ for dequeueing)

## Sets and Bags

#### Set

An unordered collection of unique elements.

• Order doesn't matter
• At most one copy of each item (key)

#### Set

mutable.Set[T]
Store one copy of element if not already present.
apply(element: T): Boolean
Return true if the element is present.
remove(element: T): Boolean
Remove the element if present or return false if not.

#### Bag

An unordered collection of non-unique elements.

• Order doesn't matter
• Multiple copies of a key allowed.

#### Bag

mutable.Bag[T]
Register the presence of a new (copy of) the element.
apply(element: T): Int
Return the number of copies of the element present.
remove(element: T): Boolean
Remove one copy the element if present or return false if not.

Property Seq Set Bag
Explicit Order
Enforced Uniqueness
Iterable

## (Rooted) Trees

#### Tree Terminology

Rooted, Directed Tree
Root is the source node
Parent of node X
A node with an out-edge to X (Max 1)
Child of node X
A node with an in-edge from X
Depth of a node X
Number of edges in the path from X to the root.
Height of a node
Number of edges in the path from X to the deepest leaf.

#### Tree Terminology

Level of a node.
Depth +1
Size of a tree ($n$)
The number of nodes in the tree
Height/Depth of a tree ($d$)
The height of the root / depth of the deepest leaf.

#### Tree Terminology

Binary Tree
Every vertex has at most 2 children
Complete Binary Tree
All leaf vertices at the deepest 2 levels.
Full Binary Tree
All leaf vertices at the deepest level.
(Every vertex has exactly 0 or 2 children)
$$d = \log(n)$$

#### Tree Nodes (via Option)


class TreeNode[T](
var _value: T,
var _left: Option[TreeNode[T]]
var _right: Option[TreeNode[T]]
)

class Tree[T] {
var root: Option[TreeNode[T]] = None // empty tree
}


#### Tree Nodes (via Traits)


trait Tree[+T]

case class TreeNode[T](
value: T,
left: Tree[T],
right: Tree[T]
) extends Tree[T]

case object EmptyTree extends Tree[Nothing]


#### Case Classes/Objects

Feature 1: Inline constructors (no new)
TreeNode(10, EmptyTree, EmptyTree)
Feature 2: Match deconstructors
foo match { case TreeNode(v, l, r) => ... }

#### Case Classes/Objects


def printTree[T](root: ImmutableTree[T], indent: Int) =
{
root match {
case TreeNode(v, left, right) =>
print((“ “ * indent) + v)
printTree(left, indent + 2)
printTree(right, indent + 2)

case EmptyTree =>
/* Do Nothing */
}
}


#### Computing Tree Height

The height of a tree is the height of the root.

$$h(root) = \begin{cases} 0 & \textbf{if the tree is empty}\\ 1 + max(h(\texttt{root.left}), h(\texttt{root.right})) & \textbf{otherwise} \end{cases}$$

#### Computing Tree Height

$$h(root) = \begin{cases} 0 & \textbf{if the tree is empty}\\ 1 + max(h(\texttt{root.left}), h(\texttt{root.right})) & \textbf{otherwise} \end{cases}$$

def height[T](root: Tree[T]): Int =
{
root match {
case EmptyTree =>
0

case TreeNode(v, left, right) =>
1 + Math.max( height(left), height(right) )
}
}


#### Binary Search Tree

A Binary Tree over where each node stores a unique key, and a value's keys are ordered.

##### Enforce Constraints
• No duplicate keys
• For every node $X_L$ in the left sub-tree of a node $X_1$: $X_L\texttt{.key} < X_1\texttt{.key}$
• For every node $X_R$ in the left sub-tree of a node $X_1$: $X_R\texttt{.key} > X_1\texttt{.key}$

$X_1$ partitions its children.

#### Find

Goal: Find an item with key $k$ in a BST rooted at root

• Is root empty? (if so, not present)
• Does root.value have key $k$? (If so, done!)
• Is $k$ lesser than root.value's key? (Try left subtree)
• Is $k$ greater than root.value's key? (Try right subtree)

#### Find


def find[V: Ordering](root: BST[V], target: V): Option[V] =
root match {
case TreeNode(v, left, right) =>
if(Ordering[V].lt( target, v )){ return find(left, target) }
else if(Ordering[V].lt( v, target )){ return find(right, target) }
else                                { return Some(v) }

case EmptyTree =>
return None
}


What's the complexity? (how many times do we call 'find'?) $O(d)$

#### Insert

Goal: Insert an item with key $k$ in a BST rooted at root

• Is root empty? (if so, insert here)
• Does root.value have key $k$? (If so, already present!)
• Is root.value's key greater than $k$? (Left subtree)
• Is root.value's key lesser than $k$? (Right subtree)

#### Insert

  def insert[V: Ordering](root: BST[V], value: V): BST[V] =
node match {
case TreeNode(v, left, right) =>
if(Ordering[V].lt( target, v ) ){
return TreeNode(v, insert(left, target), right)
} else if(Ordering[V].lt( v, target ) ){
return TreeNode(v, left, insert(right, target))
} else {
}

case EmptyTree =>
return TreeNode(value, EmptyTree, EmptyTree)
}

What's the complexity? $O(d)$

#### Remove

Goal: Remove the item with key $k$ from a BST rooted at root

• Find the item
• Replace the node with the right subtree.
• Insert the left subtree under the right.

What's the complexity? $O(d)$

#### BST Mutations

Operation Runtime
find $O(d)$
insert $O(d)$
remove $O(d)$

What's that in terms of $n$? $O(n)$

Does it need to be that bad?

#### Bags

How do we implement bags?

Idea 1: Just allow multiple copies ($X_L \leq X_1$)

Idea 2: One copy, but store a count

#### BSTs with Distinct Key/Value Pairs


trait Tree[+K, +V]

case class TreeNode[K, V](
key: K,
value: V,
left: Tree[K, V],
right: Tree[K, V]
) extends Tree[K, V]

case object EmptyTree extends Tree[Nothing, Nothing]


Balancing Trees