我在读CLRS的《算法导论》。在第二章中,作者提到了“循环不变量”。什么是循环不变量?


当前回答

之前的回答已经很好地定义了循环不变量。

以下是CLRS的作者如何使用循环不变量来证明插入排序的正确性。

插入排序算法(见书):

INSERTION-SORT(A)
    for j ← 2 to length[A]
        do key ← A[j]
        // Insert A[j] into the sorted sequence A[1..j-1].
        i ← j - 1
        while i > 0 and A[i] > key
            do A[i + 1] ← A[i]
            i ← i - 1
        A[i + 1] ← key

循环不变量在这种情况下: 子数组[1到j-1]始终被排序。

现在让我们检查一下,证明这个算法是正确的。

初始化:在第一次迭代j=2之前。所以子数组[1:1]就是要测试的数组。因为它只有一个元素,所以它是有序的。这样不变性就被满足了。

维护:这可以通过在每次迭代后检查不变量来轻松验证。在这种情况下,它被满足了。

终止:这是我们将证明算法正确性的步骤。

当循环结束时,j=n+1。循环不变量再次被满足。这意味着子数组[1到n]应该排序。

这就是我们想用算法做的。因此,我们的算法是正确的。

其他回答

我喜欢这个非常简单的定义:

循环不变量是(程序变量之间的)一种条件,它必须在每次循环迭代之前和之后立即为真。(请注意,在迭代过程中,这并没有说明它的真伪。)

By itself, a loop invariant doesn't do much. However, given an appropriate invariant, it can be used to help prove the correctness of an algorithm. The simple example in CLRS probably has to do with sorting. For example, let your loop invariant be something like, at the start of the loop, the first i entries of this array are sorted. If you can prove that this is indeed a loop invariant (i.e. that it holds before and after every loop iteration), you can use this to prove the correctness of a sorting algorithm: at the termination of the loop, the loop invariant is still satisfied, and the counter i is the length of the array. Therefore, the first i entries are sorted means the entire array is sorted.

一个更简单的例子:循环不变量、正确性和程序推导。

我理解循环不变量的方式是作为一个系统的,正式的工具来推理程序。我们做了一个陈述,我们专注于证明它是正确的,我们称之为循环不变量。这组织了我们的逻辑。虽然我们也可以非正式地讨论一些算法的正确性,但使用循环不变量迫使我们非常仔细地思考,并确保我们的推理无懈可击。

It is hard to keep track of what is happening with loops. Loops which don't terminate or terminate without achieving their goal behavior is a common problem in computer programming. Loop invariants help. A loop invariant is a formal statement about the relationship between variables in your program which holds true just before the loop is ever run (establishing the invariant) and is true again at the bottom of the loop, each time through the loop (maintaining the invariant). Here is the general pattern of the use of Loop Invariants in your code:

... // the Loop Invariant must be true here while ( TEST CONDITION ) { // top of the loop ... // bottom of the loop // the Loop Invariant must be true here } // Termination + Loop Invariant = Goal ... Between the top and bottom of the loop, headway is presumably being made towards reaching the loop's goal. This might disturb (make false) the invariant. The point of Loop Invariants is the promise that the invariant will be restored before repeating the loop body each time. There are two advantages to this:

Work is not carried forward to the next pass in complicated, data dependent ways. Each pass through the loop in independent of all others, with the invariant serving to bind the passes together into a working whole. Reasoning that your loop works is reduced to reasoning that the loop invariant is restored with each pass through the loop. This breaks the complicated overall behavior of the loop into small simple steps, each which can be considered separately. The test condition of the loop is not part of the invariant. It is what makes the loop terminate. You consider separately two things: why the loop should ever terminate, and why the loop achieves its goal when it terminates. The loop will terminate if each time through the loop you move closer to satisfying the termination condition. It is often easy to assure this: e.g. stepping a counter variable by one until it reaches a fixed upper limit. Sometimes the reasoning behind termination is more difficult.

应该创建循环不变量,以便当达到终止条件时,且不变量为真,则达到目标:

不变+终止=>目标 它需要实践来创建简单而相关的不变式,这些不变式捕获了除了终止之外的所有目标实现。最好使用数学符号来表示循环不变量,但当这导致过于复杂的情况时,我们依赖于清晰的散文和常识。

之前的回答已经很好地定义了循环不变量。

以下是CLRS的作者如何使用循环不变量来证明插入排序的正确性。

插入排序算法(见书):

INSERTION-SORT(A)
    for j ← 2 to length[A]
        do key ← A[j]
        // Insert A[j] into the sorted sequence A[1..j-1].
        i ← j - 1
        while i > 0 and A[i] > key
            do A[i + 1] ← A[i]
            i ← i - 1
        A[i + 1] ← key

循环不变量在这种情况下: 子数组[1到j-1]始终被排序。

现在让我们检查一下,证明这个算法是正确的。

初始化:在第一次迭代j=2之前。所以子数组[1:1]就是要测试的数组。因为它只有一个元素,所以它是有序的。这样不变性就被满足了。

维护:这可以通过在每次迭代后检查不变量来轻松验证。在这种情况下,它被满足了。

终止:这是我们将证明算法正确性的步骤。

当循环结束时,j=n+1。循环不变量再次被满足。这意味着子数组[1到n]应该排序。

这就是我们想用算法做的。因此,我们的算法是正确的。

Loop invariant is a mathematical formula such as (x=y+1). In that example, x and y represent two variables in a loop. Considering the changing behavior of those variables throughout the execution of the code, it is almost impossible to test all possible to x and y values and see if they produce any bug. Lets say x is an integer. Integer can hold 32 bit space in the memory. If that number exceeds, buffer overflow occurs. So we need to be sure that throughout the execution of the code, it never exceeds that space. for that, we need to understand a general formula that shows the relationship between variables. After all, we just try to understand the behavior of the program.

循环不变量属性是一个条件,适用于循环执行的每一步。For循环,while循环,等等)

这对于循环不变证明是必不可少的,如果在执行的每一步都保持循环不变属性,则可以证明算法正确执行。

对于一个正确的算法,循环不变量必须保持在:

初始化(开始)

维护(之后的每一步)

终止(当它完成时)

这被用来计算很多东西,但最好的例子是加权图遍历的贪婪算法。对于贪心算法产生最优解(穿过图的路径),它必须达到连接所有节点在最小权值路径可能。

因此,循环不变的性质是所选择的路径具有最小的权值。在开始时,我们没有添加任何边,所以这个属性为真(在这种情况下,它不是假的)。在每一步中,我们都遵循最小权值边(贪婪步),所以我们再次采用最小权值路径。最后,我们找到了最小加权路径,所以我们的性质也是成立的。

如果一个算法不这样做,我们可以证明它不是最优的。