循环不变式(loop invariants)不只是一种计算机科学的思想,准确地说是一种数学思想。在数学上阐述了通过循环(迭代、递归)去计算一个累计的目标值的正确性,属于基础数学的范畴,而且在计算机上也应用广泛。利用循环不变式可以帮助我们理解算法的正确性。
1、循环不变式的三条性质
初始化:循环的第一次迭代之前,它为真。
保持:如果循环的每次迭代之前它为真,那么下次迭代之前它仍为真。
终止:在循环终止时,不变式为我们提供一个有用的性质,该性质有助于证明算法是正确的。
2、案例1-求数组中的最大值
max = array[n];
while(n--) {
if(array[n] > max)
max = array[n];
}
循环不变式可以表述为以下形式,当n = 0时循环结束。
m
a
x
=
m
a
x
i
m
u
m
(
a
r
r
a
y
[
n
:
n
0
)
)
,
n
0
=
n
max = maximum(array[n : n_0)) , n_0 = n
max=maximum(array[n:n0)),n0=n
- 初始化:刚开始 n = n 0 n = n_0 n=n0,区间内只有一个元素,循环不变式成立。
- 保持:当n递减后,由于区间 [ n , n 0 ) [n,n_0) [n,n0)包含索引为n的元素,这个元素有可能会大于max,在if语句执行后,不变式被重新建立。
- 终止:当n=0时,循环结束。
3、案例2-插入排序(伪代码)
ps:数组下标从1开始。更详细的描述请参考《算法导论》插入排序。
INSERTION-SORT(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
- 初始化:在第一次循环迭代之前(当j=2)时,子数组A[1…j-1]仅由单个元素A[1]组成,所以子数组是排序好的,这表明第一次循环之前循环不变式成立
- 保持:for循环将所有大于key的元素向右移动一个位置,直到找到A[j]的适当位置然后插入。一次循环结束时A[1…j]的元素已经是有序的状态。这时,j = j + 1使得进入下一次迭代时循环不变式保持不变。
- 终止:有前面的分析可知,每次循环结束时的状态是这样的:j指向下一个待排序的元素,A[1…j-1]为有序状态。当j = A.length + 1时循环结束,整个数组已排序,因此算法正确。
4、关于循环不变式的一些思考
正如开头说的,循环不变式在数学上阐述了通过循环(迭代、递归)去计算一个累计的目标值的正确性,重点就在“累计”这两个字上,以用迭代和递归两种方法求最大值来看,他们不断的扩张正确域,当正确域和问题域一样大时得出最终答案。