排序算法之:循环不变式在计数排序中的正确性证明
字数 1554 2025-11-04 20:47:20
排序算法之:循环不变式在计数排序中的正确性证明
题目描述
计数排序是一种非比较的整数排序算法,其核心思想是通过统计每个元素出现的次数,然后计算每个元素在输出数组中的正确位置。本题要求使用循环不变式(Loop Invariant)来严格证明计数排序算法的正确性,确保排序后的数组满足非递减顺序。
解题过程
1. 算法步骤回顾
计数排序包含三个主要步骤(假设输入数组为A[0..n-1],元素范围为[0, k]):
- 计数阶段:创建辅助数组
C[0..k],其中C[i]记录元素i在A中出现的次数。 - 前缀和阶段:对
C进行前缀和计算,使C[i]表示小于等于i的元素个数。 - 放置阶段:从后向前遍历
A,根据C中记录的位置信息,将元素放入输出数组B的正确位置。
2. 定义循环不变式
我们重点关注放置阶段的循环不变式。假设放置阶段循环变量为j(从n-1递减到0),输出数组为B[0..n-1],前缀和数组为C[0..k]。循环不变式定义为:
- 不变式条件:在每次循环开始时,对于任意元素
x,B中已放置的元素(即B中索引大于C[x]的位置)均大于x,且B中索引小于等于C[x]的位置可能包含小于等于x的元素,但尚未放置完毕。 - 更形式化的描述:
- 对于所有
x,B[0..C[x]-1]中的元素均小于等于x(但可能未填满)。 - 对于所有
x,B[C[x]..n-1]中的元素均大于x(且已放置完毕)。
- 对于所有
3. 初始化证明
在循环开始前(j = n-1),输出数组B为空。此时:
- 对于任意
x,B[0..C[x]-1]为空,满足“元素均小于等于x”的条件(空集自然成立)。 B[C[x]..n-1]为空,满足“元素均大于x”的条件。
✅ 不变式在初始化时成立。
4. 保持证明
假设在循环的某次迭代开始时(j = i),不变式成立。本次迭代处理元素A[i]:
- 查找
A[i]应放的位置:pos = C[A[i]] - 1(因为C[A[i]]表示小于等于A[i]的元素个数,所以最后一个A[i]应放在pos位置)。 - 将
A[i]放入B[pos]。 - 更新
C[A[i]]减1,表示下一个A[i](如果存在)应放在更前的位置。
为什么不变式保持?
- 放置前,根据不变式,
B[pos+1..n-1]中的元素均大于A[i](因为pos+1 > C[A[i]]-1)。 - 放置后,
B[pos]被设为A[i],而B[0..pos-1]中的元素均小于等于A[i](由C的前缀和性质保证)。 - 更新
C[A[i]]后,对于其他元素y ≠ A[i],C[y]未变,因此关于y的不变式条件不受影响。
✅ 不变式在迭代后仍然成立。
5. 终止证明
当循环结束时(j = -1),所有元素已放入B。此时不变式表明:
- 对于任意元素
x,B[0..C[x]-1]中的元素均小于等于x,且B[C[x]..n-1]中的元素均大于x。
但由于所有元素已放置,且C[x]最终值为x的最小可能位置(因为C被递减更新),实际上B中所有小于等于x的元素恰好占据B[0..C[x]-1],而大于x的元素占据后续位置。因此B整体有序。
✅ 算法正确性得证。
关键点总结
- 循环不变式将动态的放置过程与静态的有序性关联起来。
- 前缀和数组
C的更新保证了相同元素的稳定性(从后向前遍历确保先出现的元素放在后面)。 - 通过分析初始化、保持和终止三个阶段,完整证明了计数排序的正确性。