chapter4 + chapter5 书中的观点:
@1,程序员都是乐观主义者,他们总是试图走捷径;编写函数代码,并将其插入系统中,然后热切的期望它能运行。有时候这样行的通,但是有千分之九百九十九的概率,这样做会导致一场灾难!
@2,专业的程序员永远不会忘记,无论系统的行为咋看起来多么的神秘莫测,其背后总有合乎逻辑的解释。
@3,虽然第一篇关于二分搜索的论文在1946年就发表了,但是第一个没有错误的二分搜索程序却直到1962年才出现。
@4,关键词:“连续两轮仅首次运行正确”,函数调用的前置条件和后置条件,“契约编程” .
作者用两章的篇幅,介绍了编写 “正确” 程序的方法。包括从理论上(数学)层面证明二分搜索的正确性,用脚手架验证程序,用断言快速定位程序不合理的逻辑及变量值。
a,二分搜索的数学层面证明,对于已经升序排序的数组序列 a[b,e];找出 T 在a数组中的位置。m = (b+e)/2 ,如果 a[m] > T,那么 T 必然不在 a[m+1,e] 序列中; 如果 a[m] < T,那么 T 必然不在a[b,m-1] 序列中。因此每次比较,均可对查找的范围减半。
b,程序正确性验证的方法 ,以下面的程序为例:
a,程序能否正常终止: 当输入 9 40 时,程序会陷入死循环! 为什么 b = mid +1, e = mid -1 ;已经对每次的循环都保证缩小1,还是免不了死循环?问题在于,第二次循环 b=5, mid=9, e=8;这本来就是不合理的,mid值跑到 [b,e] 范围之外去了(这也可以作为断言的条件)。当 mid - 1 正好等于 8,因此下次计算mid时,还是 b=5, mid=9, e=8.
b,修正a的问题,将 mid = b + e/2 改成 mid = b + (e-b)/2. 继续测试。按常见的错误,将 b= mid +1 改成 b = mid ; 将 e = mid -1 改成 e = mid;继续验证。当 输入 0 -1;0 0;0 5;1 0;均能正常工作,输入 1 5 时,程序又进入死循环。不难分析 b = 0, mid = 0,e = 0;下一次循环还是 b = 0, mid = 0,e = 0;问题是,每次循环,查找的范围没有保证至少缩小1个位置范围。
#include<stdio.h>
#include<assert.h>
int binarySearch(int arr[], int size, int value)
{
assert(size >= 0);
if(0==size)
{
return -1;
}
int b = 0;
int e = size - 1;
while(e >= b)
{
int mid = b + e/2;
assert(mid >= 0 && mid < size); //断言对于快速定位问题很有帮助
printf("b:%d, mid:%d, e:%d\n",b,mid,e); //有时在大型程序中,添加rintf打印调试,是个sample but powerful 的方法
if(value > arr[mid])
b = mid + 1;
else if(value < arr[mid])
e = mid - 1;
else
return mid;
}
return -1;
}
int main()
{
//简单测试
int s[10];
for(int i=0;i<10;++i)
{
s[i]=5*i;
printf("%d ",s[i]);
}
printf("\n");
int n,v;
//注意对边界情况做测试,一般能快速发现程序问题
scanf("%d %d",&n,&v);
while(n>=0 && n<=10)
{
printf("n:%d, v:%d \n",n,v);
printf("index:%d\n",binarySearch(s,n,v));
scanf("%d %d",&n,&v);
}
return 0;
}