Skip to content
Browse chapters
Imports

CLRS Chapter 18 - B-tree running time

This module bounds recursive-descent charges that follow the branch structure of the B-tree search, insertion, and deletion definitions. Each selected recursive call contributes one unit; terminal cases also contribute one. Top-level insertion adds one budget unit when the root splits.

These counters do not enumerate literal page reads/writes. In particular, split/borrow/merge accesses, separator scans, max/min predecessor/successor traversals, persistent list copying, and root-normalization work are not charged individually. No constant-factor refinement to complete page I/O is proved here.

Main results:

  • searchCost_le_height, insertCost_le_height, and deleteCost_le_height: the selected recursive path has at most height + 1 charges.

  • insertRootCost_le_height: the insertion descent/root-split budget is at most height + 3.

  • The historical *_le_diskAccessBound theorems bound these same descent counters on well-formed trees with 2 ≤ t.

  • diskAccessBound_isBigO_log_t: the common mathematical envelope is O(log_t n). The historical name is retained for compatibility; it does not convert the descent counter into a complete disk-access trace.

Functional insertion/deletion structure, key-bag, and search correctness are independent of this accounting boundary and remain available unchanged.

Notation conventions used in this section:

  • t : B-tree minimum degree (2 ≤ t for every cost theorem)

  • tr : a BTree

  • totalKeys tr : the number of represented key slots (the n of CLRS)

namespace CLRSnamespace Chapter18namespace BTreeopen List

Recursive-descent charges

Number of nodes visited by searchExec for key x: one per level on the separator-selected descent path.

def searchCost (x : Nat) : BTree → Nat | node ks cs => if x ∈ ks then 1 else match _hc : cs[findChild ks x]? with | some child => 1 + searchCost x child | none => 1 termination_by tr => heightOf tr decreasing_by exact heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨findChild ks x, _hc⟩)

Recursive-descent charges for the insertion branch structure. A full-child split changes the selected subtree but adds no separate page-read/write events. This is not a literal count of every node inspected or modified.

def insertCost (t x : Nat) : BTree → Nat | node ks cs => if cs.isEmpty then 1 else let i := findChild ks x match _hc : cs[i]? with | none => 1 | some c => match _hcc : c with | node cKeys cChildren => if cKeys.length = 2 * t - 1 then let median := cKeys.getD (t - 1) 0 if x < median then 1 + insertCost t x (node (cKeys.take (t - 1)) (cChildren.take t)) else 1 + insertCost t x (node (cKeys.drop t) (cChildren.drop t)) else 1 + insertCost t x c termination_by tr => heightOf tr decreasing_by all_goals have hmem : node cKeys cChildren ∈ cs := List.mem_iff_getElem?.mpr ⟨i, _hc⟩ refine lt_of_le_of_lt ?_ (heightOf_mem_lt hmem) first | exact le_of_eq (congrArg heightOf _hcc) | exact heightOf_le_of_children_subset (List.take_subset _ _) | exact heightOf_le_of_children_subset (List.drop_subset _ _)

Recursive-descent charges for deletion. Borrow/merge selects the next subtree; max/min chooses a replacement key. The helper traversals and local page operations are not counted by the added unit at each recursive level.

def deleteCost (t : Nat) (x : Nat) : BTree → Nat | node ks cs => if cs.isEmpty then 1 else let i := findChild ks x if Variable name `hiPos` is not explicitly referenced. The binding can be removed (if unused) or named `_` (if used implicitly). Note: This linter can be disabled with `set_option linter.unusedVariables false`hiPos : 0 < i then let ki := i - 1 match Variable name `hk` is not explicitly referenced. The binding can be removed (if unused) or named `_` (if used implicitly). Note: This linter can be disabled with `set_option linter.unusedVariables false`hk : ks[ki]? with | some k => if Variable name `hkeq` is not explicitly referenced. The binding can be removed (if unused) or named `_` (if used implicitly). Note: This linter can be disabled with `set_option linter.unusedVariables false`hkeq : k = x then match Variable name `hcl` is not explicitly referenced. The binding can be removed (if unused) or named `_` (if used implicitly). Note: This linter can be disabled with `set_option linter.unusedVariables false`hcl : cs[ki]? with | some leftChild => match hcr : cs[ki + 1]? with | some rightChild => if hla : t ≤ numKeys leftChild then 1 + deleteCost t (maxKey leftChild) leftChild else if hlb : t ≤ numKeys rightChild then 1 + deleteCost t (minKey rightChild) rightChild else 1 + deleteCost t x (mergeNodes leftChild k rightChild) | none => 1 | none => 1 else match Variable name `hc` is not explicitly referenced. The binding can be removed (if unused) or named `_` (if used implicitly). Note: This linter can be disabled with `set_option linter.unusedVariables false`hc : cs[i]? with | some child => if hcg : t ≤ numKeys child then 1 + deleteCost t x child else match hls : cs[i - 1]? with | some leftSib => if Variable name `hlg` is not explicitly referenced. The binding can be removed (if unused) or named `_` (if used implicitly). Note: This linter can be disabled with `set_option linter.unusedVariables false`hlg : t ≤ numKeys leftSib then match hsep : ks[i - 1]? with | some sep => 1 + deleteCost t x (rotateLeft leftSib sep child).2.2 | none => 1 + deleteCost t x child else match Variable name `hrs` is not explicitly referenced. The binding can be removed (if unused) or named `_` (if used implicitly). Note: This linter can be disabled with `set_option linter.unusedVariables false`hrs : cs[i + 1]? with | some rightSib => if Variable name `hrg` is not explicitly referenced. The binding can be removed (if unused) or named `_` (if used implicitly). Note: This linter can be disabled with `set_option linter.unusedVariables false`hrg : t ≤ numKeys rightSib then match hsep : ks[i]? with | some sep => 1 + deleteCost t x (rotateRight child sep rightSib).1 | none => 1 + deleteCost t x child else match hsep : ks[i - 1]? with | some sep => 1 + deleteCost t x (mergeNodes leftSib sep child) | none => 1 + deleteCost t x child | none => match hsep : ks[i - 1]? with | some sep => 1 + deleteCost t x (mergeNodes leftSib sep child) | none => 1 + deleteCost t x child | none => 1 + deleteCost t x child | none => 1 | none => match Variable name `hc` is not explicitly referenced. The binding can be removed (if unused) or named `_` (if used implicitly). Note: This linter can be disabled with `set_option linter.unusedVariables false`hc : cs[i]? with | some child => if hcg : t ≤ numKeys child then 1 + deleteCost t x child else match hls : cs[i - 1]? with | some leftSib => if Variable name `hlg` is not explicitly referenced. The binding can be removed (if unused) or named `_` (if used implicitly). Note: This linter can be disabled with `set_option linter.unusedVariables false`hlg : t ≤ numKeys leftSib then match hsep : ks[i - 1]? with | some sep => 1 + deleteCost t x (rotateLeft leftSib sep child).2.2 | none => 1 + deleteCost t x child else match Variable name `hrs` is not explicitly referenced. The binding can be removed (if unused) or named `_` (if used implicitly). Note: This linter can be disabled with `set_option linter.unusedVariables false`hrs : cs[i + 1]? with | some rightSib => if Variable name `hrg` is not explicitly referenced. The binding can be removed (if unused) or named `_` (if used implicitly). Note: This linter can be disabled with `set_option linter.unusedVariables false`hrg : t ≤ numKeys rightSib then match hsep : ks[i]? with | some sep => 1 + deleteCost t x (rotateRight child sep rightSib).1 | none => 1 + deleteCost t x child else match hsep : ks[i - 1]? with | some sep => 1 + deleteCost t x (mergeNodes leftSib sep child) | none => 1 + deleteCost t x child | none => match hsep : ks[i - 1]? with | some sep => 1 + deleteCost t x (mergeNodes leftSib sep child) | none => 1 + deleteCost t x child | none => 1 + deleteCost t x child | none => 1 else match Variable name `hc` is not explicitly referenced. The binding can be removed (if unused) or named `_` (if used implicitly). Note: This linter can be disabled with `set_option linter.unusedVariables false`hc : cs[0]? with | some child => if hcg : t ≤ numKeys child then 1 + deleteCost t x child else match hrs : cs[1]? with | some rightSib => if Variable name `hrg` is not explicitly referenced. The binding can be removed (if unused) or named `_` (if used implicitly). Note: This linter can be disabled with `set_option linter.unusedVariables false`hrg : t ≤ numKeys rightSib then match hsep : ks[0]? with | some sep => 1 + deleteCost t x (rotateRight child sep rightSib).1 | none => 1 + deleteCost t x child else match hsep : ks[0]? with | some sep => 1 + deleteCost t x (mergeNodes child sep rightSib) | none => 1 + deleteCost t x child | none => 1 + deleteCost t x child | none => 1 termination_by tr => heightOf tr decreasing_by · exact heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨ki, hcl⟩) · exact heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨ki + 1, hcr⟩) · rw [heightOf_mergeNodes_eq_max] have ha : heightOf leftChild < heightOf (node ks cs) := heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨ki, hcl⟩) have hb : heightOf rightChild < heightOf (node ks cs) := heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨ki + 1, hcr⟩) omega all_goals first | (rw [heightOf_mergeNodes_eq_max] first | (have ha : heightOf leftSib < heightOf (node ks cs) := heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨_, hls⟩) have hb : heightOf child < heightOf (node ks cs) := heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨_, hc⟩) omega) | (have ha : heightOf child < heightOf (node ks cs) := heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨_, hc⟩) have hb : heightOf rightSib < heightOf (node ks cs) := heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨_, hrs⟩) omega)) | exact heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨_, hc⟩) | (have hle := heightOf_rotateLeft_right_le leftSib sep child have ha : heightOf leftSib < heightOf (node ks cs) := heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨_, hls⟩) have hb : heightOf child < heightOf (node ks cs) := heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨_, hc⟩) omega) | (have hle := heightOf_rotateRight_left_le child sep rightSib have ha : heightOf child < heightOf (node ks cs) := heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨_, hc⟩) have hb : heightOf rightSib < heightOf (node ks cs) := heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨_, hrs⟩) omega)

Height bounds

O(h) search. searchExec descends at most one path, so its number of descent charges is bounded by the tree height plus one.

theorem searchCost_le_height (x : Nat) (tr : BTree) : searchCost x tr ≤ heightOf tr + 1 := by induction tr using searchCost.induct x with | case1 ks cs hxkeys => rw [searchCost, if_pos hxkeys] omega | case2 ks cs hxkeys child hchild ih => rw [searchCost, if_neg hxkeys] split · rename_i child' hchild' rw [hchild] at hchild' cases hchild' have hlt : heightOf child < heightOf (node ks cs) := heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨findChild ks x, hchild⟩) omega · rename_i hnone rw [hchild] at hnone contradiction | case3 ks cs hxkeys hchild => rw [searchCost, if_neg hxkeys] split · rename_i child hchild' rw [hchild] at hchild' contradiction · omega

O(h) insertion. insertNonFull descends at most one path, so its number of descent charges is bounded by the tree height plus one.

theorem insertCost_le_height (t x : Nat) (tr : BTree) : insertCost t x tr ≤ heightOf tr + 1 := by induction tr using insertCost.induct (t := t) (x := x) with | case1 ks cs hempty => rw [insertCost, if_pos hempty] omega | case2 ks cs hne i hnone => have hval : insertCost t x (node ks cs) = 1 := by rw [insertCost, if_neg hne] dsimp only split · omega · rename_i c hc rw [hnone] at hc simp at hc rw [hval] omega | case3 ks cs hne i cKeys cChildren hsome hfull median hlt hsome2 ih => have hsome' : cs[findChild ks x]? = some (node cKeys cChildren) := hsome have hval : insertCost t x (node ks cs) = 1 + insertCost t x (node (cKeys.take (t - 1)) (cChildren.take t)) := by rw [insertCost, if_neg hne] dsimp only split · rename_i hcnone rw [hsome'] at hcnone simp at hcnone · rename_i c hcsome obtain rfl : c = node cKeys cChildren := by rw [hsome'] at hcsome; injection hcsome with h; exact h.symm dsimp only rw [if_pos hfull, if_pos hlt] rw [hval] have hmem : node cKeys cChildren ∈ cs := List.mem_iff_getElem?.mpr ⟨findChild ks x, hsome'⟩ have hltH : heightOf (node (cKeys.take (t - 1)) (cChildren.take t)) < heightOf (node ks cs) := lt_of_le_of_lt (heightOf_le_of_children_subset (List.take_subset _ _)) (heightOf_mem_lt hmem) omega | case4 ks cs hne i cKeys cChildren hsome hfull median hnlt hsome2 ih => have hsome' : cs[findChild ks x]? = some (node cKeys cChildren) := hsome have hval : insertCost t x (node ks cs) = 1 + insertCost t x (node (cKeys.drop t) (cChildren.drop t)) := by rw [insertCost, if_neg hne] dsimp only split · rename_i hcnone rw [hsome'] at hcnone simp at hcnone · rename_i c hcsome obtain rfl : c = node cKeys cChildren := by rw [hsome'] at hcsome; injection hcsome with h; exact h.symm dsimp only rw [if_pos hfull, if_neg hnlt] rw [hval] have hmem : node cKeys cChildren ∈ cs := List.mem_iff_getElem?.mpr ⟨findChild ks x, hsome'⟩ have hltH : heightOf (node (cKeys.drop t) (cChildren.drop t)) < heightOf (node ks cs) := lt_of_le_of_lt (heightOf_le_of_children_subset (List.drop_subset _ _)) (heightOf_mem_lt hmem) omega | case5 ks cs hne i cKeys cChildren hsome hnfull hsome2 ih => have hsome' : cs[findChild ks x]? = some (node cKeys cChildren) := hsome have hval : insertCost t x (node ks cs) = 1 + insertCost t x (node cKeys cChildren) := by rw [insertCost, if_neg hne] dsimp only split · rename_i hcnone rw [hsome'] at hcnone simp at hcnone · rename_i c hcsome obtain rfl : c = node cKeys cChildren := by rw [hsome'] at hcsome; injection hcsome with h; exact h.symm dsimp only rw [if_neg hnfull] rw [hval] have hmem : node cKeys cChildren ∈ cs := List.mem_iff_getElem?.mpr ⟨findChild ks x, hsome'⟩ have hltH : heightOf (node cKeys cChildren) < heightOf (node ks cs) := heightOf_mem_lt hmem omega

Deletion height bound

private lemma child_lt_node (ks : List Nat) {cs : List BTree} {j : Nat} {c : BTree} (hc : cs[j]? = some c) : heightOf c < heightOf (node ks cs) := heightOf_mem_lt (List.mem_iff_getElem?.mpr ⟨j, hc⟩) private lemma merge_lt_node (ks : List Nat) {cs : List BTree} {a b : BTree} (s : Nat) (ha : a ∈ cs) (hb : b ∈ cs) : heightOf (mergeNodes a s b) < heightOf (node ks cs) := by rw [heightOf_mergeNodes_eq_max] have hha : heightOf a < heightOf (node ks cs) := heightOf_mem_lt ha have hhb : heightOf b < heightOf (node ks cs) := heightOf_mem_lt hb omegaprivate lemma rotateLeft_target_lt_node (ks : List Nat) {cs : List BTree} {a b : BTree} (s : Nat) (ha : a ∈ cs) (hb : b ∈ cs) : heightOf (rotateLeft a s b).2.2 < heightOf (node ks cs) := by have hle : heightOf (rotateLeft a s b).2.2 ≤ max (heightOf a) (heightOf b) := heightOf_rotateLeft_right_le a s b have hha : heightOf a < heightOf (node ks cs) := heightOf_mem_lt ha have hhb : heightOf b < heightOf (node ks cs) := heightOf_mem_lt hb omegaprivate lemma rotateRight_target_lt_node (ks : List Nat) {cs : List BTree} {a b : BTree} (s : Nat) (ha : a ∈ cs) (hb : b ∈ cs) : heightOf (rotateRight a s b).1 < heightOf (node ks cs) := by have hle : heightOf (rotateRight a s b).1 ≤ max (heightOf a) (heightOf b) := heightOf_rotateRight_left_le a s b have hha : heightOf a < heightOf (node ks cs) := heightOf_mem_lt ha have hhb : heightOf b < heightOf (node ks cs) := heightOf_mem_lt hb omega

O(h) deletion. composedDelete descends at most one path, so its number of descent charges is bounded by the tree height plus one.

theorem deleteCost_le_height (t x : Nat) (tr : BTree) : deleteCost t x tr ≤ heightOf tr + 1 := by induction x, tr using deleteCost.induct (t := t) with | case1 x ks cs hleaf => rw [deleteCost, if_pos hleaf] omega | case2 ks cs hnonempty sep leftChild rightChild hleftReady i hpos ki hsep hleft hright ih => have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty simp only [i] at hpos simp only [ki, i] at hsep hleft hright rw [deleteCost] simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos] rw [hsep, hleft, hright] simp [hleftReady] have hlt := child_lt_node ks hleft omega | case3 ks cs hnonempty sep leftChild rightChild hleftNotReady hrightReady i hpos ki hsep hleft hright ih => have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty simp only [i] at hpos simp only [ki, i] at hsep hleft hright rw [deleteCost] simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos] rw [hsep, hleft, hright] simp [hleftNotReady, hrightReady] have hlt := child_lt_node ks hright omega | case4 ks cs hnonempty sep leftChild rightChild hleftNotReady hrightNotReady i hpos ki hsep hleft hright ih => have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty simp only [i] at hpos simp only [ki, i] at hsep hleft hright rw [deleteCost] simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos] rw [hsep, hleft, hright] simp [hleftNotReady, hrightNotReady] have hlt := merge_lt_node ks sep (List.mem_iff_getElem?.mpr ⟨ki, hleft⟩) (List.mem_iff_getElem?.mpr ⟨ki + 1, hright⟩) omega | case5 ks cs hnonempty sep leftChild i hpos ki hsep hleft hrightNone => have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty simp only [i] at hpos simp only [ki, i] at hsep hleft hrightNone rw [deleteCost] simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos] rw [hsep, hleft, hrightNone] simp | case6 ks cs hnonempty sep i hpos ki hsep hleftNone => have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty simp only [i] at hpos simp only [ki, i] at hsep hleftNone rw [deleteCost] simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos] rw [hsep, hleftNone] simp | case7 x ks cs hnonempty i hpos ki sep hsep hne child hchild hchildReady ih => have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty simp only [i] at hpos simp only [ki, i] at hsep hchild rw [deleteCost] simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos] rw [hsep, hchild] simp [hne, hchildReady] have hlt := child_lt_node ks hchild omega | case8 x ks cs hnonempty i hpos ki sep hsep hne child hchild hchildNotReady leftSib hleftSib hleftSibReady sep2 hsep2 ih => have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty simp only [i] at hpos hchild hleftSib hsep2 simp only [ki, i] at hsep have hsepEq : sep = sep2 := Option.some.inj (hsep.symm.trans hsep2) subst sep2 rw [deleteCost] simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos] rw [hsep, hchild, hleftSib] simp [hne, hchildNotReady, hleftSibReady] have hlt := rotateLeft_target_lt_node ks sep (List.mem_iff_getElem?.mpr ⟨i - 1, hleftSib⟩) (List.mem_iff_getElem?.mpr ⟨i, hchild⟩) omega | case9 x ks cs hnonempty i hpos ki sep hsep hne child hchild hchildNotReady leftSib hleftSib hleftSibReady hsepNone ih => simp only [ki, i] at hsep hsepNone rw [hsep] at hsepNone cases hsepNone | case10 x ks cs hnonempty i hpos ki sep hsep hne child hchild hchildNotReady leftSib hleftSib hleftSibNotReady rightSib hrightSib hrightSibReady sep2 hsep2 ih => have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty simp only [i] at hpos hchild hleftSib hrightSib hsep2 simp only [ki, i] at hsep rw [deleteCost] simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos] rw [hsep, hchild, hleftSib, hrightSib, hsep2] simp [hne, hchildNotReady, hleftSibNotReady, hrightSibReady] have hlt := rotateRight_target_lt_node ks sep2 (List.mem_iff_getElem?.mpr ⟨i, hchild⟩) (List.mem_iff_getElem?.mpr ⟨i + 1, hrightSib⟩) omega | case11 x ks cs hnonempty i hpos ki sep hsep hne child hchild hchildNotReady leftSib hleftSib hleftSibNotReady rightSib hrightSib hrightSibReady hsepNone ih => have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty simp only [i] at hpos hchild hleftSib hrightSib hsepNone simp only [ki, i] at hsep rw [deleteCost] simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos] rw [hsep, hchild, hleftSib, hrightSib, hsepNone] simp [hne, hchildNotReady, hleftSibNotReady, hrightSibReady] have hlt := child_lt_node ks hchild omega | case12 x ks cs hnonempty i hpos ki sep hsep hne child hchild hchildNotReady leftSib hleftSib hleftSibNotReady rightSib hrightSib hrightSibNotReady sep2 hsep2 ih => have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty simp only [i] at hpos hchild hleftSib hrightSib hsep2 simp only [ki, i] at hsep have hsepEq : sep = sep2 := Option.some.inj (hsep.symm.trans hsep2) subst sep2 rw [deleteCost] simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos] rw [hsep, hchild, hleftSib, hrightSib] simp [hne, hchildNotReady, hleftSibNotReady, hrightSibNotReady] have hlt := merge_lt_node ks sep (List.mem_iff_getElem?.mpr ⟨i - 1, hleftSib⟩) (List.mem_iff_getElem?.mpr ⟨i, hchild⟩) omega | case13 x ks cs hnonempty i hpos ki sep hsep hne child hchild hchildNotReady leftSib hleftSib hleftSibNotReady rightSib hrightSib hrightSibNotReady hsepNone ih => simp only [ki, i] at hsep hsepNone rw [hsep] at hsepNone cases hsepNone | case14 x ks cs hnonempty i hpos ki sep hsep hne child hchild hchildNotReady leftSib hleftSib hleftSibNotReady hrightNone sep2 hsep2 ih => have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty simp only [i] at hpos hchild hleftSib hrightNone hsep2 simp only [ki, i] at hsep have hsepEq : sep = sep2 := Option.some.inj (hsep.symm.trans hsep2) subst sep2 rw [deleteCost] simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos] rw [hsep, hchild, hleftSib, hrightNone] simp [hne, hchildNotReady, hleftSibNotReady] have hlt := merge_lt_node ks sep (List.mem_iff_getElem?.mpr ⟨i - 1, hleftSib⟩) (List.mem_iff_getElem?.mpr ⟨i, hchild⟩) omega | case15 x ks cs hnonempty i hpos ki sep hsep hne child hchild hchildNotReady leftSib hleftSib hleftSibNotReady hrightNone hsepNone ih => simp only [ki, i] at hsep hsepNone rw [hsep] at hsepNone cases hsepNone | case16 x ks cs hnonempty i hpos ki sep hsep hne child hchild hchildNotReady hleftNone ih => have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty simp only [i] at hpos simp only [ki, i] at hsep hchild hleftNone rw [deleteCost] simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos] rw [hsep, hchild, hleftNone] simp [hne, hchildNotReady] have hlt := child_lt_node ks hchild omega | case17 x ks cs hnonempty i hpos ki sep hsep hne hchildNone => have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty simp only [i] at hpos simp only [ki, i] at hsep hchildNone rw [deleteCost] simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos] rw [hsep, hchildNone] simp [hne] | case18 x ks cs hnonempty i hpos ki hsepNone child hchild hchildReady ih => have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty simp only [i] at hpos simp only [ki, i] at hsepNone hchild rw [deleteCost] simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos] rw [hsepNone, hchild] simp [hchildReady] have hlt := child_lt_node ks hchild omega | case19 x ks cs hnonempty i hpos ki hsepNone child hchild hchildNotReady leftSib hleftSib hleftSibReady sep hsep ih => simp only [i] at hsep simp only [ki, i] at hsepNone rw [hsepNone] at hsep cases hsep | case20 x ks cs hnonempty i hpos ki hsepNone child hchild hchildNotReady leftSib hleftSib hleftSibReady hsepNone2 ih => have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty simp only [i] at hpos hchild hleftSib simp only [ki, i] at hsepNone rw [deleteCost] simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos] rw [hsepNone, hchild, hleftSib] simp [hchildNotReady, hleftSibReady] have hlt := child_lt_node ks hchild omega | case21 x ks cs hnonempty i hpos ki hsepNone child hchild hchildNotReady leftSib hleftSib hleftSibNotReady rightSib hrightSib hrightSibReady sep hsep ih => have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty simp only [i] at hpos hchild hleftSib hrightSib hsep simp only [ki, i] at hsepNone rw [deleteCost] simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos] rw [hsepNone, hchild, hleftSib, hrightSib, hsep] simp [hchildNotReady, hleftSibNotReady, hrightSibReady] have hlt := rotateRight_target_lt_node ks sep (List.mem_iff_getElem?.mpr ⟨i, hchild⟩) (List.mem_iff_getElem?.mpr ⟨i + 1, hrightSib⟩) omega | case22 x ks cs hnonempty i hpos ki hsepNone child hchild hchildNotReady leftSib hleftSib hleftSibNotReady rightSib hrightSib hrightSibReady hsepNone2 ih => have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty simp only [i] at hpos hchild hleftSib hrightSib hsepNone2 simp only [ki, i] at hsepNone rw [deleteCost] simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos] rw [hsepNone, hchild, hleftSib, hrightSib, hsepNone2] simp [hchildNotReady, hleftSibNotReady, hrightSibReady] have hlt := child_lt_node ks hchild omega | case23 x ks cs hnonempty i hpos ki hsepNone child hchild hchildNotReady leftSib hleftSib hleftSibNotReady rightSib hrightSib hrightSibNotReady sep hsep ih => simp only [i] at hsep simp only [ki, i] at hsepNone rw [hsepNone] at hsep cases hsep | case24 x ks cs hnonempty i hpos ki hsepNone child hchild hchildNotReady leftSib hleftSib hleftSibNotReady rightSib hrightSib hrightSibNotReady hsepNone2 ih => have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty simp only [i] at hpos hchild hleftSib hrightSib simp only [ki, i] at hsepNone rw [deleteCost] simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos] rw [hsepNone, hchild, hleftSib, hrightSib] simp [hchildNotReady, hleftSibNotReady, hrightSibNotReady] have hlt := child_lt_node ks hchild omega | case25 x ks cs hnonempty i hpos ki hsepNone child hchild hchildNotReady leftSib hleftSib hleftSibNotReady hrightNone sep hsep ih => simp only [i] at hsep simp only [ki, i] at hsepNone rw [hsepNone] at hsep cases hsep | case26 x ks cs hnonempty i hpos ki hsepNone child hchild hchildNotReady leftSib hleftSib hleftSibNotReady hrightNone hsepNone2 ih => have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty simp only [i] at hpos hchild hleftSib hrightNone simp only [ki, i] at hsepNone rw [deleteCost] simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos] rw [hsepNone, hchild, hleftSib, hrightNone] simp [hchildNotReady, hleftSibNotReady] have hlt := child_lt_node ks hchild omega | case27 x ks cs hnonempty i hpos ki hsepNone child hchild hchildNotReady hleftNone ih => have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty simp only [i] at hpos hchild hleftNone simp only [ki, i] at hsepNone rw [deleteCost] simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos] rw [hsepNone, hchild, hleftNone] simp [hchildNotReady] have hlt := child_lt_node ks hchild omega | case28 x ks cs hnonempty i hpos ki hsepNone hchildNone => have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty simp only [i] at hpos simp only [ki, i] at hsepNone hchildNone rw [deleteCost] simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte, hpos] rw [hsepNone, hchildNone] simp | case29 x ks cs hnonempty i hnotPos child hchild hchildReady ih => have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty simp only [i] at hnotPos rw [deleteCost] simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte] rw [dif_neg hnotPos] rw [hchild] simp [hchildReady] have hlt := child_lt_node ks hchild omega | case30 x ks cs hnonempty i hnotPos child hchild hchildNotReady rightSib hrightSib hrightSibReady sep hsep ih => have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty simp only [i] at hnotPos rw [deleteCost] simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte] rw [dif_neg hnotPos] rw [hchild, hrightSib, hsep] simp [hchildNotReady, hrightSibReady] have hlt := rotateRight_target_lt_node ks sep (List.mem_iff_getElem?.mpr ⟨0, hchild⟩) (List.mem_iff_getElem?.mpr ⟨1, hrightSib⟩) omega | case31 x ks cs hnonempty i hnotPos child hchild hchildNotReady rightSib hrightSib hrightSibReady hsepNone ih => have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty simp only [i] at hnotPos rw [deleteCost] simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte] rw [dif_neg hnotPos] rw [hchild, hrightSib, hsepNone] simp [hchildNotReady, hrightSibReady] have hlt := child_lt_node ks hchild omega | case32 x ks cs hnonempty i hnotPos child hchild hchildNotReady rightSib hrightSib hrightSibNotReady sep hsep ih => have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty simp only [i] at hnotPos rw [deleteCost] simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte] rw [dif_neg hnotPos] rw [hchild, hrightSib, hsep] simp [hchildNotReady, hrightSibNotReady] have hlt := merge_lt_node ks sep (List.mem_iff_getElem?.mpr ⟨0, hchild⟩) (List.mem_iff_getElem?.mpr ⟨1, hrightSib⟩) omega | case33 x ks cs hnonempty i hnotPos child hchild hchildNotReady rightSib hrightSib hrightSibNotReady hsepNone ih => have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty simp only [i] at hnotPos rw [deleteCost] simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte] rw [dif_neg hnotPos] rw [hchild, hrightSib, hsepNone] simp [hchildNotReady, hrightSibNotReady] have hlt := child_lt_node ks hchild omega | case34 x ks cs hnonempty i hnotPos child hchild hchildNotReady hrightNone ih => have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty simp only [i] at hnotPos rw [deleteCost] simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte] rw [dif_neg hnotPos] rw [hchild, hrightNone] simp [hchildNotReady] have hlt := child_lt_node ks hchild omega | case35 x ks cs hnonempty i hnotPos hchildNone => have hnotLeaf : cs.isEmpty = false := Bool.eq_false_of_not_eq_true hnonempty simp only [i] at hnotPos rw [deleteCost] simp only [hnotLeaf, Bool.false_eq_true, ↓reduceIte] rw [dif_neg hnotPos] rw [hchildNone] simp

Top-level insertion cost

Insertion descent budget with one extra charge for the full-root split. The charge is a budget unit, not a literal count of split-page reads/writes.

def insertRootCost (t x : Nat) (tr : BTree) : Nat := if rootKeyCount tr = 2 * t - 1 then insertCost t x (splitRoot t tr) + 1 else insertCost t x tr

O(h) top-level insertion. Splitting a full root adds exactly one level and then insertNonFull descends at most one path, so the cost is bounded by the tree height plus three.

theorem insertRootCost_le_height (t x : Nat) (ht : 2 ≤ t) {tr : BTree} (hwf : WellFormed t tr) : insertRootCost t x tr ≤ heightOf tr + 3 := by unfold insertRootCost by_cases hfull : rootKeyCount tr = 2 * t - 1 · rw [if_pos hfull] have hsplit : heightOf (splitRoot t tr) = heightOf tr + 1 := splitRoot_height t ht hwf hfull have hins := insertCost_le_height t x (splitRoot t tr) omega · rw [if_neg hfull] have hins := insertCost_le_height t x tr omega

Logarithmic descent-budget bounds

Common logarithmic envelope for the descent budgets. Its historical diskAccessBound name is retained, but a full page-I/O interpretation requires additional accounting not established in this module.

def diskAccessBound (t : Nat) (n : Nat) : Nat := Nat.log t ((n + 1) / 2) + 3

Search has at most log_t n + O(1) descent charges on a well-formed tree.

theorem searchCost_le_diskAccessBound (t : Nat) (ht : 2 ≤ t) {tr : BTree} (hwf : WellFormed t tr) (x : Nat) : searchCost x tr ≤ diskAccessBound t (totalKeys tr) := by have hh : heightOf tr ≤ Nat.log t ((totalKeys tr + 1) / 2) := wellFormed_height_log_bound t ht hwf have hc := searchCost_le_height x tr unfold diskAccessBound omega

Top-level insertion has at most log_t n + O(1) descent/root-split charges on a well-formed tree.

theorem insertRootCost_le_diskAccessBound (t : Nat) (ht : 2 ≤ t) {tr : BTree} (hwf : WellFormed t tr) (x : Nat) : insertRootCost t x tr ≤ diskAccessBound t (totalKeys tr) := by have hh : heightOf tr ≤ Nat.log t ((totalKeys tr + 1) / 2) := wellFormed_height_log_bound t ht hwf have hc := insertRootCost_le_height t x ht hwf unfold diskAccessBound omega

Deletion has at most log_t n + O(1) descent charges on a well-formed tree.

theorem deleteCost_le_diskAccessBound (t : Nat) (ht : 2 ≤ t) {tr : BTree} (hwf : WellFormed t tr) (x : Nat) : deleteCost t x tr ≤ diskAccessBound t (totalKeys tr) := by have hh : heightOf tr ≤ Nat.log t ((totalKeys tr + 1) / 2) := wellFormed_height_log_bound t ht hwf have hc := deleteCost_le_height t x tr unfold diskAccessBound omega

The common descent-budget envelope is O(log_t n). This is an asymptotic theorem about the displayed numeric bound, not an additional operational refinement for page I/O or auxiliary traversals.

theorem diskAccessBound_isBigO_log_t (t : Nat) (ht : 2 ≤ t) : CLRS.Chapter03.isBigO (fun n => (diskAccessBound t n : ℝ)) (fun n => (Nat.log t n : ℝ)) := by rw [CLRS.Chapter03.isBigO_iff] refine ⟨4, by norm_num, t, ?_⟩ intro n hn have hmono : Nat.log t ((n + 1) / 2) ≤ Nat.log t n := by apply Nat.log_mono_right omega have hlogpos : 0 < Nat.log t n := Nat.log_pos (by omega : 1 < t) (by omega : t ≤ n) unfold diskAccessBound have h1 : (Nat.log t ((n + 1) / 2) + 3 : ℝ) ≤ 4 * (Nat.log t n : ℝ) := by have h2 : (Nat.log t ((n + 1) / 2) : ℝ) ≤ (Nat.log t n : ℝ) := by exact_mod_cast hmono have h3 : (3 : ℝ) ≤ 3 * (Nat.log t n : ℝ) := by have h4 : (1 : ℝ) ≤ (Nat.log t n : ℝ) := by exact_mod_cast (Nat.succ_le_of_lt hlogpos) nlinarith nlinarith have h_nonneg_left : 0 ≤ ((Nat.log t ((n + 1) / 2) + 3 : Nat) : ℝ) := by positivity have h_nonneg_right : 0 ≤ (Nat.log t n : ℝ) := by positivity rw [abs_of_nonneg h_nonneg_left, abs_of_nonneg h_nonneg_right] push_cast exact h1
end BTreeend Chapter18end CLRS