CLRS GREEDY-ACTIVITY-SELECTOR
This module gives the one-pass version of the activity-selection algorithm.
greedyScan lastFinish xs carries the finish time of the most recently chosen
activity and inspects every remaining activity exactly once.
namespace CLRS.ActivitySelectionScan the finish-sorted candidates once, retaining compatible activities.
def greedyScan (lastFinish : Nat) : List Activity → List Activity
| [] => []
| a :: rest =>
if lastFinish ≤ a.start then
a :: greedyScan a.finish rest
else
greedyScan lastFinish restThe iterative textbook selector: choose the first activity, then scan.
def greedySelectIterative : List Activity → List Activity
| [] => []
| a :: rest => a :: greedyScan a.finish rest
private theorem filter_after_filter_eq
(threshold : Nat) (a : Activity) (rest : List Activity)
(hthreshold : threshold ≤ a.start) (hvalid : TextbookValid a) :
activitiesAfter a
(rest.filter fun b => decide (threshold ≤ b.start)) =
rest.filter fun b => decide (a.finish ≤ b.start) := by
rw [activitiesAfter, List.filter_filter]
congr 1
funext b
by_cases hab : a.finish ≤ b.start
· have hthresholdFinish : threshold ≤ a.finish :=
Nat.le_trans hthreshold (Nat.le_of_lt hvalid)
have hthresholdB : threshold ≤ b.start :=
Nat.le_trans hthresholdFinish hab
simp [hab, hthresholdB]
· simp [hab]
private theorem greedyScan_eq_filtered_recursive
(threshold : Nat) (xs : List Activity)
(hsorted : FinishSorted xs) (hvalid : TextbookInput xs) :
greedyScan threshold xs =
greedySelect (xs.filter fun a => decide (threshold ≤ a.start)) := by
induction xs generalizing threshold with
| nil => simp [greedyScan, greedySelect]
| cons a rest ih =>
have hsortedParts := List.pairwise_cons.mp hsorted
have hvalidParts := textbookInput_cons.mp hvalid
by_cases hselect : threshold ≤ a.start
· rw [greedyScan]
simp only [hselect, if_pos]
rw [show (a :: rest).filter (fun b => decide (threshold ≤ b.start)) =
a :: rest.filter (fun b => decide (threshold ≤ b.start)) by
simp [hselect]]
rw [greedySelect_cons_eq]
rw [filter_after_filter_eq threshold a rest hselect hvalidParts.1]
exact congrArg (a :: ·)
(ih a.finish hsortedParts.2 hvalidParts.2)
· rw [greedyScan]
simp only [hselect]
rw [show (a :: rest).filter (fun b => decide (threshold ≤ b.start)) =
rest.filter (fun b => decide (threshold ≤ b.start)) by
simp [hselect]]
exact ih threshold hsortedParts.2 hvalidParts.2The one-pass and recursive textbook selectors return the same activities on finish-sorted, textbook-valid inputs.
theorem greedySelectIterative_eq_greedySelect {xs : List Activity}
(hsorted : FinishSorted xs) (hvalid : TextbookInput xs) :
greedySelectIterative xs = greedySelect xs := by
cases xs with
| nil => simp [greedySelectIterative, greedySelect]
| cons a rest =>
have hsortedParts := List.pairwise_cons.mp hsorted
have hvalidParts := textbookInput_cons.mp hvalid
rw [greedySelectIterative, greedySelect_cons_eq]
apply congrArg (a :: ·)
simpa [activitiesAfter] using
(greedyScan_eq_filtered_recursive a.finish rest
hsortedParts.2 hvalidParts.2)The iterative selector inherits the complete maximum-cardinality theorem.
theorem greedySelectIterative_maxCardinality {xs : List Activity}
(hsorted : FinishSorted xs) (hvalid : TextbookInput xs) :
MaxCardinality xs (greedySelectIterative xs) := by
rw [greedySelectIterative_eq_greedySelect hsorted hvalid]
exact greedySelect_maxCardinality hsortedExact scan cost
Result and number of inspected candidates for greedyScan.
def greedyScanCost (lastFinish : Nat) : List Activity → List Activity × Nat
| [] => ([], 0)
| a :: rest =>
let tail := greedyScanCost (if lastFinish ≤ a.start then a.finish else lastFinish) rest
(if lastFinish ≤ a.start then a :: tail.1 else tail.1, tail.2 + 1)theorem greedyScanCost_result (lastFinish : Nat) (xs : List Activity) :
(greedyScanCost lastFinish xs).1 = greedyScan lastFinish xs := by
induction xs generalizing lastFinish with
| nil => rfl
| cons a rest ih =>
simp only [greedyScanCost, greedyScan]
by_cases h : lastFinish ≤ a.start
· simp [h, ih]
· simp [h, ih]theorem greedyScanCost_steps (lastFinish : Nat) (xs : List Activity) :
(greedyScanCost lastFinish xs).2 = xs.length := by
induction xs generalizing lastFinish with
| nil => rfl
| cons a rest ih =>
simp only [greedyScanCost]
by_cases h : lastFinish ≤ a.start <;> simp [h, ih]Result and exact inspection count for the complete iterative selector.
def greedySelectIterativeCost : List Activity → List Activity × Nat
| [] => ([], 0)
| a :: rest =>
let tail := greedyScanCost a.finish rest
(a :: tail.1, tail.2 + 1)theorem greedySelectIterativeCost_result (xs : List Activity) :
(greedySelectIterativeCost xs).1 = greedySelectIterative xs := by
cases xs with
| nil => rfl
| cons a rest =>
simp [greedySelectIterativeCost, greedySelectIterative,
greedyScanCost_result]
The iterative algorithm inspects exactly xs.length activities. This exact
cost identity is the formal Θ(n) statement for the unit-cost scan model.
theorem greedySelectIterativeCost_steps (xs : List Activity) :
(greedySelectIterativeCost xs).2 = xs.length := by
cases xs with
| nil => rfl
| cons a rest =>
simp [greedySelectIterativeCost, greedyScanCost_steps]end CLRS.ActivitySelection