首页 > 编程语言 >【算法】循环不变式

【算法】循环不变式

时间:2023-09-24 21:56:58浏览次数:36  
标签:.. 迭代 元素 不变式 算法 循环

循环不变式

一、数学归纳法

因为循环不变式的定义与数学归纳法类似,所以我们先来看看数学归纳法。

我们首先从高中开始回忆起,有关于数列的数学归纳法。

一般的,证明一个与正整数 \(n\) 有关的命题,可以分为以下两个步骤[1]
1. 归纳奠基:证明当 \(n=n_0 (n_0 \in N^*)\) 时,命题成立。
2. 归纳步骤:证明当 \(n=k (k \in N^*, k \geq n_0)\) 时,命题成立,证明当 \(n=k+1\) 时,命题也成立。
根据 1. 2. 两步,我们可以断定对于 \(n_0\) 之后的所有正整数 \(n\),命题都成立。
对于上述的证明过程,我们称之为数学归纳法。

二、循环不变式

《算法导论》有关于循环不变式的定义[2]

循环不变式主要用来帮助我们理解算法的正确性。关于循环不变式,我们必须证明三个性质:
1. 初始化:循环的第一次迭代之前,它为真。
2. 保持:如果循环的某次迭代之前它为真,那么下次迭代之前它仍为真。
3. 终止:在循环终止时,不变式为我们提供一个有用的性质,该性质有助于证明算法是正确的。

基于霍尔三元组的定义,我们可以将循环不变式中while的部分正确性可定义为[3]

\[\frac{ \{ C \wedge I \} \text{ body } \{I\}}{\{I\} \textbf{ while } (C) \text{ body } \{ \neg C \wedge I \}} \]

当前两条性质成立时,在循环的每次迭代之前,循环不变式都为真。(当然,为了证明循环不变式在每次迭代之前保持为真,我们完全可是使用不同于循环不变式本身的其他已证实的事实。)注意,这类似于数学归纳法,其中为了证明某条性质成立,需要证明一个基本情况和一个归纳步骤。这里,证明第一个迭代之前不变式成立对应于基本情况,证明从一次迭代到下一次迭代不变式成立对应于归纳步骤[2:1]

第三条性质也是是最重要的,因为我们将使用循环不变式来证明正确性。通常,我们和导致循环终止的条件一起使用循环不变式。终止性不同于我们通常使用数学归纳法的做法,在归纳法中,归纳步是无限地使用得,这里当循环终止时,停止“归纳”[2:2]

下面通过两个例子来说明循环不变式的使用。

三、循环不变式的使用

3.1 线性查找

本部分内容参考自知乎专栏《算法导论》——循环不变式[4]

我们首先来看一个简单的例子,线性查找。线性查找的算法如下:

int LinearSearch(int A[], int n, int x)
{
    for (int i = 0; i < n; i++)
    {
        if (A[i] == x)
            return i;
    }
    return -1;
}

我们来证明这个算法的正确性。首先我们需要定义循环不变式,这里我们定义循环不变式为:

在每次迭代之前,如果 \(x\) 在 \(A[0..i-1]\) 中,则 \(A[0..i-1]\) 中的元素都不等于 \(x\)。

接下来我们证明循环不变式的三条性质:

  1. 初始化:在第一次迭代之前,\(i = -1\),所以 \(A[0..i-1]\) 为空,因此循环不变式成立。
  2. 保持:在第 \(k\) 次迭代之前,在数组 \(A[0..k-1]\) 中找不到 \(x\),所以 \(A[0..k-1]\) 中的元素都不等于 \(x\),因此循环不变式成立。
  3. 终止
    1. 在第 \(n\) 次迭代时,找到了 \(x\),所以 \(A[0..n-2]\) 中的元素都不等于 \(x\),因此循环不变式成立。
      • 更为详细的,
      • I. 在第 \(n\) 次迭代时,找到了 \(x\),所以对于 \(i = n - 1\) 时,\(A[0..i-1]\) 中的元素都不等于 \(x\)。则有 \(A[0..n-2]\) 中的元素都不等于 \(x\)。
      • II. \(\forall{k}\in{[0,i)}\), 由 2. 保持 ,所以 \(A[k] \neq x\) 。
    2. 在第 \(n\) 次迭代时,没有找到 \(x\),所以 \(A[0..n-1]\) 中的元素都不等于 \(x\),因此循环不变式成立。
      综上所述,循环不变式成立,算法正确。

3.2 插入排序

插入排序是个比较经典的排序算法,其算法过程为:

  1. 将第一个元素看作已排序的序列,将第二个元素到最后一个元素看作未排序的序列。
  2. 从未排序的序列中取出第一个元素,将其插入到已排序的序列中的合适位置。
  3. 重复步骤 2,直到未排序的序列为空。

通俗来说就是,每次从后面的未排序序列中取出一个元素,然后将其插入到前面的已排序序列中的合适位置。

插入排序的算法如下:

void InsertionSort(int A[], int n)
{
    for (int i = 1; i < n; i++)
    {
        int key = A[i];
        int j = i - 1;
        while (j >= 0 && A[j] > key)
        {
            A[j + 1] = A[j];
            j--;
        }
        A[j + 1] = key;
    }
}

我们来证明这个算法的正确性。首先我们需要定义循环不变式,这里我们定义循环不变式为:

在每次迭代之前,\(A[0..i-1]\) 中的元素都是原来 \(A[0..i-1]\) 中的元素,但已经按序排列。

接下来我们证明循环不变式的三条性质:

  1. 初始化:在第一次迭代之前,\(i = 1\),所以 \(A[0..i-1]\) 只有一个元素,既 \(A[0]\),对于这一个元素,显然是已经按序排列的,因此循环不变式成立。
  2. 保持:对于for循环体的第一行,我们可以看到,每次迭代都会将 \(A[i]\) 的值赋给 \(key\),所以 \(A[0..i-1]\) 中的元素都是原来 \(A[0..i-1]\) 中的元素,但已经按序排列。接下来我们来看while循环体,while循环体的第一行是 \(A[j + 1] = A[j]\),这一行的作用是将 \(A[j]\) 的值赋给 \(A[j + 1]\),这样就将 \(A[j]\) 向后移动了一位。为方便起见,我们统一用 \(i\) 表示,重述上一句话,也就是当第 \(A[i] < A[i - 1]\) 时,将 \(A[i]\) 的值不断向前移动。直到到达一个位置,是的这个移动的值大于前面的值。
    3。 终止:导致for循环结束的条件是 \(i = n\),那么对于 \(A[0..n-1]\) 中的元素,均根据 2. 保持,已经按序排列,特别的 \(A[0..n-1]\) 中的元素就是数组 \(A\) 的所有元素,因此循环不变式成立,算法正确。

以上就是我对于循环不变式的理解,如果有错误的地方,欢迎指正。



参考与注释:


  1. 数列证明之数学归纳法_知乎 ↩︎

  2. 《算法导论》 Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, Clifford Stein 机械工业出版社 ↩︎ ↩︎ ↩︎

  3. 循环不变量_wikipedia.org ↩︎

  4. 《算法导论》——循环不变式 - 知乎 ↩︎

标签:..,迭代,元素,不变式,算法,循环
From: https://www.cnblogs.com/BryceAi/p/17726766.html

相关文章

  • 排序算法
    排序算法哪些是稳定的排序算法,哪些是不稳定的稳定的:直接插入排序:最坏情况是逆序,时间复杂度是O(N^2^),最好情况是插入的都是顺序,时间复杂度O(N),空间复杂度O(1)冒泡排序:时间复杂度O(N^2^),空间复杂度O(1)计数排序:时间复杂度O(N+Range),空间复杂度O(range)不稳定:希尔排序:时间复杂......
  • 最近公共祖先 Tarjan算法
    P3379【模板】最近公共祖先(LCA)利用并查集点击查看代码#include<bits/stdc++.h>usingnamespacestd;constintN=5e5+10;vector<int>g[N];vector<pair<int,int>>query[N];intans[N],vis[N];intf[N];intfind(intx){ returnf[x]==x?x:f[x]......
  • 实现一致性哈希算法
    背景一致性哈希主要用于分布式系统解决数据存储与访问的负载问题,极大的提高了可用性与扩展性。分布式系统往往是把数据分布到不同的节点,这些节点可以动态的加入或离开集群,这样就需要考虑一些问题,如果按照传统的hash算法进行数据分布,动态扩缩节点就需要对数据进行rehash,数据量大或......
  • 一致性哈希算法实现(java)
    代码基本实现未完待续........... publicstaticvoidmain(String[]args){​TreeMap<Integer,String>hashNodes=newTreeMap<>();hashNodes.put(1,"1.1.1.1");hashNodes.put(4,"1.1.1.2");hashNodes.put......
  • 最近公共祖先 倍增算法
    P3379【模板】最近公共祖先(LCA)点击查看代码#include<bits/stdc++.h>usingnamespacestd;constintN=5e5+10;vector<int>g[N];intdep[N],fa[N][22];voiddfs(intu,intfather){ dep[u]=dep[father]+1; fa[u][0]=father;//2的i次方的点 for(inti=......
  • 文心一言 VS 讯飞星火 VS chatgpt (99)-- 算法导论9.3 5题
    五、用go语言,假设你已经有了一个最坏情况下是线性时间的用于求解中位数的“黑箱”子程序。设计一个能在线性时间内解决任意顺序统计量的选择问题算法。文心一言:为了在线性时间内解决任意顺序统计量的选择问题,我们可以使用一个基于快速选择算法的方法。快速选择算法是基于快速排序的......
  • 算法打卡|Day4 链表part02
    Day4链表part02今日任务●24.两两交换链表中的节点●19.删除链表的倒数第N个节点●面试题02.07.链表相交●142.环形链表II[TOC]Problem:24.两两交换链表中的节点思路1.迭代法就要注意画图!画图!还是画图!另外迭代的次序不要忘记,链表迭代统一从左往右迭代。用三......
  • labview中for循环用法
    说明:记录一下自己用到的和知道的for用法。 1、基本概念for循环位置:程序面板-编程-结构-for循环for的作用:可以使for循环体内的代码执行一定的次数。这文本语言类似。for重要参数:N表示循环总数,可输入可输出,都表示循环总数。            i表示当前循......
  • 深度学习算法中的遗传编程(Genetic Programming)
    深度学习算法中的遗传编程(GeneticProgramming)引言深度学习算法在近年来取得了巨大的成功,广泛应用于计算机视觉、自然语言处理等领域。然而,深度学习算法仍然面临着一些挑战,例如需要大量的标注数据、模型结构的选择等。为了解决这些问题,研究者们开始探索结合遗传编程(GeneticProgram......
  • python基础 while,for循环
    whlie循环格式while结束条件:执行语句break结束if条件:break小游戏案例猜拳小游戏importrandomren=0ji=0print('-'*12+'欢迎玩猜拳小游戏'+'-'*12)print('本游戏实行三拳两胜制')n=0whilen<3:ran=random.randint(0,2)guess......