Imports
6.5. Priority Queues
This file gives a functional priority-queue interface on top of the Chapter 6 descending-list heap model, then refines the main CLRS array operations for maximum, increase-key, extract-max, and delete. The functional interface remains a compact scaffold; the reader-facing array theorems track parent/child indices, key mutation, bubbling, heap-prefix size, length, and permutation.
Main results:
-
Theorem
heapInsert_orderedDesc: inserting into a heap preserves the heap invariant. -
Theorem
heapInsert_perm: insertion adds exactly the inserted key. -
Theorem
heapIncreaseKey_orderedDesc: increasing one occurrence and rebuilding produces a heap. -
Theorem
heapDelete_orderedDesc: deleting one occurrence and rebuilding produces a heap. -
Theorem
arrayHeapMaximum?_max: the root returned by the array-level maximum operation bounds every key in the heap prefix. -
Theorems
ArrayMaxHeap.set_increased_except_upandArrayMaxHeapExceptUp.bubble_step: the upward-bubbling proof spine for array-levelHEAP-INCREASE-KEY. -
Theorem
arrayHeapIncreaseKey?_state_correct: the full fuelled array-levelHEAP-INCREASE-KEYwrapper writes a larger key, repeatedly bubbles it toward the root, and returns a max-heap with the same backing-list length and swapped multiset. -
Theorem
arrayHeapInsert?_state_correct: checked insertion accepts any active prefix fitting in the backing list, inserts before the inactive tail, grows both heap size and list length by one, and adds exactly the requested key. -
Theorem
arrayHeapInsertWithCost?_state_correct_and_log_cost: the same state contract paired with an honest upward-bubbling control-frame count bounded by⌊log₂(heapSize + 1)⌋ + 1. -
Theorem
arrayHeapIncreaseKeyNoBubble?_state_correct: the no-bubble branch of CLRSHEAP-INCREASE-KEYremains as a small readable corollary for the immediate-stop case. -
Theorem
arrayHeapExtractMax?_state_correct: the CLRS array-level extract-max step swaps the root with the last heap cell, shrinks the heap prefix, repairs the new root, and returns a state whose prefix is again a max-heap while the extracted key is the old maximum. -
Theorem
arrayHeapDelete?_state_correct: index-based CLRSHEAP-DELETE, implemented by raising the target cell to the current root maximum and then extracting the maximum, returns a shrunk heap prefix and records the deleted key.
Implementation detail:
-
Array-level MAX-HEAP-INSERT contains focused basic, checked, and costed modules. They isolate the new cell's incoming-edge violation and reuse the established upward-bubbling invariant.
Current gap:
-
The proved insertion cost counts visited bubbling frames; guard evaluation, persistent-list operations, allocation, and imperative RAM semantics remain outside this metric.
namespace CLRSnamespace Chapter06Functional priority-queue operations
Insert a key into the functional max-priority queue.
def heapInsert (x : Nat) (h : List Nat) : List Nat :=
insertDesc x h
Increase one occurrence of old to new, then rebuild the abstract heap.
If old is absent this inserts new; this total behavior avoids exceptions in
the mathematical interface.
def heapIncreaseKey (old new : Nat) (h : List Nat) : List Nat :=
buildMaxHeap (new :: h.erase old)
Delete one occurrence of key, then rebuild the abstract heap.
def heapDelete (key : Nat) (h : List Nat) : List Nat :=
buildMaxHeap (h.erase key)Correctness theorems
Priority-queue insertion preserves the heap invariant.
theorem heapInsert_orderedDesc {x : Nat} {h : List Nat}
(hh : OrderedDesc h) : OrderedDesc (heapInsert x h) := by
exact insertDesc_orderedDesc hhPriority-queue insertion adds exactly the inserted key.
theorem heapInsert_perm (x : Nat) (h : List Nat) :
(heapInsert x h).Perm (x :: h) := by
exact insertDesc_perm x hThe maximum after insertion is maximal among the old keys and the new key.
theorem heapInsert_max {x m : Nat} {h : List Nat}
(hh : OrderedDesc h) (hmax : heapMaximum? (heapInsert x h) = some m) :
∀ y ∈ x :: h, y ≤ m := by
intro y hy
have hyheap : y ∈ heapInsert x h :=
(List.Perm.mem_iff (heapInsert_perm x h)).2 hy
exact heapMaximum?_max (heapInsert_orderedDesc hh) hmax y hyheapIncreasing a key and rebuilding returns a heap.
theorem heapIncreaseKey_orderedDesc (old new : Nat) (h : List Nat) :
OrderedDesc (heapIncreaseKey old new h) := by
exact buildMaxHeap_orderedDesc (new :: h.erase old)Increasing a key preserves exactly the rebuilt multiset specification.
theorem heapIncreaseKey_perm (old new : Nat) (h : List Nat) :
(heapIncreaseKey old new h).Perm (new :: h.erase old) := by
exact buildMaxHeap_perm (new :: h.erase old)Deleting one key occurrence and rebuilding returns a heap.
theorem heapDelete_orderedDesc (key : Nat) (h : List Nat) :
OrderedDesc (heapDelete key h) := by
exact buildMaxHeap_orderedDesc (h.erase key)Deleting one key occurrence preserves exactly the rebuilt multiset specification.
theorem heapDelete_perm (key : Nat) (h : List Nat) :
(heapDelete key h).Perm (h.erase key) := by
exact buildMaxHeap_perm (h.erase key)Array-level maximum operation
Array-level HEAP-MAXIMUM: return the root when the heap prefix is nonempty
and within the backing list.
def arrayHeapMaximum? (a : List Nat) (heapSize : Nat) : Option Nat :=
if h : 0 < heapSize ∧ heapSize ≤ a.length then
some (a[0]'(Nat.lt_of_lt_of_le h.1 h.2))
else
noneThe array-level maximum returned from the root bounds every heap element.
theorem arrayHeapMaximum?_max {a : List Nat} {heapSize m : Nat}
(hheap : ArrayMaxHeap a heapSize)
(hmax : arrayHeapMaximum? a heapSize = some m) :
∀ {i : Nat}, (hi : i < heapSize) →
a[i]'(Nat.lt_of_lt_of_le hi hheap.heapSize_le_length) ≤ m := by
intro i hi
have hnonempty : 0 < heapSize := Nat.zero_lt_of_lt hi
have hcond : 0 < heapSize ∧ heapSize ≤ a.length :=
⟨hnonempty, hheap.heapSize_le_length⟩
have hroot :
a[0]'(Nat.lt_of_lt_of_le hnonempty hheap.heapSize_le_length) = m := by
simpa [arrayHeapMaximum?, hcond] using hmax
rw [← hroot]
exact hheap.getElem_le_root hiArray-level no-bubble increase-key branch
Reading the cell just written by List.set.
theorem valAt_set_self {a : List Nat} {i x : Nat} (hi : i < a.length) :
valAt (a.set i x) i = x := by
simp [valAt, List.getElem?_set_self hi]
Reading any other cell after List.set.
theorem valAt_set_of_ne {a : List Nat} {i k x : Nat} (hki : k ≠ i) :
valAt (a.set i x) k = valAt a k := by
simp [valAt, show i ≠ k from Ne.symm hki]
For HEAP-INCREASE-KEY, the possible violation moves upward. Unlike
MAX-HEAPIFY, where the possibly bad obligations are the outgoing child
edges of a parent, here the possibly bad obligation is the incoming edge to the
key currently bubbling up.
All heap edges are valid except possibly the edge whose child is
badChild. The extra field says that the bad child's own children are
already bounded by its parent; this is exactly the fact needed after swapping
the bad child with its parent.
structure ArrayMaxHeapExceptUp (a : List Nat) (heapSize badChild : Nat) : Prop where
heapSize_le_length : heapSize ≤ a.length
left_le : ∀ {j : Nat}, j < heapSize → left j < heapSize → left j ≠ badChild →
valAt a (left j) ≤ valAt a j
right_le : ∀ {j : Nat}, j < heapSize → right j < heapSize → right j ≠ badChild →
valAt a (right j) ≤ valAt a j
bad_children_le_parent : 0 < badChild → badChild < heapSize →
(∀ _ : left badChild < heapSize,
valAt a (left badChild) ≤ valAt a (parent badChild)) ∧
(∀ _ : right badChild < heapSize,
valAt a (right badChild) ≤ valAt a (parent badChild))In an upward-exception heap, every non-exempt child is bounded by its parent.
theorem ArrayMaxHeapExceptUp.valAt_le_parent_of_ne {a : List Nat}
{heapSize badChild i : Nat}
(h : ArrayMaxHeapExceptUp a heapSize badChild)
(hi : i < heapSize) (hpos : 0 < i) (hne : i ≠ badChild) :
valAt a i ≤ valAt a (parent i) := by
let p := parent i
have hpheap : p < heapSize := Nat.lt_trans (parent_lt_self hpos) hi
rcases eq_left_or_right_parent hpos with hleft | hright
· have hchild : left p < heapSize := by simpa [p, hleft.symm] using hi
have hchild_ne : left p ≠ badChild := by
simpa [p, hleft.symm] using hne
have hle := h.left_le hpheap hchild hchild_ne
simpa [p, hleft.symm] using hle
· have hchild : right p < heapSize := by simpa [p, hright.symm] using hi
have hchild_ne : right p ≠ badChild := by
simpa [p, hright.symm] using hne
have hle := h.right_le hpheap hchild hchild_ne
simpa [p, hright.symm] using hleIf the upward exception is absent or already bounded by its parent, the heap is global.
theorem ArrayMaxHeapExceptUp.to_global {a : List Nat} {heapSize badChild : Nat}
(h : ArrayMaxHeapExceptUp a heapSize badChild)
(hbad : badChild = 0 ∨ valAt a badChild ≤ valAt a (parent badChild)) :
ArrayMaxHeap a heapSize := by
refine ⟨h.heapSize_le_length, ?_, ?_⟩
· intro j hj hl
have hchild_len : left j < a.length := Nat.lt_of_lt_of_le hl h.heapSize_le_length
have hparent_len : j < a.length := Nat.lt_of_lt_of_le hj h.heapSize_le_length
have hval : valAt a (left j) ≤ valAt a j := by
by_cases hchild : left j = badChild
· rcases hbad with hroot | hle
· have hzero : left j = 0 := by simpa [hroot] using hchild
unfold left at hzero
omega
· have hp : parent badChild = j := by
rw [← hchild, parent_left]
simpa [hchild, hp] using hle
· exact h.left_le hj hl hchild
rw [valAt_eq_getElem a hchild_len, valAt_eq_getElem a hparent_len] at hval
exact hval
· intro j hj hr
have hchild_len : right j < a.length := Nat.lt_of_lt_of_le hr h.heapSize_le_length
have hparent_len : j < a.length := Nat.lt_of_lt_of_le hj h.heapSize_le_length
have hval : valAt a (right j) ≤ valAt a j := by
by_cases hchild : right j = badChild
· rcases hbad with hroot | hle
· have hzero : right j = 0 := by simpa [hroot] using hchild
unfold right at hzero
omega
· have hp : parent badChild = j := by
rw [← hchild, parent_right]
simpa [hchild, hp] using hle
· exact h.right_le hj hr hchild
rw [valAt_eq_getElem a hchild_len, valAt_eq_getElem a hparent_len] at hval
exact hvalIn a global heap, any positive node is bounded by its parent.
theorem ArrayMaxHeap.valAt_le_parent {a : List Nat} {heapSize i : Nat}
(hheap : ArrayMaxHeap a heapSize) (hi : i < heapSize) (hpos : 0 < i) :
valAt a i ≤ valAt a (parent i) := by
let p := parent i
have hpheap : p < heapSize := Nat.lt_trans (parent_lt_self hpos) hi
have hp_len : p < a.length := Nat.lt_of_lt_of_le hpheap hheap.heapSize_le_length
rcases eq_left_or_right_parent hpos with hleft | hright
· have hchild : left p < heapSize := by simpa [p, hleft.symm] using hi
have hchild_len : left p < a.length :=
Nat.lt_of_lt_of_le hchild hheap.heapSize_le_length
have hle := hheap.left_le hpheap hchild
have hleVal : valAt a (left p) ≤ valAt a p := by
rw [valAt_eq_getElem a hchild_len, valAt_eq_getElem a hp_len]
exact hle
simpa [p, hleft.symm] using hleVal
· have hchild : right p < heapSize := by simpa [p, hright.symm] using hi
have hchild_len : right p < a.length :=
Nat.lt_of_lt_of_le hchild hheap.heapSize_le_length
have hle := hheap.right_le hpheap hchild
have hleVal : valAt a (right p) ≤ valAt a p := by
rw [valAt_eq_getElem a hchild_len, valAt_eq_getElem a hp_len]
exact hle
simpa [p, hright.symm] using hleVal
After increasing a key at i, all heap edges are still valid except
possibly the incoming edge to i. This is the invariant entry point for
the CLRS upward bubbling loop.
theorem ArrayMaxHeap.set_increased_except_up {a : List Nat} {heapSize i key : Nat}
(hheap : ArrayMaxHeap a heapSize) (hi : i < heapSize)
(hraise : valAt a i ≤ key) :
ArrayMaxHeapExceptUp (a.set i key) heapSize i := by
have hi_len : i < a.length := Nat.lt_of_lt_of_le hi hheap.heapSize_le_length
have hlen : heapSize ≤ (a.set i key).length := by
simpa [List.length_set] using hheap.heapSize_le_length
have leftVal : ∀ {j : Nat}, j < heapSize → left j < heapSize →
valAt a (left j) ≤ valAt a j := by
intro j hj hl
have hchild_len : left j < a.length :=
Nat.lt_of_lt_of_le hl hheap.heapSize_le_length
have hparent_len : j < a.length :=
Nat.lt_of_lt_of_le hj hheap.heapSize_le_length
have hold := hheap.left_le hj hl
rw [← valAt_eq_getElem a hchild_len,
← valAt_eq_getElem a hparent_len] at hold
exact hold
have rightVal : ∀ {j : Nat}, j < heapSize → right j < heapSize →
valAt a (right j) ≤ valAt a j := by
intro j hj hr
have hchild_len : right j < a.length :=
Nat.lt_of_lt_of_le hr hheap.heapSize_le_length
have hparent_len : j < a.length :=
Nat.lt_of_lt_of_le hj hheap.heapSize_le_length
have hold := hheap.right_le hj hr
rw [← valAt_eq_getElem a hchild_len,
← valAt_eq_getElem a hparent_len] at hold
exact hold
refine ⟨hlen, ?_, ?_, ?_⟩
· intro j hj hl hchild_ne
by_cases hparent : j = i
· subst j
rw [valAt_set_of_ne (a := a) (i := i) (k := left i) (x := key)
(left_ne_self i),
valAt_set_self hi_len]
exact Nat.le_trans (leftVal hi hl) hraise
· rw [valAt_set_of_ne (a := a) (i := i) (k := left j) (x := key) hchild_ne,
valAt_set_of_ne (a := a) (i := i) (k := j) (x := key) hparent]
exact leftVal hj hl
· intro j hj hr hchild_ne
by_cases hparent : j = i
· subst j
rw [valAt_set_of_ne (a := a) (i := i) (k := right i) (x := key)
(right_ne_self i),
valAt_set_self hi_len]
exact Nat.le_trans (rightVal hi hr) hraise
· rw [valAt_set_of_ne (a := a) (i := i) (k := right j) (x := key) hchild_ne,
valAt_set_of_ne (a := a) (i := i) (k := j) (x := key) hparent]
exact rightVal hj hr
· intro hpos _
have hle_parent := hheap.valAt_le_parent hi hpos
have hp_ne : parent i ≠ i := ne_of_lt (parent_lt_self hpos)
constructor
· intro hl
rw [valAt_set_of_ne (a := a) (i := i) (k := left i) (x := key)
(left_ne_self i),
valAt_set_of_ne (a := a) (i := i) (k := parent i) (x := key) hp_ne]
exact Nat.le_trans (leftVal hi hl) hle_parent
· intro hr
rw [valAt_set_of_ne (a := a) (i := i) (k := right i) (x := key)
(right_ne_self i),
valAt_set_of_ne (a := a) (i := i) (k := parent i) (x := key) hp_ne]
exact Nat.le_trans (rightVal hi hr) hle_parent
One CLRS upward bubbling swap moves the only possible bad incoming edge from
i to parent i.
theorem ArrayMaxHeapExceptUp.bubble_step {a : List Nat} {heapSize i : Nat}
(h : ArrayMaxHeapExceptUp a heapSize i) (hi : i < heapSize) (hpos : 0 < i)
(hswap : valAt a (parent i) < valAt a i) :
ArrayMaxHeapExceptUp (swapAt a i (parent i)) heapSize (parent i) := by
let p := parent i
have hpheap : p < heapSize := Nat.lt_trans (parent_lt_self hpos) hi
have hpi : p < i := parent_lt_self hpos
have hi_len : i < a.length := Nat.lt_of_lt_of_le hi h.heapSize_le_length
have hp_len : p < a.length := Nat.lt_of_lt_of_le hpheap h.heapSize_le_length
have hlen : heapSize ≤ (swapAt a i p).length := by
simpa [p, swapAt_length] using h.heapSize_le_length
have hp_ne_i : p ≠ i := ne_of_lt hpi
have hchildren := h.bad_children_le_parent hpos hi
have hp_old_le_parent : ∀ (hp_pos : 0 < p), valAt a p ≤ valAt a (parent p) := by
intro hp_pos
exact h.valAt_le_parent_of_ne hpheap hp_pos hp_ne_i
refine ⟨hlen, ?_, ?_, ?_⟩
· intro j hj hl hchild_ne_p
by_cases hchild_i : left j = i
· have hjp : j = p := by
calc
j = parent (left j) := (parent_left j).symm
_ = parent i := by rw [hchild_i]
_ = p := rfl
rw [hchild_i, hjp]
rw [valAt_swapAt_left hi_len hp_len, valAt_swapAt_right hi_len hp_len]
exact Nat.le_of_lt hswap
· by_cases hj_i : j = i
· subst j
have hleft_ne_p : left i ≠ p := by
unfold left p parent
omega
rw [valAt_swapAt_of_ne hi_len hp_len (left_ne_self i) hleft_ne_p,
valAt_swapAt_left hi_len hp_len]
exact hchildren.1 hl
· by_cases hj_p : j = p
· subst j
rw [valAt_swapAt_of_ne hi_len hp_len hchild_i hchild_ne_p,
valAt_swapAt_right hi_len hp_len]
have hold := h.left_le hpheap hl hchild_i
exact Nat.le_trans hold (Nat.le_of_lt hswap)
· rw [valAt_swapAt_of_ne hi_len hp_len hchild_i hchild_ne_p,
valAt_swapAt_of_ne hi_len hp_len hj_i hj_p]
exact h.left_le hj hl hchild_i
· intro j hj hr hchild_ne_p
by_cases hchild_i : right j = i
· have hjp : j = p := by
calc
j = parent (right j) := (parent_right j).symm
_ = parent i := by rw [hchild_i]
_ = p := rfl
rw [hchild_i, hjp]
rw [valAt_swapAt_left hi_len hp_len, valAt_swapAt_right hi_len hp_len]
exact Nat.le_of_lt hswap
· by_cases hj_i : j = i
· subst j
have hright_ne_p : right i ≠ p := by
unfold right p parent
omega
rw [valAt_swapAt_of_ne hi_len hp_len (right_ne_self i) hright_ne_p,
valAt_swapAt_left hi_len hp_len]
exact hchildren.2 hr
· by_cases hj_p : j = p
· subst j
rw [valAt_swapAt_of_ne hi_len hp_len hchild_i hchild_ne_p,
valAt_swapAt_right hi_len hp_len]
have hold := h.right_le hpheap hr hchild_i
exact Nat.le_trans hold (Nat.le_of_lt hswap)
· rw [valAt_swapAt_of_ne hi_len hp_len hchild_i hchild_ne_p,
valAt_swapAt_of_ne hi_len hp_len hj_i hj_p]
exact h.right_le hj hr hchild_i
· intro hp_pos _
have hparentp_ne_i : parent p ≠ i := by
have hlt : parent p < i := Nat.lt_trans (parent_lt_self hp_pos) hpi
exact ne_of_lt hlt
have hparentp_ne_p : parent p ≠ p := ne_of_lt (parent_lt_self hp_pos)
have hp_le_gp := hp_old_le_parent hp_pos
constructor
· intro hl
by_cases hchild_i : left p = i
· rw [hchild_i, valAt_swapAt_left hi_len hp_len]
rw [valAt_swapAt_of_ne hi_len hp_len hparentp_ne_i hparentp_ne_p]
exact hp_le_gp
· have hleft_ne_p : left p ≠ p := left_ne_self p
rw [valAt_swapAt_of_ne hi_len hp_len hchild_i hleft_ne_p,
valAt_swapAt_of_ne hi_len hp_len hparentp_ne_i hparentp_ne_p]
have hold := h.left_le hpheap hl hchild_i
exact Nat.le_trans hold hp_le_gp
· intro hr
by_cases hchild_i : right p = i
· rw [hchild_i, valAt_swapAt_left hi_len hp_len]
rw [valAt_swapAt_of_ne hi_len hp_len hparentp_ne_i hparentp_ne_p]
exact hp_le_gp
· have hright_ne_p : right p ≠ p := right_ne_self p
rw [valAt_swapAt_of_ne hi_len hp_len hchild_i hright_ne_p,
valAt_swapAt_of_ne hi_len hp_len hparentp_ne_i hparentp_ne_p]
have hold := h.right_le hpheap hr hchild_i
exact Nat.le_trans hold hp_le_gp
Fuelled upward bubbling loop for array-level HEAP-INCREASE-KEY. The
fuel is bounded by the starting index, since each swap moves to the strict
parent.
def arrayHeapIncreaseKeyBubbleUpFuel : Nat → List Nat → Nat → Nat → List Nat
| 0, a, _heapSize, _i => a
| fuel + 1, a, heapSize, i =>
if _ : 0 < i then
if valAt a (parent i) < valAt a i then
arrayHeapIncreaseKeyBubbleUpFuel fuel (swapAt a i (parent i)) heapSize (parent i)
else
a
else
aThe upward bubbling loop preserves the backing-list length.
theorem arrayHeapIncreaseKeyBubbleUpFuel_length (fuel : Nat) (a : List Nat)
(heapSize i : Nat) :
(arrayHeapIncreaseKeyBubbleUpFuel fuel a heapSize i).length = a.length := by
induction fuel generalizing a i with
| zero =>
simp [arrayHeapIncreaseKeyBubbleUpFuel]
| succ fuel ih =>
by_cases hpos : 0 < i
· by_cases hswap : valAt a (parent i) < valAt a i
· simp [arrayHeapIncreaseKeyBubbleUpFuel, hpos, hswap,
ih (a := swapAt a i (parent i)) (i := parent i), swapAt_length]
· simp [arrayHeapIncreaseKeyBubbleUpFuel, hpos, hswap]
· simp [arrayHeapIncreaseKeyBubbleUpFuel, hpos]The upward bubbling loop only swaps cells, so it preserves the multiset.
theorem arrayHeapIncreaseKeyBubbleUpFuel_perm (fuel : Nat) (a : List Nat)
(heapSize i : Nat) :
(arrayHeapIncreaseKeyBubbleUpFuel fuel a heapSize i).Perm a := by
induction fuel generalizing a i with
| zero =>
simp [arrayHeapIncreaseKeyBubbleUpFuel]
| succ fuel ih =>
by_cases hpos : 0 < i
· by_cases hswap : valAt a (parent i) < valAt a i
· have hrec := ih (a := swapAt a i (parent i)) (i := parent i)
exact (by
simpa [arrayHeapIncreaseKeyBubbleUpFuel, hpos, hswap] using
hrec.trans (swapAt_perm a i (parent i)))
· simp [arrayHeapIncreaseKeyBubbleUpFuel, hpos, hswap]
· simp [arrayHeapIncreaseKeyBubbleUpFuel, hpos]Enough upward-bubbling fuel discharges the upward exception and restores a global heap.
theorem ArrayMaxHeapExceptUp.bubbleUpFuel_global {fuel : Nat} {a : List Nat}
{heapSize i : Nat} (h : ArrayMaxHeapExceptUp a heapSize i)
(hi : i < heapSize) (hfuel : i ≤ fuel) :
ArrayMaxHeap (arrayHeapIncreaseKeyBubbleUpFuel fuel a heapSize i) heapSize := by
induction fuel generalizing a heapSize i with
| zero =>
have hzero : i = 0 := by omega
have hglobal : ArrayMaxHeap a heapSize := h.to_global (Or.inl hzero)
simpa [arrayHeapIncreaseKeyBubbleUpFuel] using hglobal
| succ fuel ih =>
by_cases hpos : 0 < i
· by_cases hswap : valAt a (parent i) < valAt a i
· have hpheap : parent i < heapSize := Nat.lt_trans (parent_lt_self hpos) hi
have hp_le_fuel : parent i ≤ fuel := by
have hpi : parent i < i := parent_lt_self hpos
omega
have hnext : ArrayMaxHeapExceptUp (swapAt a i (parent i)) heapSize (parent i) :=
h.bubble_step hi hpos hswap
have hrec := ih (a := swapAt a i (parent i)) (heapSize := heapSize)
(i := parent i) hnext hpheap hp_le_fuel
simpa [arrayHeapIncreaseKeyBubbleUpFuel, hpos, hswap] using hrec
· have hle : valAt a i ≤ valAt a (parent i) := Nat.le_of_not_lt hswap
have hglobal : ArrayMaxHeap a heapSize := h.to_global (Or.inr hle)
simpa [arrayHeapIncreaseKeyBubbleUpFuel, hpos, hswap] using hglobal
· have hzero : i = 0 := Nat.eq_zero_of_not_pos hpos
have hglobal : ArrayMaxHeap a heapSize := h.to_global (Or.inl hzero)
simpa [arrayHeapIncreaseKeyBubbleUpFuel, hpos] using hglobal
Array-level CLRS HEAP-INCREASE-KEY: write the key and bubble it upward.
def arrayHeapIncreaseKey? (a : List Nat) (heapSize i key : Nat) : Option (List Nat) :=
if _h : i < heapSize ∧ heapSize ≤ a.length ∧ valAt a i ≤ key then
some (arrayHeapIncreaseKeyBubbleUpFuel i (a.set i key) heapSize i)
else
none
State-correctness theorem for array-level HEAP-INCREASE-KEY.
theorem arrayHeapIncreaseKey?_state_correct {a rest : List Nat} {heapSize i key : Nat}
(hheap : ArrayMaxHeap a heapSize)
(hres : arrayHeapIncreaseKey? a heapSize i key = some rest) :
i < heapSize ∧
heapSize ≤ a.length ∧
valAt a i ≤ key ∧
ArrayMaxHeap rest heapSize ∧
rest.length = a.length ∧
rest.Perm (a.set i key) := by
unfold arrayHeapIncreaseKey? at hres
by_cases hcond : i < heapSize ∧ heapSize ≤ a.length ∧ valAt a i ≤ key
· simp [hcond] at hres
subst rest
have hentry : ArrayMaxHeapExceptUp (a.set i key) heapSize i :=
hheap.set_increased_except_up hcond.1 hcond.2.2
have hrest_heap :
ArrayMaxHeap (arrayHeapIncreaseKeyBubbleUpFuel i (a.set i key) heapSize i)
heapSize :=
hentry.bubbleUpFuel_global hcond.1 (Nat.le_refl i)
have hlen := arrayHeapIncreaseKeyBubbleUpFuel_length i (a.set i key) heapSize i
have hperm := arrayHeapIncreaseKeyBubbleUpFuel_perm i (a.set i key) heapSize i
refine ⟨hcond.1, hcond.2.1, hcond.2.2, hrest_heap, ?_, hperm⟩
simpa [List.length_set] using hlen
· simp [hcond] at hres
Core no-bubble lemma for CLRS HEAP-INCREASE-KEY. If increasing a heap
cell leaves it below its parent, the array is already a max-heap after the
write, so the upward while-loop would stop immediately.
theorem ArrayMaxHeap.set_increased_no_bubble {a : List Nat} {heapSize i key : Nat}
(hheap : ArrayMaxHeap a heapSize) (hi : i < heapSize)
(hraise : valAt a i ≤ key)
(hnobubble : i = 0 ∨ key ≤ valAt a (parent i)) :
ArrayMaxHeap (a.set i key) heapSize := by
have hi_len : i < a.length := Nat.lt_of_lt_of_le hi hheap.heapSize_le_length
have hlen : heapSize ≤ (a.set i key).length := by
simpa [List.length_set] using hheap.heapSize_le_length
have leftVal : ∀ {j : Nat}, j < heapSize → left j < heapSize →
valAt a (left j) ≤ valAt a j := by
intro j hj hl
have hchild_len : left j < a.length :=
Nat.lt_of_lt_of_le hl hheap.heapSize_le_length
have hparent_len : j < a.length :=
Nat.lt_of_lt_of_le hj hheap.heapSize_le_length
have hold := hheap.left_le hj hl
rw [← valAt_eq_getElem a hchild_len,
← valAt_eq_getElem a hparent_len] at hold
exact hold
have rightVal : ∀ {j : Nat}, j < heapSize → right j < heapSize →
valAt a (right j) ≤ valAt a j := by
intro j hj hr
have hchild_len : right j < a.length :=
Nat.lt_of_lt_of_le hr hheap.heapSize_le_length
have hparent_len : j < a.length :=
Nat.lt_of_lt_of_le hj hheap.heapSize_le_length
have hold := hheap.right_le hj hr
rw [← valAt_eq_getElem a hchild_len,
← valAt_eq_getElem a hparent_len] at hold
exact hold
refine ⟨hlen, ?_, ?_⟩
· intro j hj hl
have hchild_len : left j < (a.set i key).length := Nat.lt_of_lt_of_le hl hlen
have hparent_len : j < (a.set i key).length := Nat.lt_of_lt_of_le hj hlen
have hval : valAt (a.set i key) (left j) ≤ valAt (a.set i key) j := by
by_cases hchild : left j = i
· rw [hchild, valAt_set_self hi_len]
have hparent_ne : j ≠ i := by
rw [← hchild]
unfold left
omega
rw [valAt_set_of_ne (a := a) (i := i) (k := j) (x := key) hparent_ne]
rcases hnobubble with hroot | hle_parent
· have hleft_zero : left j = 0 := by simpa [hroot] using hchild
unfold left at hleft_zero
omega
· have hp : parent i = j := by
rw [← hchild, parent_left]
simpa [hp] using hle_parent
· by_cases hparent : j = i
· subst j
rw [valAt_set_of_ne (a := a) (i := i) (k := left i) (x := key)
(left_ne_self i),
valAt_set_self hi_len]
exact Nat.le_trans (leftVal hi hl) hraise
· rw [valAt_set_of_ne (a := a) (i := i) (k := left j) (x := key) hchild,
valAt_set_of_ne (a := a) (i := i) (k := j) (x := key) hparent]
exact leftVal hj hl
rw [valAt_eq_getElem (a.set i key) hchild_len,
valAt_eq_getElem (a.set i key) hparent_len] at hval
exact hval
· intro j hj hr
have hchild_len : right j < (a.set i key).length := Nat.lt_of_lt_of_le hr hlen
have hparent_len : j < (a.set i key).length := Nat.lt_of_lt_of_le hj hlen
have hval : valAt (a.set i key) (right j) ≤ valAt (a.set i key) j := by
by_cases hchild : right j = i
· rw [hchild, valAt_set_self hi_len]
have hparent_ne : j ≠ i := by
rw [← hchild]
unfold right
omega
rw [valAt_set_of_ne (a := a) (i := i) (k := j) (x := key) hparent_ne]
rcases hnobubble with hroot | hle_parent
· have hright_zero : right j = 0 := by simpa [hroot] using hchild
unfold right at hright_zero
omega
· have hp : parent i = j := by
rw [← hchild, parent_right]
simpa [hp] using hle_parent
· by_cases hparent : j = i
· subst j
rw [valAt_set_of_ne (a := a) (i := i) (k := right i) (x := key)
(right_ne_self i),
valAt_set_self hi_len]
exact Nat.le_trans (rightVal hi hr) hraise
· rw [valAt_set_of_ne (a := a) (i := i) (k := right j) (x := key) hchild,
valAt_set_of_ne (a := a) (i := i) (k := j) (x := key) hparent]
exact rightVal hj hr
rw [valAt_eq_getElem (a.set i key) hchild_len,
valAt_eq_getElem (a.set i key) hparent_len] at hval
exact hval
Array-level no-bubble branch of CLRS HEAP-INCREASE-KEY: write the new
key at index i when the key is larger than the old key but still no
larger than its parent, so the upward repair loop stops immediately.
def arrayHeapIncreaseKeyNoBubble? (a : List Nat) (heapSize i key : Nat) :
Option (List Nat) :=
if _h : i < heapSize ∧ heapSize ≤ a.length ∧ valAt a i ≤ key ∧
(i = 0 ∨ key ≤ valAt a (parent i)) then
some (a.set i key)
else
noneState-correctness theorem for the no-bubble branch of array-level increase-key.
theorem arrayHeapIncreaseKeyNoBubble?_state_correct {a rest : List Nat}
{heapSize i key : Nat}
(hheap : ArrayMaxHeap a heapSize)
(hres : arrayHeapIncreaseKeyNoBubble? a heapSize i key = some rest) :
i < heapSize ∧
heapSize ≤ a.length ∧
valAt a i ≤ key ∧
(i = 0 ∨ key ≤ valAt a (parent i)) ∧
ArrayMaxHeap rest heapSize ∧
rest.length = a.length ∧
valAt rest i = key ∧
(∀ {k : Nat}, k ≠ i → valAt rest k = valAt a k) := by
unfold arrayHeapIncreaseKeyNoBubble? at hres
by_cases hcond :
i < heapSize ∧ heapSize ≤ a.length ∧ valAt a i ≤ key ∧
(i = 0 ∨ key ≤ valAt a (parent i))
· simp [hcond] at hres
subst rest
have hi_len : i < a.length := Nat.lt_of_lt_of_le hcond.1 hcond.2.1
refine ⟨hcond.1, hcond.2.1, hcond.2.2.1, hcond.2.2.2, ?_, ?_, ?_, ?_⟩
· exact hheap.set_increased_no_bubble hcond.1 hcond.2.2.1 hcond.2.2.2
· simp [List.length_set]
· exact valAt_set_self hi_len
· intro k hk
exact valAt_set_of_ne (a := a) (i := i) (k := k) (x := key) hk
· simp [hcond] at hresArray-level extract-max
Array-level HEAP-EXTRACT-MAX. The returned triple is the extracted
maximum, the backing array after the CLRS root/last swap and root heapify, and
the new heap prefix size.
def arrayHeapExtractMax? (a : List Nat) (heapSize : Nat) :
Option (Nat × List Nat × Nat) :=
if h : 0 < heapSize ∧ heapSize ≤ a.length then
let newHeapSize := heapSize - 1
let maximum := a[0]'(Nat.lt_of_lt_of_le h.1 h.2)
let moved := swapAt a 0 newHeapSize
let repaired := maxHeapifyFuel newHeapSize moved newHeapSize 0
some (maximum, repaired, newHeapSize)
else
none
State-correctness theorem for the array-level CLRS HEAP-EXTRACT-MAX
step. The array keeps the same length and multiset, the heap prefix shrinks by
one and is repaired into a max-heap, the returned key bounds the old heap prefix,
and that key is stored at the first cell outside the new heap prefix.
theorem arrayHeapExtractMax?_state_correct {a : List Nat} {heapSize m : Nat}
{rest : List Nat} {newHeapSize : Nat}
(hheap : ArrayMaxHeap a heapSize)
(hres : arrayHeapExtractMax? a heapSize = some (m, rest, newHeapSize)) :
0 < heapSize ∧
newHeapSize + 1 = heapSize ∧
ArrayMaxHeap rest newHeapSize ∧
rest.length = a.length ∧
rest.Perm a ∧
(∀ {i : Nat}, i < heapSize → valAt a i ≤ m) ∧
newHeapSize < rest.length ∧
valAt rest newHeapSize = m := by
unfold arrayHeapExtractMax? at hres
by_cases hcond : 0 < heapSize ∧ heapSize ≤ a.length
· simp [hcond] at hres
rcases hres with ⟨hm, hrest_eq, hnew_eq⟩
subst m
subst rest
subst newHeapSize
set newSize := heapSize - 1
have hsize_eq : newSize + 1 = heapSize := by
dsimp [newSize]
omega
have hlen_swapped : newSize ≤ (swapAt a 0 newSize).length := by
rw [swapAt_length]
dsimp [newSize]
omega
have hrest_len :
(maxHeapifyFuel newSize (swapAt a 0 newSize) newSize 0).length =
a.length := by
rw [maxHeapifyFuel_length, swapAt_length]
have hrest_perm :
(maxHeapifyFuel newSize (swapAt a 0 newSize) newSize 0).Perm a := by
exact (maxHeapifyFuel_perm newSize (swapAt a 0 newSize) newSize 0).trans
(swapAt_perm a 0 newSize)
have h0_len : 0 < a.length := Nat.lt_of_lt_of_le hcond.1 hcond.2
have hlast_len : newSize < a.length := by
dsimp [newSize]
omega
have hroot_val :
valAt a 0 = a[0]'(Nat.lt_of_lt_of_le hcond.1 hcond.2) := by
rw [valAt_eq_getElem a h0_len]
have hmax_bound :
∀ {i : Nat}, i < heapSize →
valAt a i ≤ a[0]'(Nat.lt_of_lt_of_le hcond.1 hcond.2) := by
intro i hi
rw [← hroot_val]
exact hheap.valAt_le_root hi
have hheap_rest :
ArrayMaxHeap (maxHeapifyFuel newSize (swapAt a 0 newSize) newSize 0)
newSize := by
by_cases hnew : 0 < newSize
· have hheap' : ArrayMaxHeap a (newSize + 1) := by
rwa [hsize_eq]
have hexcept : ArrayMaxHeapExcept (swapAt a 0 newSize) newSize 0 :=
ArrayMaxHeapExcept.of_swap_root_last
(a := a) (newHeapSize := newSize) hheap'
exact maxHeapifyFuel_root_isMaxHeap
(fuel := newSize) hexcept hnew (Nat.le_refl newSize)
· have hzero : newSize = 0 := by omega
rw [hzero]
refine ⟨Nat.zero_le _, ?_, ?_⟩ <;> intro i hi hchild <;> omega
have hstored :
valAt (maxHeapifyFuel newSize (swapAt a 0 newSize) newSize 0) newSize =
a[0]'(Nat.lt_of_lt_of_le hcond.1 hcond.2) := by
by_cases hnew : 0 < newSize
· have hheapify_read :
valAt (maxHeapifyFuel newSize (swapAt a 0 newSize) newSize 0) newSize =
valAt (swapAt a 0 newSize) newSize :=
maxHeapifyFuel_valAt_of_heapSize_le
(fuel := newSize) (a := swapAt a 0 newSize) (heapSize := newSize)
(i := 0) (k := newSize) hlen_swapped hnew (Nat.le_refl newSize)
have hswap_read :
valAt (swapAt a 0 newSize) newSize = valAt a 0 :=
valAt_swapAt_right h0_len hlast_len
rw [hheapify_read, hswap_read, hroot_val]
· have hzero : newSize = 0 := by omega
rw [hzero]
have hswap_read : valAt (swapAt a 0 0) 0 = valAt a 0 :=
valAt_swapAt_right h0_len h0_len
simpa [maxHeapifyFuel, hroot_val] using hswap_read
refine ⟨hcond.1, hsize_eq, hheap_rest, hrest_len, hrest_perm, hmax_bound, ?_, hstored⟩
rw [hrest_len]
exact hlast_len
· simp [hcond] at hresArray-level delete
Array-level CLRS HEAP-DELETE, expressed through the usual priority-queue
recipe: raise the target cell to the current root maximum, then extract the
maximum. In a finite natural-number model, the old root key is enough: it is a
heap-prefix upper bound, so replacing the target by that key makes the target
eligible for the subsequent extract-max step.
def arrayHeapDelete? (a : List Nat) (heapSize i : Nat) :
Option (Nat × List Nat × Nat) :=
if _h : i < heapSize ∧ heapSize ≤ a.length then
match arrayHeapIncreaseKey? a heapSize i (valAt a 0) with
| some raised =>
match arrayHeapExtractMax? raised heapSize with
| some (_removed, rest, newHeapSize) => some (valAt a i, rest, newHeapSize)
| none => none
| none => none
else
none
State-correctness theorem for array-level HEAP-DELETE. The returned
heap prefix has size one less than the old prefix, is a max-heap, and the
backing list is exactly the permutation produced by replacing the deleted cell
with the old root maximum before the extract step.
theorem arrayHeapDelete?_state_correct {a rest : List Nat}
{heapSize i deleted newHeapSize : Nat}
(hheap : ArrayMaxHeap a heapSize)
(hres : arrayHeapDelete? a heapSize i = some (deleted, rest, newHeapSize)) :
i < heapSize ∧
heapSize ≤ a.length ∧
deleted = valAt a i ∧
newHeapSize + 1 = heapSize ∧
ArrayMaxHeap rest newHeapSize ∧
rest.length = a.length ∧
rest.Perm (a.set i (valAt a 0)) ∧
(∀ {k : Nat}, k < heapSize → valAt a k ≤ valAt a 0) := by
unfold arrayHeapDelete? at hres
by_cases hcond : i < heapSize ∧ heapSize ≤ a.length
· simp [hcond] at hres
cases hinc : arrayHeapIncreaseKey? a heapSize i (valAt a 0) with
| none =>
simp [hinc] at hres
| some raised =>
simp [hinc] at hres
cases hext : arrayHeapExtractMax? raised heapSize with
| none =>
simp [hext] at hres
| some extracted =>
rcases extracted with ⟨removed, extractedRest, extractedNewHeapSize⟩
simp [hext] at hres
rcases hres with ⟨hdeleted, hrest, hnew⟩
subst deleted
subst rest
subst newHeapSize
have hinc_correct := arrayHeapIncreaseKey?_state_correct
(a := a) (rest := raised) (heapSize := heapSize) (i := i)
(key := valAt a 0) hheap hinc
have hext_correct := arrayHeapExtractMax?_state_correct
(a := raised) (heapSize := heapSize) (m := removed)
(rest := extractedRest) (newHeapSize := extractedNewHeapSize)
hinc_correct.2.2.2.1 hext
have hroot_bound :
∀ {k : Nat}, k < heapSize → valAt a k ≤ valAt a 0 := by
intro k hk
exact hheap.valAt_le_root hk
have hperm : extractedRest.Perm (a.set i (valAt a 0)) :=
hext_correct.2.2.2.2.1.trans hinc_correct.2.2.2.2.2
refine ⟨hcond.1, hcond.2, rfl, hext_correct.2.1,
hext_correct.2.2.1, ?_, hperm, hroot_bound⟩
exact hext_correct.2.2.2.1.trans hinc_correct.2.2.2.2.1
· simp [hcond] at hresend Chapter06end CLRSDefinitions and proofs
CLRSLean.FourthEdition.Chapter_06.Section_06_5_Priority_Queues.Insert.Basic
Section 6.5 — Basic array-level MAX-HEAP-INSERT
Appending a key to a full-prefix max-heap can invalidate only the new cell's incoming edge. The existing upward-exception invariant and bubble loop provide the complete repair proof.
namespace CLRSnamespace Chapter06Reading an old array cell is unchanged after appending one new cell.
theorem valAt_append_singleton_of_lt {a : List Nat} {key i : Nat}
(hi : i < a.length) :
valAt (a ++ [key]) i = valAt a i := by
simp [valAt, List.getD, List.getElem?_append_left hi]Appending a key to a full-prefix heap leaves at most the new cell's incoming edge invalid. The new cell has no children inside the enlarged heap.
theorem ArrayMaxHeap.append_key_except_up {a : List Nat}
(hheap : ArrayMaxHeap a a.length) (key : Nat) :
ArrayMaxHeapExceptUp (a ++ [key]) (a.length + 1) a.length := by
refine ⟨by simp, ?_, ?_, ?_⟩
· intro j hj hl hne
have hchild : left j < a.length := by omega
have hparent : j < a.length := by
unfold left at hl
omega
rw [valAt_append_singleton_of_lt hchild,
valAt_append_singleton_of_lt hparent,
valAt_eq_getElem a hchild,
valAt_eq_getElem a hparent]
exact hheap.left_le hparent hchild
· intro j hj hr hne
have hchild : right j < a.length := by omega
have hparent : j < a.length := by
unfold right at hr
omega
rw [valAt_append_singleton_of_lt hchild,
valAt_append_singleton_of_lt hparent,
valAt_eq_getElem a hchild,
valAt_eq_getElem a hparent]
exact hheap.right_le hparent hchild
· intro _hpos _hbad
constructor
· intro hleft
unfold left at hleft
omega
· intro hright
unfold right at hright
omega
Array-level CLRS MAX-HEAP-INSERT: append the new key and bubble it upward.
The fuel is the starting index, matching the existing increase-key repair loop.
def arrayHeapInsert (a : List Nat) (key : Nat) : List Nat :=
arrayHeapIncreaseKeyBubbleUpFuel a.length (a ++ [key])
(a.length + 1) a.lengthArray-level insertion restores the max-heap invariant.
theorem arrayHeapInsert_isMaxHeap {a : List Nat} (key : Nat)
(hheap : ArrayMaxHeap a a.length) :
ArrayMaxHeap (arrayHeapInsert a key) (a.length + 1) := by
exact (hheap.append_key_except_up key).bubbleUpFuel_global
(by omega) (Nat.le_refl a.length)Array-level insertion grows the backing list by exactly one cell.
theorem arrayHeapInsert_length (a : List Nat) (key : Nat) :
(arrayHeapInsert a key).length = a.length + 1 := by
simpa [arrayHeapInsert] using
arrayHeapIncreaseKeyBubbleUpFuel_length a.length (a ++ [key])
(a.length + 1) a.lengthArray-level insertion adds exactly the new key to the old multiset.
theorem arrayHeapInsert_perm (a : List Nat) (key : Nat) :
(arrayHeapInsert a key).Perm (key :: a) := by
have hbubble :=
arrayHeapIncreaseKeyBubbleUpFuel_perm a.length (a ++ [key])
(a.length + 1) a.length
have happend : (a ++ [key]).Perm (key :: a) := by
simpa only [List.singleton_append] using
(List.perm_append_comm : (a ++ [key]).Perm ([key] ++ a))
exact hbubble.trans happendThe textbook state package for full-prefix array-level insertion.
theorem arrayHeapInsert_state_correct {a : List Nat} (key : Nat)
(hheap : ArrayMaxHeap a a.length) :
ArrayMaxHeap (arrayHeapInsert a key) (a.length + 1) ∧
(arrayHeapInsert a key).length = a.length + 1 ∧
(arrayHeapInsert a key).Perm (key :: a) := by
exact ⟨arrayHeapInsert_isMaxHeap key hheap,
arrayHeapInsert_length a key, arrayHeapInsert_perm a key⟩end Chapter06end CLRSCLRSLean.FourthEdition.Chapter_06.Section_06_5_Priority_Queues.Insert.Checked
Section 6.5 — Checked active-prefix MAX-HEAP-INSERT
The checked list-backed operation accepts exactly the states whose active heap prefix fits in the backing list. It inserts a new cell between that prefix and the inactive tail, preserving the tail order.
namespace CLRSnamespace Chapter06Restricting a heap to its active prefix preserves the indexed heap.
theorem ArrayMaxHeap.take {a : List Nat} {heapSize : Nat}
(hheap : ArrayMaxHeap a heapSize) :
ArrayMaxHeap (a.take heapSize) heapSize := by
have hlen : (a.take heapSize).length = heapSize :=
List.length_take_of_le hheap.heapSize_le_length
refine ⟨by simp [hlen], ?_, ?_⟩
· intro i hi hl
simpa only [List.getElem_take] using hheap.left_le hi hl
· intro i hi hr
simpa only [List.getElem_take] using hheap.right_le hi hrAppending an inactive tail does not change heap obligations in the prefix.
theorem ArrayMaxHeap.append_tail {xs tail : List Nat} {heapSize : Nat}
(hheap : ArrayMaxHeap xs heapSize) :
ArrayMaxHeap (xs ++ tail) heapSize := by
refine ⟨?_, ?_, ?_⟩
· calc
heapSize ≤ xs.length := hheap.heapSize_le_length
_ ≤ (xs ++ tail).length := by simp
· intro i hi hl
have hiPrefix : i < xs.length :=
Nat.lt_of_lt_of_le hi hheap.heapSize_le_length
have hlPrefix : left i < xs.length :=
Nat.lt_of_lt_of_le hl hheap.heapSize_le_length
simpa only [List.getElem_append_left hiPrefix,
List.getElem_append_left hlPrefix] using hheap.left_le hi hl
· intro i hi hr
have hiPrefix : i < xs.length :=
Nat.lt_of_lt_of_le hi hheap.heapSize_le_length
have hrPrefix : right i < xs.length :=
Nat.lt_of_lt_of_le hr hheap.heapSize_le_length
simpa only [List.getElem_append_left hiPrefix,
List.getElem_append_left hrPrefix] using hheap.right_le hi hrTotal checked insertion into an active heap prefix. The inactive tail remains after the newly enlarged prefix; an out-of-range heap size is rejected.
def arrayHeapInsert? (a : List Nat) (heapSize key : Nat) : Option (List Nat × Nat) :=
if _h : heapSize ≤ a.length then
some
(arrayHeapInsert (a.take heapSize) key ++ a.drop heapSize,
heapSize + 1)
else
noneChecked insertion fails exactly when the active prefix exceeds the backing list.
theorem arrayHeapInsert?_eq_none_iff (a : List Nat) (heapSize key : Nat) :
arrayHeapInsert? a heapSize key = none ↔ ¬ heapSize ≤ a.length := by
unfold arrayHeapInsert?
by_cases h : heapSize ≤ a.length <;> simp [h]Exact guard and output characterization for successful checked insertion.
theorem arrayHeapInsert?_eq_some_iff
(a rest : List Nat) (heapSize newHeapSize key : Nat) :
arrayHeapInsert? a heapSize key = some (rest, newHeapSize) ↔
heapSize ≤ a.length ∧
rest = arrayHeapInsert (a.take heapSize) key ++ a.drop heapSize ∧
newHeapSize = heapSize + 1 := by
unfold arrayHeapInsert?
by_cases h : heapSize ≤ a.length
· rw [dif_pos h]
simp only [Option.some.injEq, Prod.mk.injEq]
constructor
· rintro ⟨hrest, hsize⟩
exact ⟨h, hrest.symm, hsize.symm⟩
· rintro ⟨_, hrest, hsize⟩
exact ⟨hrest.symm, hsize.symm⟩
· rw [dif_neg h]
simp [h]Successful checked insertion grows the active heap and backing list by one, preserves the inactive tail order, and adds exactly the requested key.
theorem arrayHeapInsert?_state_correct
{a rest : List Nat} {heapSize newHeapSize key : Nat}
(hheap : ArrayMaxHeap a heapSize)
(hres : arrayHeapInsert? a heapSize key = some (rest, newHeapSize)) :
heapSize ≤ a.length ∧
newHeapSize = heapSize + 1 ∧
rest.length = a.length + 1 ∧
ArrayMaxHeap rest newHeapSize ∧
rest.Perm (key :: a) ∧
rest.drop newHeapSize = a.drop heapSize := by
have hspec := (arrayHeapInsert?_eq_some_iff
a rest heapSize newHeapSize key).1 hres
rcases hspec with ⟨hguard, rfl, rfl⟩
have htakeLen : (a.take heapSize).length = heapSize :=
List.length_take_of_le hguard
have hprefixHeap :
ArrayMaxHeap (a.take heapSize) (a.take heapSize).length := by
simpa [htakeLen] using hheap.take
have hinsertHeap := arrayHeapInsert_isMaxHeap key hprefixHeap
have hresultHeap :
ArrayMaxHeap
(arrayHeapInsert (a.take heapSize) key ++ a.drop heapSize)
(heapSize + 1) := by
simpa [htakeLen] using hinsertHeap.append_tail (tail := a.drop heapSize)
have hlength :
(arrayHeapInsert (a.take heapSize) key ++ a.drop heapSize).length =
a.length + 1 := by
rw [List.length_append, arrayHeapInsert_length]
simp only [htakeLen, List.length_drop]
have hsplit := Nat.sub_add_cancel hguard
omega
have hpermPrefix :=
(arrayHeapInsert_perm (a.take heapSize) key).append_right (a.drop heapSize)
have hperm :
(arrayHeapInsert (a.take heapSize) key ++ a.drop heapSize).Perm
(key :: a) := by
simpa [List.take_append_drop] using hpermPrefix
have hinsertLen :
(arrayHeapInsert (a.take heapSize) key).length = heapSize + 1 := by
simpa [htakeLen] using arrayHeapInsert_length (a.take heapSize) key
have htail :
(arrayHeapInsert (a.take heapSize) key ++ a.drop heapSize).drop
(heapSize + 1) =
a.drop heapSize := by
rw [← hinsertLen]
exact List.drop_left
exact ⟨hguard, rfl, hlength, hresultHeap, hperm, htail⟩end Chapter06end CLRSCLRSLean.FourthEdition.Chapter_06.Section_06_5_Priority_Queues.Insert.Cost
Section 6.5 — Costed MAX-HEAP-INSERT
This module charges one unit for every visited nonempty-fuel frame of the upward-bubbling controller. Reads, writes, list insertion, allocation, and guards are not separately charged, matching Chapter 6's existing abstract unit-control-step metric rather than claiming RAM costs for Lean lists.
namespace CLRSnamespace Chapter06Upward bubbling paired with its number of visited control frames.
def arrayHeapIncreaseKeyBubbleUpFuelWithCost :
Nat → List Nat → Nat → Nat → List Nat × Nat
| 0, a, _heapSize, _i => (a, 0)
| fuel + 1, a, heapSize, i =>
if _ : 0 < i then
if valAt a (parent i) < valAt a i then
let next := arrayHeapIncreaseKeyBubbleUpFuelWithCost fuel
(swapAt a i (parent i)) heapSize (parent i)
(next.1, next.2 + 1)
else
(a, 1)
else
(a, 1)Erasing the cost recovers the existing upward-bubbling implementation.
theorem arrayHeapIncreaseKeyBubbleUpFuelWithCost_result
(fuel : Nat) (a : List Nat) (heapSize i : Nat) :
(arrayHeapIncreaseKeyBubbleUpFuelWithCost fuel a heapSize i).1 =
arrayHeapIncreaseKeyBubbleUpFuel fuel a heapSize i := by
induction fuel generalizing a i with
| zero =>
simp [arrayHeapIncreaseKeyBubbleUpFuelWithCost,
arrayHeapIncreaseKeyBubbleUpFuel]
| succ fuel ih =>
simp only [arrayHeapIncreaseKeyBubbleUpFuelWithCost,
arrayHeapIncreaseKeyBubbleUpFuel]
split_ifs
· exact ih _ _
· rfl
· rflA zero-based heap parent step drops the binary index level by at least one.
theorem parent_log_add_one_le {i : Nat} (hpos : 0 < i) :
Nat.log 2 (parent i + 1) + 1 ≤ Nat.log 2 (i + 1) := by
have hmul : 2 * (parent i + 1) ≤ i + 1 := by
rcases eq_left_or_right_parent hpos with hleft | hright
· unfold left at hleft
omega
· unfold right at hright
omega
have hmono : Nat.log 2 (2 * (parent i + 1)) ≤ Nat.log 2 (i + 1) :=
Nat.log_mono_right hmul
have hlogmul :
Nat.log 2 (2 * (parent i + 1)) = Nat.log 2 (parent i + 1) + 1 := by
rw [Nat.mul_comm 2 (parent i + 1)]
exact Nat.log_mul_base (by norm_num) (by omega)
rwa [hlogmul] at hmonoUpward bubbling visits at most one frame per binary-index level, including its terminal frame.
theorem arrayHeapIncreaseKeyBubbleUpFuelWithCost_cost_le_log
(fuel : Nat) (a : List Nat) (heapSize i : Nat) :
(arrayHeapIncreaseKeyBubbleUpFuelWithCost fuel a heapSize i).2 ≤
Nat.log 2 (i + 1) + 1 := by
induction fuel generalizing a i with
| zero =>
simp [arrayHeapIncreaseKeyBubbleUpFuelWithCost]
| succ fuel ih =>
simp only [arrayHeapIncreaseKeyBubbleUpFuelWithCost]
split_ifs with hpos hswap
· have hrec := ih (swapAt a i (parent i)) (parent i)
have hdrop := parent_log_add_one_le hpos
omega
· simp
· simpFull-prefix insertion paired with upward-bubbling control cost.
def arrayHeapInsertWithCost (a : List Nat) (key : Nat) : List Nat × Nat :=
arrayHeapIncreaseKeyBubbleUpFuelWithCost a.length (a ++ [key])
(a.length + 1) a.length
Erasing full-prefix insertion cost recovers arrayHeapInsert.
theorem arrayHeapInsertWithCost_result (a : List Nat) (key : Nat) :
(arrayHeapInsertWithCost a key).1 = arrayHeapInsert a key := by
exact arrayHeapIncreaseKeyBubbleUpFuelWithCost_result
a.length (a ++ [key]) (a.length + 1) a.lengthFull-prefix insertion has an explicit logarithmic control-frame bound.
theorem arrayHeapInsertWithCost_cost_le_log (a : List Nat) (key : Nat) :
(arrayHeapInsertWithCost a key).2 ≤ Nat.log 2 (a.length + 1) + 1 := by
exact arrayHeapIncreaseKeyBubbleUpFuelWithCost_cost_le_log
a.length (a ++ [key]) (a.length + 1) a.lengthChecked active-prefix insertion with its upward-bubbling control cost.
def arrayHeapInsertWithCost? (a : List Nat) (heapSize key : Nat) :
Option ((List Nat × Nat) × Nat) :=
if _h : heapSize ≤ a.length then
let run := arrayHeapInsertWithCost (a.take heapSize) key
some ((run.1 ++ a.drop heapSize, heapSize + 1), run.2)
else
noneErasing checked insertion cost recovers the checked state operation.
theorem arrayHeapInsertWithCost?_result (a : List Nat) (heapSize key : Nat) :
(arrayHeapInsertWithCost? a heapSize key).map Prod.fst =
arrayHeapInsert? a heapSize key := by
unfold arrayHeapInsertWithCost? arrayHeapInsert?
by_cases h : heapSize ≤ a.length
· simp [h, arrayHeapInsertWithCost_result]
· simp [h]The checked costed wrapper inherits the complete state contract and the logarithmic bound in the enlarged heap size.
theorem arrayHeapInsertWithCost?_state_correct_and_log_cost
{a rest : List Nat} {heapSize newHeapSize key cost : Nat}
(hheap : ArrayMaxHeap a heapSize)
(hres : arrayHeapInsertWithCost? a heapSize key =
some ((rest, newHeapSize), cost)) :
heapSize ≤ a.length ∧
newHeapSize = heapSize + 1 ∧
rest.length = a.length + 1 ∧
ArrayMaxHeap rest newHeapSize ∧
rest.Perm (key :: a) ∧
rest.drop newHeapSize = a.drop heapSize ∧
cost ≤ Nat.log 2 newHeapSize + 1 := by
have hraw : arrayHeapInsert? a heapSize key = some (rest, newHeapSize) := by
have hmap := congrArg (Option.map Prod.fst) hres
rw [arrayHeapInsertWithCost?_result] at hmap
simpa using hmap
have hstate := arrayHeapInsert?_state_correct hheap hraw
rcases hstate with ⟨hguard, hsize, hlength, hresultHeap, hperm, htail⟩
have hcostRun := arrayHeapInsertWithCost_cost_le_log (a.take heapSize) key
have htakeLen : (a.take heapSize).length = heapSize :=
List.length_take_of_le hguard
have hcost : cost = (arrayHeapInsertWithCost (a.take heapSize) key).2 := by
unfold arrayHeapInsertWithCost? at hres
rw [dif_pos hguard] at hres
have hall :
(rest = (arrayHeapInsertWithCost (a.take heapSize) key).1 ++ a.drop heapSize ∧
newHeapSize = heapSize + 1) ∧
cost = (arrayHeapInsertWithCost (a.take heapSize) key).2 := by
simpa only [Option.some.injEq, Prod.mk.injEq] using hres.symm
exact hall.2
refine ⟨hguard, hsize, hlength, hresultHeap, hperm, htail, ?_⟩
rw [hcost, hsize]
simpa [htakeLen] using hcostRunend Chapter06end CLRS