【数据结构和算法】循环不变式的理解
一、什么是循环不变式
循环不变式(loop invariants)不只是一种计算机科学的思想,准确地说是一种数学思想。在数学上阐述了通过循环(迭代、递归)去计算一个累计的目标值的正确性。
比如插入排序,每次循环从数组A中取出第j个元素插入有序区A[1 .. j-1],然后递增j。这样A[1 .. j-1]的有序性始终得到保持,这就是所谓的“循环不变”了。
循环不变式主体是不变式,也就是一种描述规则的表达式。其过程分三个部分:初始,保持,终止。
1、初始:循环的第一次迭代之前,它为真。
2、保持:如果循环的某次迭代之前它为真,那么循环的下次迭代之前它仍为真。
3、终止:在循环终止时,循环不变式为我们提供一个有用的性质,该性质有助于我们证明算法是正确的。
在这三个部分中,前两个是条件,最后一个是结论。
利用循环不变式(loop invariant)来证明循环的正确性与用数学归纳法(induction)证明数学等式的相同点在于:
都需要验证初值,或初始状态是否满足条件。
之后再证明在归纳或递推的过程中仍然满足这种条件。(这个条件在数学归纳中叫做递推关系,在循环中就是循环不变式(loop invariant))。
循环不变式(loop invariant)与数学归纳法(induction)的区别在于:
数学归纳可能是无限的,是无限递推的,但循环不变式所要证明的循环是要结束并给出正确结果的。
如何找循环不变式?
由于算法是一步步执行的,那么如果每一步(包括初试和结束)都满足一个共同的条件,那么这个条件就是要找的循环不变式(loop invariant)。
二、理解循环不变式
例子:
问题:给出二分查找的实现,函数原形 int seach(int[] array,int target)
array是保存已经从小到大排序好的数字,target是待查找的数字;
若查找成功则返回元素的下标,否则返回-1。
分析:二分查找的原理很简单:
把数组分成三份,中间一份只有一个元素,其他两份个数基本相同,如果中间的元素等于目标值,就返回它的下标;
如果大于,则去前半数组继续执行二分查找;
如果小于,则去后半数组;
直到数组没有元素为止。
这是循环不变式的一个计算机算法应用,我们可以按照它的规则来做,
我们的不变式就是:二分之后子数组的开始下标<=结束下标
循环结束条件是:二分之后子数组的开始下标>结束下标
二分查找的代码
package com.spring.test.service.algorithm.sort; /** * */ public class BinarySearch { public static void main(String[] args) { int[] arry={0,1,2,3,4,5,6}; System.out.println(seach(arry,7)); } /** * * @param array 目标数组 * @param a 确认元素a是在目标数组中 * @return 如果元素存在目标数组,则返回数组的下标,如果不存在,则返回-1 */ public static int seach(int[] array,int a){ //检测初始时,不变式是否为真 if(array==null || array.length==0){ //数组为空,则不存在 return -1; } //准备循环 int leftIndex=0;//数组左端下标 int rightIndex=array.length-1;//数组右端下标 int middleIndex;//数组中间下标 //循环开始 while(leftIndex<=rightIndex){//rightIndex>=1 就是循环不变式 middleIndex=(rightIndex+leftIndex)/2;//计算中间位置 if(array[middleIndex]==a){//分支1:如果中间下标的元素等于目标元素 return middleIndex; }else if(array[middleIndex]>a){//分支2:中间元素大于目标元素,则代表数据在左半部分数组 rightIndex=middleIndex-1;//把数组右端的下标设置为中间元素下标的前一个 }else{//分支3:中间元素小于目标元素,则代表数据在右半部分数组 leftIndex=middleIndex+1;//把数组左端的小标设置为中间元素的下标的后一个 } } return -1; } }
浙公网安备 33010602011771号