什么是循环不变量?

时间:2010-07-11 02:07:54

标签: algorithm terminology definition clrs loop-invariant

我正在阅读CLRS的“算法简介”。在第2章中,作者提到了“循环不变量”。什么是循环不变量?

16 个答案:

答案 0 :(得分:315)

简单来说,循环不变量是一个谓词(条件),它适用于循环的每次迭代。例如,让我们看一个简单的for循环,如下所示:

int j = 9;
for(int i=0; i<10; i++)  
  j--;

在这个例子中,i + j == 9是真的(对于每次迭代)。一个较弱的不变量也是如此 i >= 0 && i <= 10

答案 1 :(得分:107)

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

  

循环不变量是[程序变量中的]条件,它必须在循环的每次迭代之前和之后立即生效。 (请注意,这在迭代过程中没有说明其真实性或虚假性。)

循环不变本身并没有太大作用。但是,给定适当的不变量,它可以用来帮助证明算法的正确性。 CLRS中的简单示例可能与排序有关。例如,让循环不变,就像在循环开始时,对此数组的第一个i条目进行排序。如果你可以证明这确实是一个循环不变量(即它在每次循环迭代之前和之后保持),你可以用它来证明排序算法的正确性:在循环终止时,循环不变量仍然满足,计数器i是数组的长度。因此,排序的第一个i条目表示整个数组已排序。

一个更简单的例子:Loops Invariants, Correctness, and Program Derivation

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

答案 2 :(得分:36)

在处理循环和不变量时,有许多人没有立即意识到这一点。他们在循环不变量和循环条件(控制循环终止的条件)之间感到困惑。

正如人们所指出的,循环不变量必须为真

  1. 在循环开始之前
  2. 在循环的每次迭代之前
  3. 循环结束后
  4. (尽管在循环体中它可能暂时是假的)。 另一方面,循环终止后循环条件 必须为假,否则循环永远不会终止。

    因此,循环不变量和循环条件必须是不同的条件。

    复杂循环不变量的一个很好的例子是二进制搜索。

    bsearch(type A[], type a) {
    start = 1, end = length(A)
    
        while ( start <= end ) {
            mid = floor(start + end / 2)
    
            if ( A[mid] == a ) return mid
            if ( A[mid] > a ) end = mid - 1
            if ( A[mid] < a ) start = mid + 1
    
        }
        return -1
    
    }
    

    所以循环条件似乎非常直接 - 当开始&gt;结束循环终止。但为什么循环正确?什么是循环不变量证明它的正确性?

    不变量是逻辑陈述:

    if ( A[mid] == a ) then ( start <= mid <= end )
    

    这个陈述是一个逻辑重言式 - 在我们试图证明的特定循环/算法的上下文中总是真实的 。它提供了有关循环终止后循环正确性的有用信息。

    如果我们返回是因为我们在数组中找到了元素,那么该语句显然是正确的,因为如果A[mid] == a那么a在数组中,mid必须在开始和结束之间。如果循环因start > end而终止,那么就不会有start <= mid mid <= end这样的数字,因此我们知道语句A[mid] == a必须是假的。但是,因此整体逻辑语句在空意义上仍然是正确的。 (在逻辑上,语句if(false)then(something)总是为真。)

    现在,当循环终止时,我所说的循环条件必然是假的?看起来当在数组中找到元素时,当循环终止时,循环条件为真!它实际上不是,因为隐含的循环条件实际上是while ( A[mid] != a && start <= end )但是我们缩短了实际测试,因为隐含了第一部分。无论循环如何终止,这个条件在循环后显然都是假的。

答案 3 :(得分:32)

以前的答案以非常好的方式定义了一个循环不变量。

以下是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]进行排序。

这是我们想要用我们的算法做的事情。因此我们的算法是正确的。

答案 4 :(得分:16)

除了所有好的答案之外,我想一个很好的例子来自 如何思考算法,由Jeff Edmonds 可以很好地说明这个概念:

  

例1.2.1“Find-Max双指算法”

     

1)规格:输入实例由列表L(1..n)组成   元素。输出由索引i组成,使得L(i)具有最大值   值。如果有多个条目具有相同的值,那么任何条目   其中一个被退回。

     

2)基本步骤:您决定使用双指方法。你的右手指   在列表中运行。

     

3)进步的衡量标准:进展的衡量标准是沿着进展的程度   列出你的右手是。

     

4)循环不变量循环不变量表明你的左手指向你目前遇到的最大条目之一   右手指。

     

5)主要步骤:每次迭代,你将右手指向下移动一个   列表中的条目。如果右手指现在指向一个条目   它比左手指的入口大,然后向左移动   用右手指指。

     

6)取得进步:你的右手指移动,你取得了进步   一个条目。

     

7)维护循环不变:您知道循环不变量已按如下方式维护。对于每个步骤,新的左手指元素   是Max(旧左手指元素,新元素)。通过循环不变量,   这是Max(Max(更短列表),新元素)。在数学上,这是   Max(更长的名单)。

     

8)建立循环不变量:您最初通过将两个手指指向第一个元素来建立循环不变量。

     

9)退出条件:你的右手指完成后就完成了   遍历清单。

     

10)结束:最后,我们知道问题解决如下。通过   出口条件,你的右手已经遇到了所有的   条目。通过循环不变量,你的左手指指向最大值   这些。退回此条目。

     

11)终止和运行时间:所需的时间是不变的   乘以列表的长度。

     

12)特殊情况:检查有多个条目时会发生什么   具有相同的值或当n = 0或n = 1时。

     

13)编码和实施细节:......

     

14)形式证明:算法的正确性来自于   以上步骤。

答案 5 :(得分:6)

应该注意的是,循环不变量可以帮助设计迭代算法,当被认为是表达变量之间的重要关系的断言时,在每次迭代开始时和循环终止时必须为真。如果这种情况成立,则计算是在实现有效性的道路上。如果为false,则算法失败。

答案 6 :(得分:5)

在这种情况下不变意味着在每次循环迭代中的某个点必须为真的条件。

在合同编程中,不变量是在调用任何公共方法之前和之后必须为真(通过契约)的条件。

答案 7 :(得分:4)

不变的含义永远不会改变

这里循环不变意味着“在循环中发生变化的变化(递增或递减)不会改变循环条件,即条件满足”以便循环不变概念已经出现

答案 8 :(得分:1)

很难跟踪循环发生的情况。在没有实现其目标行为的情况下不终止或终止的循环是计算机编程中的常见问题。循环不变量有帮助。循环不变量是关于程序中变量之间关系的形式语句,它在循环运行之前保持为真(建立不变量),并且在循环的底部再次为真,每次循环(保持不变量) )。 以下是在代码中使用循环不变量的一般模式:

...    //这里的循环不变量必须为真         而(测试条件){
      //循环的顶部
      ...
      //循环的底部
      //这里的循环不变量必须为真    }
   //终止+循环不变=目标
   ...
在环的顶部和底部之间,可能正朝着达到环的目标的方向前进。这可能会扰乱(伪造)不变量。循环不变量的要点是在每次重复循环体之前恢复不变量的承诺。 这有两个好处:

工作不会以复杂的数据相关方式转移到下一轮。每个都独立于所有其他循环通过循环,使用不变的服务将通道绑定到一个工作整体。 你的循环工作的推理减少到推断循环不变量随着每次循环而恢复。这将循环的复杂整体行为分解为小的简单步骤,每个步骤可以单独考虑。 循环的测试条件不是不变量的一部分。这是循环终止的原因。你分别考虑两件事:为什么循环应该终止,以及循环在终止时实现其目标的原因。如果每次循环都接近满足终止条件,则循环将终止。通常很容易确保这一点:例如将计数器变量加1,直到达到固定的上限。有时终止背后的推理更加困难。

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

不变量+终止=&gt;目标
需要练习创建简单且相关的不变量,其捕获除终止之外的所有目标达成。最好使用数学符号来表达循环不变量,但是当这导致过于复杂的情况时,我们依赖于明确的散文和常识。

答案 9 :(得分:1)

抱歉,我没有评论权限。

@Tomas Petricek,如你所说

  

较弱的不变量也是如此,即i> = 0&amp;&amp;我&lt; 10(因为这是继续条件!)&#34;

它是如何循环不变的?

我希望我没错,据我所知 [1] ,循环开始时循环不变量为真(初始化),每次迭代前后都为真(维护)和在循环终止(终止)后也是如此。但是在最后一次迭代之后,我变为10.因此,条件i> = 0&amp;&amp;我&lt; 10变为false并终止循环。它违反了循环不变量的第三个属性(终止)。

[1] http://www.win.tue.nl/~kbuchin/teaching/JBP030/notebooks/loop-invariants.html

答案 10 :(得分:1)

Loop Invariant属性是一个条件,它适用于循环执行的每一步(即for循环,while循环等)。

这对循环不变证明至关重要,其中一个人能够证明算法在执行的每个步骤都能正确执行此循环不变属性。

要使算法正确,Loop Invariant必须保持在:

初始化(开头)

维护(后面的每一步)

终止(当它完成时)

这用于评估一堆事物,但最好的例子是加权图遍历的贪心算法。对于产生最佳解决方案(图中的路径)的贪婪算法,它必须以尽可能低的权重路径连接所有节点。

因此,循环不变属性是所采用的路径具有最小权重。在开头,我们没有添加任何边,因此该属性为true(在这种情况下,它不是false)。在每一步,我们遵循最低权重边缘(贪婪步骤),所以我们再次采用最低权重路径。在结束,我们找到了最低的加权路径,因此我们的属性也是如此。

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

答案 11 :(得分:0)

循环不变量是一个数学公式,例如(x=y+1)。在该示例中,xy表示循环中的两个变量。考虑到在整个代码执行过程中这些变量的行为发生变化,几乎不可能测试所有可能的xy值并查看它们是否产生任何错误。可以说x是一个整数。整数可以在内存中保留32位空间。如果该数量超过,则发生缓冲区溢出。所以我们需要确保在整个代码执行过程中,它永远不会超过这个空间。为此,我们需要了解一个显示变量之间关系的通用公式。 毕竟,我们只是试着了解程序的行为。

答案 12 :(得分:0)

简单来说,在每次循环迭代中都是一个LOOP条件:

for(int i=0; i<10; i++)
{ }

在这里我们可以说i的状态是i<10 and i>=0

答案 13 :(得分:0)

循环不变性是在执行循环之前和之后都为真的断言。

答案 14 :(得分:0)

定义如何思考算法,Jeff Edmonds

<块引用>

循环不变量是放置在循环顶部的断言,并且 每次计算返回到循环顶部时,它都必须为真。

答案 15 :(得分:-1)

在线性搜索中(根据书中给出的练习),我们需要在给定数组中找到值V.

从0&lt; = k&lt;长度和比较每个元素。如果找到V,或者扫描到达数组的长度,则只需终止循环。

根据我对上述问题的理解 -

循环不变量(初始化): 在k-1次迭代中找不到V.第一次迭代,这将是-1,因此我们可以说在位置-1

处找不到V.

<强>修的: 在下一次迭代中,在k-1中找不到的V保持为真

<强> Terminatation: 如果在k位置找到V或k达到数组的长度,则终止循环。