1、循环不变式与插入排序的正确性
示例:对A = <5,2,4,6,1,4>进行插入排序
动图示例演示
上图表明A = <5,2,4,6,1,4> 进行插入排序的算法是如何工作的。下标j指出正被插入到手中的“当前元素”。在for循环(循环变量为j)的每次迭代的开始,包含元素A[1..j-1]的子数组构成了当前排序好的元素,剩余的子数组A[j + 1...n]对应于还未排序的元素。事实上,元素A[1...j-1]就是原来位置1到j-1的元素,但是现在已经按序排列。我们把A[1...j-1]的这些性质形式地表示为一个循环不变式。
循环不变式主要用来帮助我们理解算法的正确性。关于循环不变式,我们必须证明三条性质:
初始化:循环的第一次迭代之前,它为真。
保持:如果循环的某次迭代之前它为真,那么下次迭代之前它仍为真。
终止:在循环终止时,不变式为我们提供一个有用的性质,该性质有助于证明算法是正确的。
2、插入排序伪代码
InsertSort(A)
for j = 2 to A.length
key = A[j];
// Insert A[j] into the sorted sequence A[1...j-1]
i = j - 1;
while i > 0 and A[i] > key
A[i + 1] = A[i];
i = i -1;
A[i + 1] = key;
3、插入排序如何证明循环不变式成立?
初始化:首先证明在第一次循环迭代之前(当j = 2时),循环不变式成立。所以子数组A[1..j-1]仅由单个元素A[1]组成,实际上就是A[1]中原来的元素。而且该子数组是排序好的。这表明第一次循环迭代之前循环不变式成立。
保持:证明每次迭代保持循环不变式保持不变。非形式化,for循环体的4~7行将A[j-1]、A[j-2]、A[j-3]等向右移动一个位置,知道找到A[j]的适当位置,第8行将A[j]的值插入该位置。这是子数组A[1..j]由原来在A[1..j]中的元素组成,但已按顺序排列。那么对for循环的下一次迭代增加j将保持循环不变式。
终止:最后研究在循环终止时发生了什么。导致for循环终止的条件是 j > A.Length = n 。因此每次循环迭代j增加1,那么必有j = n + 1。在循环不变式的表述中将j用n+1代替,我们有:子数组A[1..n]由原来在A[1..n]中的元素组成,但已按序排列。此时,子数组A[1..n]就是真个数组,我们推断整个数组已排序。因此算法正确。
4、具体实现
// 插入排序
///
// brief: 插入排序
// @param:[in] numbers 待排序的数组
// @param:[in] n 排序数组的长度
// @returnValue int 表示返回值的标记
int InsertionSort(int *numbers, int n)
{
if(numbers == NULL)
return -1;
int i = 0, j = 0, temp = 0;
for(i = 1; i < n; i++) // 循环遍历每一个元素
{
temp = numbers[i]; // 将numbers[i]赋值给temp
j = i - 1;
while(j >= 0 && temp < numbers[j]) // 由小到大排序
{
numbers[j + 1] = numbers[j]; // 将大的元素向前放
j--;
}
// 如果插入的数比之前的大,将numbers[j] 与 numbers[j + 1]互换
numbers[j + 1] = temp;
}
return 0;
}