CSE-250 Fall 2022 - Section B - Priority Queues, Heaps

Oct 24, 2022

- enqueue(v: A): Unit
- Insert a value $v$ into the priority queue.
- dequeue: A
- Remove the greatest element in the priority queue.
- head: A
- Peek at the greatest element in the priority queue.

Operation | Lazy | Proactive |
---|---|---|

enqueue | $O(1)$ | $O(n)$ |

dequeue | $O(n)$ | $O(1)$ |

head | $O(n)$ | $O(1)$ |

Can we do better?

- Lazy
- Fast Enqueue, Slow Dequeue
- Proactive
- Slow Enqueue, Fast Dequeue
- ???
- Fast(-ish) Enqueue, Fast(-ish) Dequeue

**Idea: ** Keep the priority queue "kinda" sorted.

Larger items tend to be closer to the front of the list. The closer we are to the front of the list, the more sorted it gets.

**Challenge: ** How do we keep track of which elements are still sorted?

**Idea: ** Organize the priority queue as a tree

**Directed: ** A directed edge means "≥"

__Child__- An adjacent node connected by an out-edge
__Leaf__- A node with no children
__Depth__of a node- The number of edges from the root to the node
__Depth__of a tree- The maximum depth of any node in the tree
__Level__of a node- The depth + 1

Organize the priority queue as a tree

**Directed: ** A directed edge means "≥"

**Binary: ** Max out-degree of 2 (Easy to reason about)

**Complete: ** Every "level" except the last is full

**Balanced:**TBD (loosely, all leaves at ~ same level)- Easy to encode in an array (later today)

All nodes in the last level are all-the-way left

Ordering by "≥" gets us a "Max-Heap"

What is the depth of a binary heap containing $n$ items?

- Level 1: Up to 1 item
- Level 2: Up to 2 items
- Level 3: Up to 4 items
- Level 4: Up to 8 items
- Level 5: Up to 16 items
- Level $i$: Up to $O(2^i)$ items

What is the depth of a binary heap containing $n$ items?

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

$$\ell_{max} = O\left(\log(n)\right)$$

- enqueue(elem: A) [pushHeap]
- Place an item into the heap
- dequeue: A [popHeap]
- Remove and return the maximal element from the heap
- head: A
- The maximal element in the heap
- length: Int
- The number of elements in the heap.

**Idea:** Insert element at next available spot, then fix.

- Call the insertion point
`current` - While
`current != root`and`current > parent`- Swap
`current`with`parent` - Repeat with
`current`←`parent`

- Swap

```
def fixUp[A: Ordering](current: Vertex[A]): Unit =
{
if(current.parent.isDefined){
val parent = current.parent.get
if( Ordering[A].lt( parent.value, current.value ) ){
swap(current.value, parent.value)
fixUp(parent)
}
}
}
```

What's the complexity?

(How many swaps are required?)

**Idea:** Replace root with last element, then fix.

- Call start with
`current`←`root` - While
`current`has a`child > current`- Swap
`current`with larger`child` - Repeat with
`current`←`child`

- Swap

```
def fixDown[A: Ordering](current: Vertex[A]): Unit =
{
if(current.leftChild.isDefined){
val left = current.leftChild.get
if( Ordering[A].lt( current.value, left.value ) ){
if(current.rightChild.isDefined){
val right = current.rightChild.get
if( Ordering[A].lt( right.value, left.value ) ){
swap(current.value, left.value); fixDown(left)
} else {
swap(current.value, right.value); fixDown(right)
}
} else {
swap(current.value, left.value); fixDown(left)
}
} else if(current.rightChild.isDefined) {
val right = current.rightChild.get
if( Ordering[A].lt( current.value, right.value ) ){
swap(current.value, right.value); fixDown(right)
}
}
}
}
```

What's the complexity?

Operation | Lazy | Proactive | Heap |
---|---|---|---|

enqueue | $O(1)$ | $O(n)$ | $O(\log(n))$ |

dequeue | $O(n)$ | $O(1)$ | $O(\log(n))$ |

head | $O(n)$ | $O(1)$ | $O(1)$ |

- Each layer has a maximum size
- Each layer grows left-to-right
- Only the last layer grows

**Idea: ** Use an `ArrayBuffer` to store the heap.

- 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))$