粗略地说,你是对的。循环不变量是“只是在循环的每次迭代之前和之后立即为真的条件”。然而,在这个定义下,对于所讨论的代码,实际上存在无限数量的循环不变量,其中大多数都没有特别的意义。例如,i < 101, i < 102, i < 103, ... 都是给定代码的循环不变量。
然而,通常我们对仅仅为了找到循环不变量而简单地寻找循环不变量感兴趣。相反,我们感兴趣的是证明一个程序是正确的,如果我们想证明一个程序是正确的,那么一个精心挑选的循环不变量将非常有用。
例如,有问题的代码是冒泡排序算法的内部循环,其目的是使数组“更有序”。因此,为了证明这段代码的完全正确性,我们必须证明三件事:
(1) 当执行到代码末尾时,数组是代码开头的数组的一个排列。(2) 当执行到代码末尾时,数组的反转数要么为零,要么小于代码开头的数组反转数(这个条件帮助我们证明外层冒泡排序算法的循环终止)。(3) 代码终止。
为了证明 (1) 我们需要考虑三个执行路径(当我们考虑 PATH 2 时,循环不变量将发挥关键作用)。
(路径 1)考虑当执行从代码的开头开始并第一次到达循环顶部时会发生什么。由于在此执行路径上未对数组进行任何操作,因此该数组是代码开头的数组的排列。
(路径 2)现在考虑当执行从循环顶部开始、绕过循环并返回到循环顶部时会发生什么。如果 a[i] <= a[i+1] 则不会发生交换,因此,数组仍然是代码开头的数组的排列(因为没有对其进行任何操作)。或者,如果 a[i] > a[i+1] 则交换确实发生。但是,数组仍然是代码开头的数组的排列(因为交换是一种排列)。因此,每当执行到达循环顶部时,数组就是代码开头的数组的排列。请注意,语句“数组是代码开头的数组的排列”是我们需要帮助我们证明代码正确的精心选择的循环不变量。
(路径 3)最后,考虑当执行从循环顶部开始但没有进入循环而是进入代码末尾时会发生什么。由于在此执行路径上未对数组进行任何操作,因此该数组是代码开头的数组的排列。
这三个路径涵盖了执行从代码开头到代码结尾的所有可能方式,因此,我们证明了(1)代码末尾的数组是开头数组的排列的代码。