Imports
open Finsetopen scoped BigOperators4.4. The Recursion-Tree Method for Solving Recurrences
This file makes the finite-sum core of the CLRS recursion-tree method explicit.
Main results:
-
Theorem
CLRS.Chapter04.recursion_tree_additive_unroll: an additive one-step recurrence is exactly the base value plus the sum of level costs. -
Theorem
CLRS.Chapter04.recursion_tree_additive_upper_envelope: if each level cost is bounded by an envelope, the whole tree is bounded by the sum of the envelope. -
Theorem
CLRS.Chapter04.recursion_tree_constant_level_cost: constant level costs give the usual linear closed form. -
Theorem
CLRS.Chapter04.BranchingRecursionTree.totalCost_eq_levelCosts_add_leafCost: an explicit finite branching tree decomposes into internal level costs plus leaf costs. -
Theorems
CLRS.Chapter04.balancedThreeQuarter_totalCost_leandCLRS.Chapter04.unbalancedThirdTwoThird_totalCost: the two detailed textbook examples have respectively geometric and constant level costs. -
Theorem
CLRS.Chapter04.IntegerBranchingSpec.build_totalCost_eq: an explicit finite natural-size tree has exactly the value of its independently stated floor/ceiling recurrence, even when branches stop at unequal depths. -
Theorems
CLRS.Chapter04.balancedIntegerTree_totalCost_eqandCLRS.Chapter04.unbalancedIntegerTree_totalCost_eq: arbitrary-input integer instances of the two detailed textbook examples. -
Theorems
CLRS.Chapter04.balancedIntegerCost_isBigThetaandCLRS.Chapter04.unbalancedIntegerCost_isBigTheta: actual rounded-tree total costs areΘ(n²)andΘ(n log n)for positive local-cost coefficients and nonnegative base costs.
Status: proved for the finite-sum core, the fixed-depth level-sum model,
and the explicit unequal-depth integer floor/ceiling trees. The exact tree
semantics equal independently stated rounded recurrences. Strong induction on these recurrences
proves the two actual execution bounds, including unequal child depths. This
does not claim a general theorem for arbitrary rounded branching recurrences.
namespace CLRSnamespace Chapter04Unroll an additive recurrence into the sum of its level costs.
theorem recursion_tree_additive_unroll (T cost : ℕ → ℝ)
(hstep : ∀ n, T (n + 1) = T n + cost n) (n : ℕ) :
T n = T 0 + ∑ k ∈ range n, cost k := by
induction n with
| zero =>
simp
| succ n ih =>
rw [hstep n, ih]
simp [sum_range_succ, add_assoc]If every level cost is bounded by an envelope, then the unrolled recursion tree is bounded by the sum of that envelope.
theorem recursion_tree_additive_upper_envelope (T cost envelope : ℕ → ℝ)
(hcost : ∀ k, cost k ≤ envelope k)
(hstep : ∀ n, T (n + 1) = T n + cost n) (n : ℕ) :
T n ≤ T 0 + ∑ k ∈ range n, envelope k := by
rw [recursion_tree_additive_unroll T cost hstep n]
exact add_le_add le_rfl (Finset.sum_le_sum (fun k _hk => hcost k))If every level cost is at least an envelope, then the same unrolling gives a lower bound by the envelope sum.
theorem recursion_tree_additive_lower_envelope (T cost envelope : ℕ → ℝ)
(hcost : ∀ k, envelope k ≤ cost k)
(hstep : ∀ n, T (n + 1) = T n + cost n) (n : ℕ) :
T 0 + ∑ k ∈ range n, envelope k ≤ T n := by
rw [recursion_tree_additive_unroll T cost hstep n]
exact add_le_add le_rfl (Finset.sum_le_sum (fun k _hk => hcost k))Constant level costs collapse the recursion tree to the base value plus a linear term.
theorem recursion_tree_constant_level_cost (T : ℕ → ℝ) {level : ℝ}
(hstep : ∀ n, T (n + 1) = T n + level) :
∀ n : ℕ, T n = T 0 + level * (n : ℝ) := by
intro n
rw [recursion_tree_additive_unroll T (fun _ => level) hstep n]
simp [Finset.sum_const, nsmul_eq_mul, mul_comm]
A bounded-cost recursion tree with at most level work per depth is
bounded by the base bound plus level * n.
theorem recursion_tree_constant_upper_bound (T cost : ℕ → ℝ) {base level : ℝ}
(hbase : T 0 ≤ base)
(hcost : ∀ k, cost k ≤ level)
(hstep : ∀ n, T (n + 1) = T n + cost n) :
∀ n : ℕ, T n ≤ base + level * (n : ℝ) := by
intro n
have hsum := recursion_tree_additive_upper_envelope T cost (fun _ => level) hcost hstep n
calc
T n ≤ T 0 + ∑ _k ∈ range n, level := hsum
_ ≤ base + ∑ _k ∈ range n, level := by linarith
_ = base + level * (n : ℝ) := by
simp [Finset.sum_const, nsmul_eq_mul, mul_comm]end Chapter04end CLRSDefinitions and proofs
CLRSLean.FourthEdition.Chapter_04.Section_04_4_Recursion_Tree_Method.Branching.IntegerTree.Asymptotics
Connections to Chapter 4 asymptotic interfaces
The costs of the generated rounded trees satisfy the textbook bounds on all
natural inputs: the balanced tree has quadratic cost, and the unequal-depth
tree has Θ(n log n) cost. The proofs use their actual recurrence
equations, including the floor and ceiling operations and base cases.
namespace CLRSnamespace Chapter04
The integer unequal-depth example uses the classic Akra--Bazzi root p=1.
theorem unbalancedInteger_akraBazziRoot :
IsAkraBazziRoot [(1, (3 : Real)), (1, (3 : Real) / 2)] 1 :=
akraBazziRoot_two_thirds_oneThe balanced tree has nonnegative cost and a quadratic upper potential, including zero-size leaves.
theorem balancedIntegerCost_bounds {c base : ℝ} (hc : 0 ≤ c) (hb : 0 ≤ base)
(n : ℕ) :
0 ≤ balancedIntegerCost c base n ∧
balancedIntegerCost c base n ≤ (4*c+base)*((n : ℝ)+1)^2 := by
induction n using Nat.strong_induction_on with
| h n ih =>
by_cases hn : n ≤ 1
· rw [balancedIntegerCost_base hn]
constructor
· exact hb
· have hsq : 1 ≤ ((n : ℝ)+1)^2 := by nlinarith [Nat.cast_nonneg (α := ℝ) n]
have hmul := mul_le_mul_of_nonneg_left hsq (show 0 ≤ 4*c+base by positivity)
nlinarith
· have hn' : 1 < n := by omega
have hq : n/4 < n := Nat.div_lt_self (by omega) (by norm_num)
obtain ⟨hl, hu⟩ := ih (n/4) hq
rw [balancedIntegerCost_step hn']
constructor
· positivity
· have hhalfNat : 2*(n/4+1) ≤ n+1 := by omega
have hhalf : 2*((n/4 : ℕ)+1 : ℝ) ≤ (n : ℝ)+1 := by exact_mod_cast hhalfNat
have hq0 : 0 ≤ ((n/4 : ℕ) : ℝ) := by positivity
have hn0 : 0 ≤ (n : ℝ) := by positivity
have hK0 : 0 ≤ 4*c+base := by positivity
have hsq : 4*(((n/4 : ℕ) : ℝ)+1)^2 ≤ ((n : ℝ)+1)^2 := by nlinarith
have hw := mul_le_mul_of_nonneg_left hsq hK0
have hlocal := mul_nonneg hc
(show 0 ≤ ((n : ℝ)+1)^2-(n : ℝ)^2 by nlinarith)
have hbase := mul_nonneg hb (sq_nonneg ((n : ℝ)+1))
nlinarith
The generated balanced rounded tree has Θ(n²) cost whenever its
quadratic local charge is positive and its leaf charge is nonnegative.
theorem balancedIntegerCost_isBigTheta {c base : ℝ} (hc : 0 < c) (hb : 0 ≤ base) :
Chapter03.isBigTheta (balancedIntegerCost c base) (fun n : ℕ => (n : ℝ)^2) := by
constructor
· apply (Chapter03.isBigO_iff _ _).mpr
refine ⟨4*(4*c+base), by positivity, 2, ?_⟩
intro n hn
obtain ⟨hl, hu⟩ := balancedIntegerCost_bounds hc.le hb n
rw [abs_of_nonneg hl, abs_of_nonneg (sq_nonneg _)]
have hn1 : 1 ≤ (n : ℝ) := by exact_mod_cast (show 1 ≤ n by omega)
have hsq : ((n : ℝ)+1)^2 ≤ 4*(n : ℝ)^2 := by nlinarith
have hw := mul_le_mul_of_nonneg_left hsq (show 0 ≤ 4*c+base by positivity)
nlinarith
· apply (Chapter03.isBigOmega_iff _ _).mpr
refine ⟨c, hc, 2, ?_⟩
intro n hn
rw [abs_of_nonneg (balancedIntegerCost_bounds hc.le hb n).1,
abs_of_nonneg (sq_nonneg _), balancedIntegerCost_step (by omega)]
have hchild := (balancedIntegerCost_bounds hc.le hb (n/4)).1
linarith
private theorem rounded_thirds_bounds (n : ℕ) (hn : 2 < n) :
n/3 + twoThirdsCeil n = n ∧ 0 < n/3 ∧ 0 < twoThirdsCeil n ∧
n ≤ 5*(n/3) ∧ n ≤ 5*twoThirdsCeil n ∧
5*(n/3) ≤ 4*n ∧ 5*twoThirdsCeil n ≤ 4*n := by
rw [twoThirdsCeil, Nat.ceilDiv_eq_add_pred_div]
by_cases hsmall : n < 6
· interval_cases n <;> norm_num at *
· omega
private theorem split_log_gap (n x y : ℝ) (hx : 0 < x) (hy : 0 < y)
(hs : x+y=n) (hlx : n ≤ 5*x) (hly : n ≤ 5*y)
(hux : 5*x ≤ 4*n) (huy : 5*y ≤ 4*n) :
n/5 ≤ n*Real.log n-x*Real.log x-y*Real.log y ∧
n*Real.log n-x*Real.log x-y*Real.log y ≤ 4*n := by
have hn : 0 < n := by linarith
have hlogLow : (1:ℝ)/5 ≤ Real.log ((5:ℝ)/4) := by
have h := Real.one_sub_inv_le_log_of_pos (x := (5:ℝ)/4) (by norm_num)
norm_num at h ⊢
exact h
have hlogHigh : Real.log (5:ℝ) ≤ 4 := by
have h := Real.log_le_sub_one_of_pos (x := (5:ℝ)) (by norm_num)
norm_num at h ⊢
exact h
have hlogs (z : ℝ) (hz : 0 < z) (hl : n ≤ 5*z) (hu : 5*z ≤ 4*n) :
(1:ℝ)/5 ≤ Real.log n-Real.log z ∧ Real.log n-Real.log z ≤ 4 := by
have hlo : (5:ℝ)/4 ≤ n/z := (le_div_iff₀ hz).mpr (by nlinarith)
have hhi : n/z ≤ 5 := (div_le_iff₀ hz).mpr hl
rw [← Real.log_div hn.ne' hz.ne']
exact ⟨hlogLow.trans (Real.log_le_log (by norm_num) hlo),
(Real.log_le_log (div_pos hn hz) hhi).trans hlogHigh⟩
have hxl := mul_le_mul_of_nonneg_left (hlogs x hx hlx hux).1 hx.le
have hxu := mul_le_mul_of_nonneg_left (hlogs x hx hlx hux).2 hx.le
have hyl := mul_le_mul_of_nonneg_left (hlogs y hy hly huy).1 hy.le
have hyu := mul_le_mul_of_nonneg_left (hlogs y hy hly huy).2 hy.le
have heq : n*Real.log n-x*Real.log x-y*Real.log y =
x*(Real.log n-Real.log x)+y*(Real.log n-Real.log y) := by
rw [← hs]
ring
rw [heq]
constructor <;> nlinarithExplicit affine logarithmic potentials for the actual unequal-depth tree. The linear terms absorb the base cases and cancel at each rounded split.
theorem unbalancedIntegerCost_bounds {c base : ℝ} (hc : 0 ≤ c) (hb : 0 ≤ base)
(n : ℕ) (hn : 1 ≤ n) :
0 ≤ unbalancedIntegerCost c base n ∧
(c/4)*(n : ℝ)*(Real.log (n : ℝ)-1) ≤ unbalancedIntegerCost c base n ∧
unbalancedIntegerCost c base n ≤ 5*c*(n : ℝ)*Real.log (n : ℝ)+base*(n : ℝ) := by
induction n using Nat.strong_induction_on with
| h n ih =>
by_cases hbase : n ≤ 2
· rw [unbalancedIntegerCost_base hbase]
have hlog2 : Real.log (2:ℝ) ≤ 1 := by
have h := Real.log_le_sub_one_of_pos (x := (2:ℝ)) (by norm_num)
norm_num at h ⊢
exact h
have hlog2pos : 0 ≤ Real.log (2:ℝ) := Real.log_nonneg (by norm_num)
interval_cases n <;> norm_num at *
· exact ⟨hb, by nlinarith⟩
· exact ⟨hb, by nlinarith [mul_nonneg hc (sub_nonneg.mpr hlog2)],
by nlinarith [mul_nonneg hc hlog2pos]⟩
· have hrec : 2 < n := by omega
obtain ⟨hs, hx, hy, hlx, hly, hux, huy⟩ := rounded_thirds_bounds n hrec
have hsR : ((n/3 : ℕ) : ℝ)+(twoThirdsCeil n : ℝ)=(n : ℝ) := by exact_mod_cast hs
have hgap := split_log_gap (n : ℝ) ((n/3 : ℕ) : ℝ) (twoThirdsCeil n : ℝ)
(by exact_mod_cast hx) (by exact_mod_cast hy) hsR
(by exact_mod_cast hlx) (by exact_mod_cast hly)
(by exact_mod_cast hux) (by exact_mod_cast huy)
obtain ⟨hx0, hxL, hxU⟩ := ih (n/3) (thirdFloor_lt_self hrec) (by omega)
obtain ⟨hy0, hyL, hyU⟩ := ih (twoThirdsCeil n) (twoThirdsCeil_lt_self hrec) (by omega)
rw [unbalancedIntegerCost_step hrec]
refine ⟨by positivity, ?_, ?_⟩
· have hw := mul_le_mul_of_nonneg_left hgap.2 (show 0 ≤ c/4 by positivity)
have hcancel : (c/4)*((n/3 : ℕ) : ℝ)+(c/4)*(twoThirdsCeil n : ℝ)=
(c/4)*(n : ℝ) := by rw [← mul_add, hsR]
nlinarith
· have hw := mul_le_mul_of_nonneg_left hgap.1 (show 0 ≤ 5*c by positivity)
have hcancel : base*((n/3 : ℕ) : ℝ)+base*(twoThirdsCeil n : ℝ)=
base*(n : ℝ) := by rw [← mul_add, hsR]
nlinarith
private theorem two_le_log_of_sixteen_le {n : ℕ} (hn : 16 ≤ n) :
2 ≤ Real.log (n : ℝ) := by
have hhalf := Real.one_sub_inv_le_log_of_pos (x := (2:ℝ)) (by norm_num)
have hm := Real.log_le_log (by norm_num : (0:ℝ)<16) (show (16:ℝ) ≤ n by exact_mod_cast hn)
have hpow : Real.log (16:ℝ) = 4*Real.log (2:ℝ) := by
rw [show (16:ℝ)=2^4 by norm_num, Real.log_pow]
norm_num
rw [hpow] at hm
norm_num at hhalf
linarith
The generated floor/ceiling unequal-depth tree has Θ(n log n) cost.
This follows from its recurrence and rounding arithmetic, without an assumed
asymptotic comparison or an Akra--Bazzi certificate.
theorem unbalancedIntegerCost_isBigTheta {c base : ℝ} (hc : 0 < c) (hb : 0 ≤ base) :
Chapter03.isBigTheta (unbalancedIntegerCost c base)
(fun n : ℕ => (n : ℝ)*Real.log (n : ℝ)) := by
constructor
· apply (Chapter03.isBigO_iff _ _).mpr
refine ⟨5*c+base, by positivity, 16, ?_⟩
intro n hn
obtain ⟨h0, _, hU⟩ := unbalancedIntegerCost_bounds hc.le hb n (by omega)
have hlog := two_le_log_of_sixteen_le hn
rw [abs_of_nonneg h0, abs_of_nonneg (by positivity)]
have hw := mul_nonneg (mul_nonneg hb (Nat.cast_nonneg n))
(show 0 ≤ Real.log (n : ℝ)-1 by linarith)
nlinarith
· apply (Chapter03.isBigOmega_iff _ _).mpr
refine ⟨c/8, by positivity, 16, ?_⟩
intro n hn
obtain ⟨h0, hL, _⟩ := unbalancedIntegerCost_bounds hc.le hb n (by omega)
have hlog := two_le_log_of_sixteen_le hn
rw [abs_of_nonneg h0, abs_of_nonneg (by positivity)]
have hw := mul_nonneg (mul_nonneg hc.le (Nat.cast_nonneg n))
(show 0 ≤ Real.log (n : ℝ)-2 by linarith)
nlinarithend Chapter04end CLRSCLRSLean.FourthEdition.Chapter_04.Section_04_4_Recursion_Tree_Method.Branching.IntegerTree.Balanced
The integer tree for 3T(n/4) + c n²
This is the rounded, arbitrary-input counterpart of the existing fixed-depth real-scale level calculation.
namespace CLRSnamespace Chapter04open Finsetopen scoped BigOperatorsThree equal floor-divided children, with base cases at sizes zero and one.
def balancedIntegerSpec (c base : Real) : IntegerBranchingSpec (Fin 3) where
cutoff := 1
childSize := fun _ n => n / 4
localCost := fun n => c * (n : Real) ^ 2
baseCost := fun _ => base
decreases := by
intro _ n hn
exact Nat.div_lt_self (by omega) (by norm_num)Independent textbook equations for the rounded balanced recurrence.
structure BalancedIntegerRecurrence (c base : Real) (T : Nat → Real) : Prop where
base_eq : ∀ n, n ≤ 1 → T n = base
step_eq : ∀ n, 1 < n →
T n = 3 * T (n / 4) + c * (n : Real) ^ 2The textbook equations instantiate the generic finite-tree semantics.
theorem BalancedIntegerRecurrence.satisfies {c base : Real} {T : Nat → Real}
(hT : BalancedIntegerRecurrence c base T) :
(balancedIntegerSpec c base).Satisfies T := by
constructor
· intro n hn
exact hT.base_eq n hn
· intro n hn
rw [hT.step_eq n hn]
simp [balancedIntegerSpec]
ringExact equality between the rounded recurrence and its explicit tree.
theorem balancedIntegerTree_totalCost_eq {c base : Real} {T : Nat → Real}
(hT : BalancedIntegerRecurrence c base T) (n : Nat) :
IntegerBranchingTree.totalCost ((balancedIntegerSpec c base).build n) = T n :=
IntegerBranchingSpec.build_totalCost_eq _ _ hT.satisfies nCost function computed by the generated balanced integer tree.
def balancedIntegerCost (c base : Real) (n : Nat) : Real :=
IntegerBranchingTree.totalCost ((balancedIntegerSpec c base).build n)@[simp] theorem balancedIntegerCost_base {c base : Real} {n : Nat}
(hn : n ≤ 1) : balancedIntegerCost c base n = base := by
simp [balancedIntegerCost, balancedIntegerSpec, hn]
theorem balancedIntegerCost_step {c base : Real} {n : Nat}
(hn : 1 < n) :
balancedIntegerCost c base n =
3 * balancedIntegerCost c base (n / 4) + c * (n : Real) ^ 2 := by
rw [balancedIntegerCost, IntegerBranchingSpec.build_of_lt _ _ hn]
simp only [IntegerBranchingTree.totalCost_node]
simp [balancedIntegerSpec, balancedIntegerCost]
ringtheorem balancedIntegerCost_satisfies (c base : Real) :
BalancedIntegerRecurrence c base (balancedIntegerCost c base) := by
constructor
· intro n hn
exact balancedIntegerCost_base hn
· intro n hn
exact balancedIntegerCost_step hnForcing term that makes the generated tree cost an all-input floor recurrence, including the explicitly represented base cases.
def balancedIntegerForcing (c base : Real) (n : Nat) : Real :=
balancedIntegerCost c base n - 3 * balancedIntegerCost c base (n / 4)Direct connection from the explicit tree to the all-input §4.6 interface.
theorem balancedIntegerCost_floorRecurrence (c base : Real) :
FloorDivideRecurrence 3 4 (balancedIntegerForcing c base)
(balancedIntegerCost c base) := by
constructor
intro n
simp [balancedIntegerForcing]
Above the cutoff, the all-input forcing is exactly the textbook c n².
theorem balancedIntegerForcing_of_lt {c base : Real} {n : Nat} (hn : 1 < n) :
balancedIntegerForcing c base n = c * (n : Real) ^ 2 := by
rw [balancedIntegerForcing, balancedIntegerCost_step hn]
ringend Chapter04end CLRSCLRSLean.FourthEdition.Chapter_04.Section_04_4_Recursion_Tree_Method.Branching.IntegerTree.Execution
Building integer branching trees
A specification contains the termination proof for every rounded child. The recurrence equation is stated independently, then strong induction identifies its solution with the cost of the generated finite tree.
namespace CLRSnamespace Chapter04open Finsetopen scoped BigOperatorsData needed to expand a natural-size recurrence into a finite tree.
structure IntegerBranchingSpec (Branch : Type) where
cutoff : Nat
childSize : Branch → Nat → Nat
localCost : Nat → Real
baseCost : Nat → Real
decreases : ∀ branch n, cutoff < n → childSize branch n < nnamespace IntegerBranchingSpecvariable {Branch : Type}The recurrence semantics, stated without referring to the generated tree.
structure Satisfies [Fintype Branch] (spec : IntegerBranchingSpec Branch)
(T : Nat → Real) : Prop where
base : ∀ n, n ≤ spec.cutoff → T n = spec.baseCost n
step : ∀ n, spec.cutoff < n →
T n = spec.localCost n + ∑ branch, T (spec.childSize branch n)Executably expand all children until their independently certified cutoff.
def build (spec : IntegerBranchingSpec Branch) (n : Nat) :
IntegerBranchingTree Branch :=
if _h : n ≤ spec.cutoff then
.leaf n (spec.baseCost n)
else
.node n (spec.localCost n) (fun branch => spec.build (spec.childSize branch n))
termination_by n
decreasing_by
exact spec.decreases _ _ (Nat.lt_of_not_ge _h)
@[simp] theorem build_of_le (spec : IntegerBranchingSpec Branch) (n : Nat)
(h : n ≤ spec.cutoff) :
spec.build n = .leaf n (spec.baseCost n) := by
rw [build]
simp [h]
@[simp] theorem build_of_lt (spec : IntegerBranchingSpec Branch) (n : Nat)
(h : spec.cutoff < n) :
spec.build n = .node n (spec.localCost n)
(fun branch => spec.build (spec.childSize branch n)) := by
rw [build]
simp [Nat.not_le_of_gt h]Building preserves the requested root subproblem size.
@[simp] theorem rootSize_build (spec : IntegerBranchingSpec Branch) (n : Nat) :
IntegerBranchingTree.rootSize (spec.build n) = n := by
by_cases h : n ≤ spec.cutoff
· simp [build_of_le spec n h]
· simp [build_of_lt spec n (Nat.lt_of_not_ge h)]Predicate saying that every leaf size satisfies a given property.
def EveryLeafSize (P : Nat → Prop) : IntegerBranchingTree Branch → Prop
| .leaf size _ => P size
| .node _ _ children => ∀ branch, EveryLeafSize P (children branch)Predicate saying that every internal-node size satisfies a property.
def EveryInternalSize (P : Nat → Prop) : IntegerBranchingTree Branch → Prop
| .leaf _ _ => True
| .node size _ children => P size ∧ ∀ branch, EveryInternalSize P (children branch)Every generated leaf is genuinely in the base-case range.
theorem build_everyLeaf_le (spec : IntegerBranchingSpec Branch) (n : Nat) :
EveryLeafSize (fun size => size ≤ spec.cutoff) (spec.build n) := by
induction n using Nat.strong_induction_on with
| h n ih =>
by_cases hbase : n ≤ spec.cutoff
· simp [EveryLeafSize, hbase]
· have hrec : spec.cutoff < n := Nat.lt_of_not_ge hbase
rw [build_of_lt spec n hrec]
simp only [EveryLeafSize]
intro branch
exact ih (spec.childSize branch n) (spec.decreases branch n hrec)Every generated internal node is genuinely above the cutoff.
theorem build_everyInternal_gt (spec : IntegerBranchingSpec Branch) (n : Nat) :
EveryInternalSize (fun size => spec.cutoff < size) (spec.build n) := by
induction n using Nat.strong_induction_on with
| h n ih =>
by_cases hbase : n ≤ spec.cutoff
· simp [build_of_le spec n hbase, EveryInternalSize]
· have hrec : spec.cutoff < n := Nat.lt_of_not_ge hbase
rw [build_of_lt spec n hrec]
simp only [EveryInternalSize]
refine ⟨hrec, fun branch => ?_⟩
exact ih (spec.childSize branch n) (spec.decreases branch n hrec)
Exact recurrence-tree semantics. This is not true by definition: T is
specified only by its base and recursive equations, independently of build.
theorem build_totalCost_eq [Fintype Branch] (spec : IntegerBranchingSpec Branch)
(T : Nat → Real) (hT : spec.Satisfies T) (n : Nat) :
IntegerBranchingTree.totalCost (spec.build n) = T n := by
induction n using Nat.strong_induction_on with
| h n ih =>
by_cases hbase : n ≤ spec.cutoff
· rw [build_of_le spec n hbase]
simp [hT.base n hbase]
· have hrec : spec.cutoff < n := Nat.lt_of_not_ge hbase
rw [build_of_lt spec n hrec]
simp only [IntegerBranchingTree.totalCost_node]
have hchildren : ∀ branch,
IntegerBranchingTree.totalCost
(spec.build (spec.childSize branch n)) =
T (spec.childSize branch n) := fun branch =>
ih (spec.childSize branch n) (spec.decreases branch n hrec)
simp_rw [hchildren]
exact (hT.step n hrec).symmend IntegerBranchingSpecend Chapter04end CLRSCLRSLean.FourthEdition.Chapter_04.Section_04_4_Recursion_Tree_Method.Branching.IntegerTree.Model
Unequal-depth integer branching trees
This datatype records the actual natural-number size and work of every node. It is intentionally not indexed by a common depth: floor and ceiling branches may reach the base case at different times.
namespace CLRSnamespace Chapter04open Finsetopen scoped BigOperatorsA finite recursion tree whose branches need not have the same height.
inductive IntegerBranchingTree (Branch : Type)
| leaf (size : Nat) (work : Real)
| node (size : Nat) (work : Real)
(children : Branch → IntegerBranchingTree Branch)namespace IntegerBranchingTreevariable {Branch : Type}Natural-number subproblem size stored at the root.
def rootSize : IntegerBranchingTree Branch → Nat
| .leaf size _ => size
| .node size _ _ => sizeWork stored at the root, whether it is a base or recursive node.
def rootWork : IntegerBranchingTree Branch → Real
| .leaf _ work => work
| .node _ work _ => workWork stored in every node of the finite tree.
def totalCost [Fintype Branch] : IntegerBranchingTree Branch → Real
| .leaf _ work => work
| .node _ work children => work + ∑ branch, totalCost (children branch)Maximum number of recursive edges on a root-to-leaf path.
def height [Fintype Branch] [DecidableEq Branch] :
IntegerBranchingTree Branch → Nat
| .leaf _ _ => 0
| .node _ _ children => 1 + Finset.univ.sup (fun branch => height (children branch))@[simp] theorem rootSize_leaf (size : Nat) (work : Real) :
rootSize (.leaf size work : IntegerBranchingTree Branch) = size := rfl@[simp] theorem rootSize_node (size : Nat) (work : Real)
(children : Branch → IntegerBranchingTree Branch) :
rootSize (.node size work children) = size := rfl@[simp] theorem rootWork_leaf (size : Nat) (work : Real) :
rootWork (.leaf size work : IntegerBranchingTree Branch) = work := rfl@[simp] theorem rootWork_node (size : Nat) (work : Real)
(children : Branch → IntegerBranchingTree Branch) :
rootWork (.node size work children) = work := rfl@[simp] theorem totalCost_leaf [Fintype Branch] (size : Nat) (work : Real) :
totalCost (.leaf size work : IntegerBranchingTree Branch) = work := rfl@[simp] theorem totalCost_node [Fintype Branch] (size : Nat) (work : Real)
(children : Branch → IntegerBranchingTree Branch) :
totalCost (.node size work children) =
work + ∑ branch, totalCost (children branch) := rfl@[simp] theorem height_leaf [Fintype Branch] [DecidableEq Branch]
(size : Nat) (work : Real) :
height (.leaf size work : IntegerBranchingTree Branch) = 0 := rfl@[simp] theorem height_node [Fintype Branch] [DecidableEq Branch]
(size : Nat) (work : Real)
(children : Branch → IntegerBranchingTree Branch) :
height (.node size work children) =
1 + Finset.univ.sup (fun branch => height (children branch)) := rflend IntegerBranchingTreeend Chapter04end CLRSCLRSLean.FourthEdition.Chapter_04.Section_04_4_Recursion_Tree_Method.Branching.IntegerTree.Unbalanced
The integer tree for T(n/3) + T(2n/3) + c n
The larger child uses natural ceiling division. The generated tree therefore has no artificial common depth.
namespace CLRSnamespace Chapter04open Finsetopen scoped BigOperators
The textbook rounded size ceil(2n/3).
def twoThirdsCeil (n : Nat) : Nat :=
(2 * n) ⌈/⌉ 3The smaller floor branch decreases above the chosen cutoff.
theorem thirdFloor_lt_self {n : Nat} (hn : 2 < n) : n / 3 < n := by
exact Nat.div_lt_self (by omega) (by norm_num)The larger ceiling branch also decreases above the chosen cutoff.
theorem twoThirdsCeil_lt_self {n : Nat} (hn : 2 < n) : twoThirdsCeil n < n := by
rw [twoThirdsCeil, Nat.ceilDiv_eq_add_pred_div]
omegaFloor and ceiling differ by at most one for the two-thirds child.
theorem twoThirds_floor_ceil_sandwich (n : Nat) :
(2 * n) / 3 ≤ twoThirdsCeil n ∧
twoThirdsCeil n ≤ (2 * n) / 3 + 1 := by
constructor
· rw [twoThirdsCeil, Nat.ceilDiv_eq_add_pred_div]
omega
· rw [twoThirdsCeil, Nat.ceilDiv_eq_add_pred_div]
omegaTwo differently rounded children, with base cases through size two.
def unbalancedIntegerSpec (c base : Real) : IntegerBranchingSpec Bool where
cutoff := 2
childSize := fun branch n => if branch then twoThirdsCeil n else n / 3
localCost := fun n => c * (n : Real)
baseCost := fun _ => base
decreases := by
intro branch n hn
cases branch with
| false =>
simpa using thirdFloor_lt_self hn
| true =>
simpa using twoThirdsCeil_lt_self hnIndependent textbook equations for the rounded unbalanced recurrence.
structure UnbalancedIntegerRecurrence (c base : Real) (T : Nat → Real) : Prop where
base_eq : ∀ n, n ≤ 2 → T n = base
step_eq : ∀ n, 2 < n →
T n = T (n / 3) + T (twoThirdsCeil n) + c * (n : Real)The textbook equations instantiate the generic finite-tree semantics.
theorem UnbalancedIntegerRecurrence.satisfies {c base : Real} {T : Nat → Real}
(hT : UnbalancedIntegerRecurrence c base T) :
(unbalancedIntegerSpec c base).Satisfies T := by
constructor
· intro n hn
exact hT.base_eq n hn
· intro n hn
rw [hT.step_eq n hn]
simp [unbalancedIntegerSpec]
ringExact equality between the rounded recurrence and its explicit tree.
theorem unbalancedIntegerTree_totalCost_eq {c base : Real} {T : Nat → Real}
(hT : UnbalancedIntegerRecurrence c base T) (n : Nat) :
IntegerBranchingTree.totalCost ((unbalancedIntegerSpec c base).build n) = T n :=
IntegerBranchingSpec.build_totalCost_eq _ _ hT.satisfies nCost function computed by the generated unbalanced integer tree.
def unbalancedIntegerCost (c base : Real) (n : Nat) : Real :=
IntegerBranchingTree.totalCost ((unbalancedIntegerSpec c base).build n)@[simp] theorem unbalancedIntegerCost_base {c base : Real} {n : Nat}
(hn : n ≤ 2) : unbalancedIntegerCost c base n = base := by
simp [unbalancedIntegerCost, unbalancedIntegerSpec, hn]
theorem unbalancedIntegerCost_step {c base : Real} {n : Nat}
(hn : 2 < n) :
unbalancedIntegerCost c base n =
unbalancedIntegerCost c base (n / 3) +
unbalancedIntegerCost c base (twoThirdsCeil n) + c * (n : Real) := by
rw [unbalancedIntegerCost, IntegerBranchingSpec.build_of_lt _ _ hn]
simp only [IntegerBranchingTree.totalCost_node]
simp [unbalancedIntegerSpec, unbalancedIntegerCost]
ringtheorem unbalancedIntegerCost_satisfies (c base : Real) :
UnbalancedIntegerRecurrence c base (unbalancedIntegerCost c base) := by
constructor
· intro n hn
exact unbalancedIntegerCost_base hn
· intro n hn
exact unbalancedIntegerCost_step hn
At input four, the floor(4/3) child is already a leaf while the
ceil(8/3) child expands once more. This witnesses genuinely unequal depth.
theorem unbalancedIntegerTree_has_unequal_depth :
IntegerBranchingTree.height
((unbalancedIntegerSpec 1 1).build
((unbalancedIntegerSpec 1 1).childSize false 4)) ≠
IntegerBranchingTree.height
((unbalancedIntegerSpec 1 1).build
((unbalancedIntegerSpec 1 1).childSize true 4)) := by
have hleft : (unbalancedIntegerSpec 1 1).childSize false 4 = 1 := by
norm_num [unbalancedIntegerSpec]
have hright : (unbalancedIntegerSpec 1 1).childSize true 4 = 3 := by
norm_num [unbalancedIntegerSpec, twoThirdsCeil, Nat.ceilDiv_eq_add_pred_div]
rw [hleft, hright]
have hheightOne : IntegerBranchingTree.height
((unbalancedIntegerSpec 1 1).build 1) = 0 := by
rw [IntegerBranchingSpec.build_of_le _ _ (by norm_num [unbalancedIntegerSpec])]
rfl
have hheightThree : IntegerBranchingTree.height
((unbalancedIntegerSpec 1 1).build 3) = 1 := by
rw [IntegerBranchingSpec.build_of_lt _ _ (by norm_num [unbalancedIntegerSpec])]
simp [IntegerBranchingTree.height, unbalancedIntegerSpec, twoThirdsCeil]
omegaThe limiting one-third and two-thirds branch weights sum to one.
theorem unbalancedInteger_characteristic_one :
(1 : Real) / 3 + 2 / 3 = 1 := by
norm_numend Chapter04end CLRSCLRSLean.FourthEdition.Chapter_04.Section_04_4_Recursion_Tree_Method.Branching.LevelSums
Reusable branching level sums
This file builds a full recursion tree from per-branch work-scaling ratios and proves its exact per-level cost. It also packages the convergent geometric sum bound used by the balanced textbook example.
namespace CLRSnamespace Chapter04open Finsetopen scoped BigOperatorsopen BranchingRecursionTreevariable {Branch : Type} [Fintype Branch]
A homogeneous branching expansion. A child on branch branch receives
ratios branch times its parent's local work. Leaves have a common cutoff
cost; this parameter is intentionally separate from the internal work scale.
def scaledBranchingTree (ratios : Branch -> Real) (rootWork leafWork : Real) :
(depth : Nat) -> BranchingRecursionTree Branch depth
| 0 => .leaf leafWork
| depth + 1 => .node rootWork (fun branch =>
scaledBranchingTree ratios (rootWork * ratios branch) leafWork depth)
At level k, the total work is the root work times the kth power of the
sum of the per-branch work ratios.
theorem scaledBranchingTree_levelCost (ratios : Branch -> Real)
(rootWork leafWork : Real) {depth : Nat} (level : Fin depth) :
levelCost (scaledBranchingTree ratios rootWork leafWork depth) level =
rootWork * (∑ branch, ratios branch) ^ level.val := by
induction depth generalizing rootWork with
| zero => exact Fin.elim0 level
| succ depth ih =>
refine Fin.cases ?_ (fun childLevel => ?_) level
· simp [scaledBranchingTree, levelCost]
· simp only [scaledBranchingTree, levelCost, Fin.cases_succ]
simp_rw [ih]
rw [← Finset.sum_mul, ← Finset.mul_sum]
simp only [Fin.val_succ, pow_succ]
ring
A full depth-d expansion has card Branch ^ d leaves.
theorem scaledBranchingTree_leafCost (ratios : Branch -> Real)
(rootWork leafWork : Real) (depth : Nat) :
leafCost (scaledBranchingTree ratios rootWork leafWork depth) =
(Fintype.card Branch : Real) ^ depth * leafWork := by
induction depth generalizing rootWork with
| zero => simp [scaledBranchingTree, leafCost]
| succ depth ih =>
simp [scaledBranchingTree, leafCost, ih, Finset.sum_const, nsmul_eq_mul,
pow_succ]
ringExact closed form of the full branching expansion through a fixed depth.
theorem scaledBranchingTree_totalCost (ratios : Branch -> Real)
(rootWork leafWork : Real) (depth : Nat) :
totalCost (scaledBranchingTree ratios rootWork leafWork depth) =
(∑ level : Fin depth,
rootWork * (∑ branch, ratios branch) ^ level.val) +
(Fintype.card Branch : Real) ^ depth * leafWork := by
rw [totalCost_eq_levelCosts_add_leafCost]
simp_rw [scaledBranchingTree_levelCost]
rw [scaledBranchingTree_leafCost]A reusable finite geometric level-sum bound.
theorem geometricLevelSum_le (base ratio : Real) (hbase : 0 <= base)
(hratio_nonneg : 0 <= ratio) (hratio_lt_one : ratio < 1) (depth : Nat) :
(∑ level ∈ Finset.range depth, base * ratio ^ level) <=
base / (1 - ratio) := by
have hsumm : Summable fun level : Nat => ratio ^ level :=
summable_geometric_of_lt_one hratio_nonneg hratio_lt_one
have hpartial : (∑ level ∈ Finset.range depth, ratio ^ level) <=
(1 - ratio)⁻¹ := by
calc
(∑ level ∈ Finset.range depth, ratio ^ level) <= ∑' level : Nat, ratio ^ level :=
hsumm.sum_le_tsum (Finset.range depth)
(fun level _ => pow_nonneg hratio_nonneg level)
_ = (1 - ratio)⁻¹ :=
tsum_geometric_of_lt_one hratio_nonneg hratio_lt_one
calc
(∑ level ∈ Finset.range depth, base * ratio ^ level) =
base * ∑ level ∈ Finset.range depth, ratio ^ level := by
rw [Finset.mul_sum]
_ <= base * (1 - ratio)⁻¹ := mul_le_mul_of_nonneg_left hpartial hbase
_ = base / (1 - ratio) := by rw [div_eq_mul_inv]end Chapter04end CLRSCLRSLean.FourthEdition.Chapter_04.Section_04_4_Recursion_Tree_Method.Branching.Model
Full branching recursion trees
BranchingRecursionTree Branch depth is an explicit, finite expansion of a
branching recurrence through exactly depth internal levels. Every internal
node has one child for each value of the finite type Branch; all leaves lie
at the same cutoff depth. This is the exact-power / fixed-depth model used in
the textbook level-cost calculation. Floor and ceiling transfer for arbitrary
input sizes is deliberately a separate concern.
namespace CLRSnamespace Chapter04open Finsetopen scoped BigOperators
A full finite branching recursion tree with depth internal levels.
inductive BranchingRecursionTree (Branch : Type) : Nat -> Type
| leaf (cost : Real) : BranchingRecursionTree Branch 0
| node {depth : Nat} (work : Real)
(children : Branch -> BranchingRecursionTree Branch depth) :
BranchingRecursionTree Branch (depth + 1)namespace BranchingRecursionTreevariable {Branch : Type} [Fintype Branch]Total work stored in all internal nodes and leaves.
def totalCost : {depth : Nat} -> BranchingRecursionTree Branch depth -> Real
| 0, .leaf cost => cost
| _ + 1, .node work children => work + ∑ branch, totalCost (children branch)Total contribution of the leaves at the cutoff depth.
def leafCost : {depth : Nat} -> BranchingRecursionTree Branch depth -> Real
| 0, .leaf cost => cost
| _ + 1, .node _ children => ∑ branch, leafCost (children branch)Work contributed by the internal nodes at one depth.
def levelCost : {depth : Nat} -> (tree : BranchingRecursionTree Branch depth) ->
Fin depth -> Real
| 0, .leaf _, level => Fin.elim0 level
| _ + 1, .node work children, level =>
Fin.cases work (fun childLevel =>
∑ branch, levelCost (children branch) childLevel) levelExact recursion-tree decomposition: total cost is the sum of all internal level costs plus the cost of the leaves at the common cutoff depth.
theorem totalCost_eq_levelCosts_add_leafCost {depth : Nat}
(tree : BranchingRecursionTree Branch depth) :
totalCost tree = (∑ level : Fin depth, levelCost tree level) + leafCost tree := by
induction tree with
| leaf cost => simp [totalCost, leafCost]
| @node depth work children ih =>
simp only [totalCost, leafCost]
rw [Fin.sum_univ_succ]
simp only [levelCost, Fin.cases_zero, Fin.cases_succ]
simp_rw [ih]
rw [Finset.sum_add_distrib]
rw [Finset.sum_comm]
ringend BranchingRecursionTreeend Chapter04end CLRSCLRSLean.FourthEdition.Chapter_04.Section_04_4_Recursion_Tree_Method.Branching.TextbookExamples
Textbook branching-recursion-tree examples
The theorems below are exact fixed-depth expansions over real-valued problem scales. They formalize the level-cost calculations from CLRS §4.4 while keeping the assumptions visible:
-
no floor or ceiling is taken at a child size;
-
all branches are expanded through a common cutoff depth;
-
leafWorkis the common cost assigned at that cutoff.
Consequently these results are the recursion-tree algebra for the original branch ratios, not an arbitrary-input termination theorem. A transfer from these exact scales to rounded natural-number recurrences needs separate monotonicity and floor/ceiling bounds.
namespace CLRSnamespace Chapter04open Finsetopen scoped BigOperatorsopen BranchingRecursionTree
The balanced example 3T(n/4) + c n^2
The fixed-depth recursion tree for 3T(n/4) + c n^2. Quadratic local
work scales by 1/16 on each of the three child branches.
noncomputable def balancedThreeQuarterTree (c n leafWork : Real) (depth : Nat) :
BranchingRecursionTree (Fin 3) depth :=
scaledBranchingTree (fun _ : Fin 3 => (1 : Real) / 16) (c * n ^ 2) leafWork depth
At level k, the balanced example costs exactly
c n^2 (3/16)^k.
theorem balancedThreeQuarter_levelCost (c n leafWork : Real) {depth : Nat}
(level : Fin depth) :
levelCost (balancedThreeQuarterTree c n leafWork depth) level =
c * n ^ 2 * ((3 : Real) / 16) ^ level.val := by
rw [balancedThreeQuarterTree, scaledBranchingTree_levelCost]
norm_numExact internal-level plus leaf decomposition for the balanced example.
theorem balancedThreeQuarter_totalCost_eq (c n leafWork : Real) (depth : Nat) :
totalCost (balancedThreeQuarterTree c n leafWork depth) =
(∑ level : Fin depth,
c * n ^ 2 * ((3 : Real) / 16) ^ level.val) +
(3 : Real) ^ depth * leafWork := by
rw [balancedThreeQuarterTree, scaledBranchingTree_totalCost]
norm_num
The internal work of 3T(n/4) + c n^2 is bounded by the convergent
geometric sum 16/13 * c n^2; the leaf contribution remains explicit.
theorem balancedThreeQuarter_totalCost_le (c n leafWork : Real)
(hc : 0 <= c) (depth : Nat) :
totalCost (balancedThreeQuarterTree c n leafWork depth) <=
((16 : Real) / 13) * (c * n ^ 2) + (3 : Real) ^ depth * leafWork := by
have hbase : 0 <= c * n ^ 2 := mul_nonneg hc (sq_nonneg n)
have hlevels := geometricLevelSum_le (c * n ^ 2) ((3 : Real) / 16)
hbase (by norm_num) (by norm_num) depth
have hlevelsFin :
(∑ level : Fin depth, c * n ^ 2 * ((3 : Real) / 16) ^ level.val) <=
(c * n ^ 2) / (1 - (3 : Real) / 16) := by
rw [Fin.sum_univ_eq_sum_range
(fun level : Nat => c * n ^ 2 * ((3 : Real) / 16) ^ level) depth]
exact hlevels
rw [balancedThreeQuarter_totalCost_eq]
calc
(∑ level : Fin depth, c * n ^ 2 * ((3 : Real) / 16) ^ level.val) +
(3 : Real) ^ depth * leafWork <=
(c * n ^ 2) / (1 - (3 : Real) / 16) +
(3 : Real) ^ depth * leafWork := add_le_add hlevelsFin le_rfl
_ = ((16 : Real) / 13) * (c * n ^ 2) +
(3 : Real) ^ depth * leafWork := by ring
The unbalanced example T(n/3) + T(2n/3) + c n
The two exact child-work ratios for the linear-work unbalanced recurrence.
noncomputable def thirdTwoThirdRatio : Bool -> Real
| false => (1 : Real) / 3
| true => (2 : Real) / 3
A common-depth expansion of T(n/3) + T(2n/3) + c n.
The two child trees retain their different 1/3 and 2/3 scales; they are not
replaced by a balanced recurrence.
noncomputable def unbalancedThirdTwoThirdTree (c n leafWork : Real) (depth : Nat) :
BranchingRecursionTree Bool depth :=
scaledBranchingTree thirdTwoThirdRatio (c * n) leafWork depth
Since 1/3 + 2/3 = 1, every internal level in the common-depth expansion
has exactly the root's linear work c n.
theorem unbalancedThirdTwoThird_levelCost (c n leafWork : Real) {depth : Nat}
(level : Fin depth) :
levelCost (unbalancedThirdTwoThirdTree c n leafWork depth) level = c * n := by
rw [unbalancedThirdTwoThirdTree, scaledBranchingTree_levelCost]
have hratio : (∑ branch : Bool, thirdTwoThirdRatio branch) = (1 : Real) := by
norm_num [thirdTwoThirdRatio]
rw [hratio, one_pow, mul_one]
Exact total cost through a common cutoff depth: depth * c n internal
work plus one leafWork contribution for each of the 2^depth leaves.
This theorem is deliberately not advertised as an arbitrary-size solution:
the actual 1/3 and 2/3 branches reach a natural-number base threshold at
different depths after rounding.
theorem unbalancedThirdTwoThird_totalCost (c n leafWork : Real) (depth : Nat) :
totalCost (unbalancedThirdTwoThirdTree c n leafWork depth) =
(depth : Real) * (c * n) + (2 : Real) ^ depth * leafWork := by
rw [unbalancedThirdTwoThirdTree, scaledBranchingTree_totalCost]
have hratio : (∑ branch : Bool, thirdTwoThirdRatio branch) = (1 : Real) := by
norm_num [thirdTwoThirdRatio]
rw [hratio]
simp [nsmul_eq_mul]end Chapter04end CLRS