lean
import SFLean4.VFA.Perm
import SFLean4.VFA.Sort
namespace VFA.Merge
open VFA.Perm
open VFA.Sort《软件基础》(Software Foundations) 卷三:函数式算法验证 (VFA)
第七章:归并排序及其规范与正确性证明 (Merge: Merge Sort, With Specification and Proof of Correctness)
归并排序(Mergesort)是一种经典的分治排序算法。其基本思想是:
- 划分 (Split):将待排序列划分为大小大致相等的左右两个子序列;
- 递归求解 (Recurse):对左右两个子序列分别递归进行归并排序;
- 合并 (Merge):将已排序的两个子序列合并为一个完整且有序的序列。
在数组等命令式结构中,归并排序具有
在本章中,我们将形式化定义列表上的归并排序,深入探讨“双步归纳原理 (Two-step Induction Principle)”, 并完整形式化验证归并排序的保序性与置换性。
1. 列表划分及其性质 (Split and its Properties)
lean
/-- 将列表元素交替分配到两个子列表中 -/
def split {X : Type} (l : List X) : List X × List X :=
match l with
| [] => ([], [])
| [x] => ([x], [])
| x1 :: x2 :: l' =>
let (l1, l2) := split l'
(x1 :: l1, x2 :: l2)1.1 双步归纳原理 (Two-Step Induction Principle)
普通列表的标准归纳原理假设性质对 tail 成立以推导整个非空列表。 但在处理 split 时,递归步跨越了两个元素(x1 :: x2 :: l'), 因此需要一个对跨步为 2 的列表生效的双步归纳原理。
lean
/-- 双步列表归纳原理的命题陈述 -/
def list_ind2_principle : Prop :=
∀ (A : Type) (P : List A → Prop),
P [] →
(∀ a, P [a]) →
(∀ a b l, P l → P (a :: b :: l)) →
∀ l, P l
/-- 双步归纳原理的具体构造实现 -/
theorem list_ind2 {A : Type} {P : List A → Prop}
(h_nil : P [])
(h_one : ∀ a, P [a])
(h_two : ∀ a b l, P l → P (a :: b :: l)) :
∀ l, P l
| [] => h_nil
| [a] => h_one a
| a :: b :: rest => h_two a b rest (list_ind2 h_nil h_one h_two rest)
/-- 若假设双步归纳原理,则 `split` 后两子列表长度均不大于原列表长度 -/
theorem split_len' : list_ind2_principle →
∀ {X : Type} (l l1 l2 : List X),
split l = (l1, l2) →
l1.length ≤ l.length ∧ l2.length ≤ l.length := by
intro _IP X l
induction l using list_ind2 with
| h_nil =>
intro l1 l2 h; injection h with h1 h2; subst h1 h2; simp
| h_one a =>
intro l1 l2 h; injection h with h1 h2; subst h1 h2; simp
| h_two a b l' ih =>
intro l1 l2 h
simp [split] at h
match hrec : split l' with
| (r1, r2) =>
rw [hrec] at h
rcases h with ⟨rfl, rfl⟩
have := ih r1 r2 hrec
simp
omega
/-- 使用证明好的 `list_ind2` 确立 `split_len` -/
theorem split_len : ∀ {X : Type} (l l1 l2 : List X),
split l = (l1, l2) →
l1.length ≤ l.length ∧ l2.length ≤ l.length := by
intro X l
induction l using list_ind2 with
| h_nil =>
intro l1 l2 h; injection h with h1 h2; subst h1 h2; simp
| h_one a =>
intro l1 l2 h; injection h with h1 h2; subst h1 h2; simp
| h_two a b l' ih =>
intro l1 l2 h
simp [split] at h
match hrec : split l' with
| (r1, r2) =>
rw [hrec] at h
rcases h with ⟨rfl, rfl⟩
have := ih r1 r2 hrec
simp
omega
/-- 辅助引理:当原列表长度至少为 2 时,`split` 划分出的两个子列表长度严格小于原列表 -/
theorem split_strict_len : ∀ {X : Type} (a b : X) (rest l1 l2 : List X),
split (a :: b :: rest) = (l1, l2) →
l1.length < (a :: b :: rest).length ∧ l2.length < (a :: b :: rest).length := by
intro X a b rest l1 l2 h
simp [split] at h
match hrec : split rest with
| (r1, r2) =>
rw [hrec] at h
rcases h with ⟨rfl, rfl⟩
have := split_len rest r1 r2 hrec
simp
omega
/-- #### 练习:3 星 (split_perm)
证明原列表与切分后拼接的双列表互为置换。 -/
theorem split_perm : ∀ {X : Type} (l l1 l2 : List X),
split l = (l1, l2) → Permutation l (l1 ++ l2) := by
sorry2. 合并两个已排序列表 (Defining Merge)
lean
/-- 将两个升序列表按大小顺序归并为一个有序列表。
在 Lean 4 中,以两列表长度和 `l1.length + l2.length` 作为良基度量,系统自动完成终止性验证。 -/
def merge (l1 l2 : List Nat) : List Nat :=
match l1, l2 with
| [], _ => l2
| _, [] => l1
| a1 :: l1', a2 :: l2' =>
if a1 ≤ a2 then
a1 :: merge l1' (a2 :: l2')
else
a2 :: merge (a1 :: l1') l2'
termination_by l1.length + l2.length
/-- 空列表在左侧时的合并退化情形 -/
theorem merge_nil_l : ∀ l, merge [] l = l := by
intro l
rw [merge]
/-- 头部元素满足序关系时的合并前进步伐 -/
theorem merge2 : ∀ (x1 x2 : Nat) r1 r2,
x1 ≤ x2 →
merge (x1 :: r1) (x2 :: r2) = x1 :: merge r1 (x2 :: r2) := by
intro x1 x2 r1 r2 h
conv => lhs; rw [merge]
simp [h]3. 归并排序主算法 (Defining Mergesort)
lean
/-- 完整的归并排序算法:
对于基准情况(空列表与单元素列表)直接返回;
对于长度至少为 2 的列表,切分为两半并递归排序后合并。 -/
def mergesort (l : List Nat) : List Nat :=
match l with
| [] => []
| [x] => [x]
| x1 :: x2 :: l' =>
match heq : split (x1 :: x2 :: l') with
| (l1, l2) =>
have _hlen := split_strict_len x1 x2 l' l1 l2 heq
merge (mergesort l1) (mergesort l2)
termination_by l.length
decreasing_by
· exact _hlen.1
· exact _hlen.2
/-- 归并排序计算验证示例 -/
theorem mergesort_example : mergesort [5, 2, 8, 3, 1, 9, 4, 6, 7] = [1, 2, 3, 4, 5, 6, 7, 8, 9] := by
sorry
/-- 归并排序展开方程 -/
theorem mergesort_equation : ∀ l : List Nat,
mergesort l =
match l with
| [] => []
| [x] => [x]
| x1 :: x2 :: l' =>
let (l1, l2) := split (x1 :: x2 :: l')
merge (mergesort l1) (mergesort l2) := by
intro l
rw [mergesort.eq_def]
/-- 适合归并排序结构的特化归纳原理 -/
theorem mergesort_ind : ∀ (P : List Nat → List Nat → Prop),
(∀ l, l = ([] : List Nat) → P [] []) →
(∀ l (x : Nat), l = ([x] : List Nat) → P [x] [x]) →
(∀ l _x, l = _x →
(match _x with | _ :: _ :: _ => True | _ => False) →
∀ l1 l2, split l = (l1, l2) →
P l1 (mergesort l1) →
P l2 (mergesort l2) →
P _x (merge (mergesort l1) (mergesort l2))) →
∀ l, P l (mergesort l) := by
sorry4. 正确性证明:保序性 (Correctness: Sortedness)
lean
/-- #### 练习:2 星 (sorted_merge1)
证明若首元素不大于待合并两列表的首元素,则将其置于合并结果前仍保持有序。 -/
theorem sorted_merge1 : ∀ x x1 l1 x2 l2,
x ≤ x1 → x ≤ x2 →
sorted (merge (x1 :: l1) (x2 :: l2)) →
sorted (x :: merge (x1 :: l1) (x2 :: l2)) := by
sorry
/-- #### 练习:4 星 (sorted_merge)
核心引理:两个有序列表合并后的结果仍有序。 -/
theorem sorted_merge : ∀ l1, sorted l1 →
∀ l2, sorted l2 →
sorted (merge l1 l2) := by
sorry
/-- #### 练习:2 星 (mergesort_sorts)
证明归并排序产出的列表必然升序有序。 -/
theorem mergesort_sorts : ∀ l, sorted (mergesort l) := by
sorry5. 正确性证明:置换性 (Correctness: Permutation)
lean
/-- #### 练习:3 星,进阶 (merge_perm)
核心引理:合并两个列表得到的结果是原两列表拼接结果的一个置换。 -/
theorem merge_perm : ∀ (l1 l2 : List Nat),
Permutation (l1 ++ l2) (merge l1 l2) := by
sorry
/-- #### 练习:3 星,进阶 (mergesort_perm)
证明归并排序产出的列表是输入的置换。 -/
theorem mergesort_perm : ∀ l, Permutation l (mergesort l) := by
sorry
/-- 最终结论:归并排序是一个完全正确的排序算法! -/
theorem mergesort_correct :
is_a_sorting_algorithm mergesort := by
intro al
exact ⟨mergesort_perm al, mergesort_sorts al⟩
end VFA.Merge