Solution

ID: past-exam-of-the-year-1-of-the-computer-science-course-of-the-university-of-oxford/2016/functional-programming-design-and-analysis-of-algorithms/7/b/solution

Save the root, move the last active entry to the root, shrink the active prefix, then restore heap order by heapify down:
ExtractMin(A):
    if A.heap_size == 0:
        error "empty heap"
    answer = A[0]
    A[0] = A[A.heap_size-1]
    A.heap_size = A.heap_size-1
    if A.heap_size > 0:
        SiftDown(A,0)
    return answer

SiftDown(A,i):
    while true:
        best = i
        for j = 0,...,d-1:
            c = Child(i,j)
            if c >= A.heap_size:
                break
            if A[c] < A[best]:
                best = c
        if best == i:
            return
        swap A[i], A[best]
        i = best
Only edges descending from the moved entry can violate heap order. If the entry is no larger than every child, the entire heap is valid. Otherwise swapping with a minimum child restores the order at the old position: that child is no larger than the old entry or any sibling. Its original subtree was already ordered, so only the moved entry's new outgoing edges can remain invalid. This is the loop invariant of SiftDown. Each swap moves it down one level, proving termination and correctness.
For , the height is , and selecting a minimum child costs at most comparisons per level. Thus ExtractMin takes time, with auxiliary storage. Empty and singleton heaps take constant time. For , there is at most one nonroot level and the loop examines at most actual children, taking ; this also lies within for . The coarser uniform expression safely includes all small sizes.

New to topics? Read the docs here!