由July师兄二分查找代码及编程珠玑有感:循环不变性(断言)证明程序的正确性及发现bug

最近看到July大神的二分查找帖子里的两个版本,以及Lucida大神写的算法数据结构经历以及各种面试经历帖,偶然看到编程珠玑这本书,发现里面对二分查找的证明有着与算法导论的循环不变性相似,但是更加make sense的证明思路,最近读了一下,有一些心得。大家可以尝试运用这个方法来证明下更加容易出现bug的快速排序算法,以便确定自己的代码是否正确。

二分查找算法(h=n-1版本):(代码1)

int BinarySearch(int *a, int n, int x)//
{
	int l=0,h=n-1;
	while(1)
	{
	if(l>h)
		return -1;
	m=l+(h-l)/2;
	case:
		a[m]<x:  l=m+1;
		x<a[m]:  h=m-1;
		x=a[m]:  return m;
	}
}

定义循环不变式 mustbe(l,m): 如果x在a[l...m]中,则mustbe(l,m) bool值为真
                             cannotbe(l,m): x不可能出现在a[l,m]中 


二分查找算法(h=n-1版本)循环不变性证明:(代码2)

int BinarySearch(int *a, int n, int x)//
{
	{mustbe(0,n-1)}
	int l=0,h=n-1;
	{mustbe(l,h)}
	while(1)
	{
	{musbe(l,h)}
	if(l>h)
	{musbe(l,h)&&l>h}
	{can not find x}
		return -1;
	{musbe(l,h)&&l<=h}
	m=l+(h-l)/2;
	{musbe(l,h)&&l<=m<=h}
	case:
		a[m]<x:
		{musbe(l,h)&&l<=m<=h&&a[0]<=a[1]...<=a[m]<x}
		{mustbe(l,h)&&cannotbe(0,m)}
		{mustbe(m+1,h)}
			l=m+1;
		{musbe(l,h)}//loop invariant
		
		
		x<a[m]:  
		{musbe(l,h)&&l<=m<=h&&x<a[m]<=a[m+1]...<=a[n-1]}
		{mustbe(l,h)&&cannotbe(m,n-1)}
		{mustbe(l,m-1)}
			h=m-1;
		{musbe(l,h)}//loop invariant
		
		
		x=a[m]:  
		{musbe(l,h)&&l<=m<=h&&x==a[m]}
		{mustbe(m,m)}
			return m;
	}
}

二分查找算法(h=n版本):(代码3)

int BinarySearch(int *a, int n, int x)//
{
	int l=0,h=n;
	while(1)
	{
	if(l>=h)
		return -1;
	m=l+(h-l)/2;
	case:
		a[m]<x:  l=m+1;
		x<a[m]:  h=m;
		x=a[m]:  return m;
	}
}

二分查找算法(h=n版本)循环不变性证明:(代码4)

int BinarySearch(int *a, int n, int x)//
{
	{mustbe(0,n-1)}
	int l=0,h=n;
	{mustbe(l,h-1)}
	while(1)
	{
	{musbe(l,h-1)}
	if(l>=h)
	{musbe(l,h-1)&&l>=h}
	{can not find x}
		return -1;
	{musbe(l,h-1)&&l<h}
	m=l+(h-l)/2;
	{musbe(l,h-1)&&l<=m<=h-1}
	case:
		a[m]<x:
		{musbe(l,h-1)&&l<=m<=h-1&&a[0]<=a[1]...<=a[m]<x}
		{mustbe(l,h-1)&&l<=m<=h-1&&cannotbe(0,m)}
		{mustbe(m+1,h-1)}
			l=m+1;
		{musbe(l,h-1)}//loop invariant
		
		
		x<a[m]:  
		{musbe(l,h-1)&&l<=m<=h-1&&x<a[m]<=a[m+1]...<=a[n-1]}
		{mustbe(l,h-1)&&l<=m<=h-1&&cannotbe(m,n-1)}
		{mustbe(l,m-1)}
			h=m;
		{musbe(l,h-1)}//loop invariant
		
		
		x=a[m]:  
		{musbe(l,h-1)&&l<=m<=h-1&&x==a[m]}
		{mustbe(m,m)}
			return m;
	}
}


解释:

1.以下代码:(代码5)

int BinarySearch(int *a, int n, int x)//
{
	int l=0,h=n-1;
	while(1)
	{
	if(l>h)
		return -1;
	m=l+(h-l)/2;
	case:
		a[m]<x:  l=m+1;
		x<a[m]:  h=m-1;
		x=a[m]:  return m;
	}
}

与以下代码等价:(代码6)

int BinarySearch(int *a, int n, int x)//
{
	int l=0,h=n-1;
	while(l<=h)
	{
	m=l+(h-l)/2;
	case:
		a[m]<x:  l=m+1;
		x<a[m]:  h=m-1;
		x=a[m]:  return m;
	}
	return -1;
}

同理可得第二个case的二分查找代码.

为什么在证明程序的时候,要改写一下呢?

因为证明比较方便,而且非常beautiful~~

  

2.正如July师兄的代码所说,为啥初始从h=n-1 -> h=n后,后面if(l>h) ->if( l>=h), x<a[m]后的h=m-1 -> h=m


从循环不变性来解释,如果后面h=n, 然后后面还是if(l>h), h=m-1
代码4中,验证到第 10行时,{musbe(l,h-1)&&l>h} 得出找不到x,会漏掉l=h的情况,此时mustbe{l,h-1}也是找不到x的。这个或许不够make sense, 再往下走

验证到第 13行时,{musbe(l,h-1)&&l<=h}
m=l+(h-l)/2;
{musbe(l,h-1)&&l<=m<=h} 注意:此处之前l<h,加上m=(l+h)/2可以推导出l<=m<=h-1,而现在l<=h,加上m=(l+h)/2只可以推导出l<=m<=h


验证到第25 行时, x<a[m]:  
{musbe(l,h-1)&&l<=m<=h&&x<a[m]<=a[m+1]...<=a[n-1]}
{mustbe(l,h-1)&&cannotbe(m,n-1)&&m-1<=h-1}
{mustbe(l,m-1)}
h=m-1;
{musbe(l,h)}//不能保持loop invariant {mustbe(l,h-1)}

有人会说如果用mustbe(l,h)作为循环不变性呢?

当你走到代码4第10行的时候,就会发现{mustbe(l,h)&&l>=h}这个是推不出x not found的,因为可能l=h,区间还有一个元素,可能是x

这种问题对应于程序,就是所谓bug,所以这种断言式证明其实就是找bug的一种严谨的数学手段,而测试只能发现bug,不能确定没有bug!


引用自July师兄的二分查找博客http://blog.csdn.net/v_july_v/article/details/7093204#reply 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值