先看引用自由Cay Horstmann写的Computing Concepts with C++ Essentials (3rd) 一书的,用以计算a^n的例子:
double power(double a, int n)
{
double r = 1;
int i = n;
int b = a;
while(i > 0)
{
if(i % 2 == 0)
{
b = b * b;
i = i / 2;
}
else
{
r = r * b;
i--;
}
}
return r;
}
粗看一下,用此方法计算a^n时比原始方法进行循环次数要少很多。几乎是1/2倍地递减。
解释此算法,必须知道循环不变式(loop invariant)。即这个循环存在一条式子,每次循环它都成立。既然每次循环都成立,必然地最后一次循环亦成立,故可根据这恒等式得到答案。发现这条式子是困难的,不过幸运的是,我们是证明者而不是发现者,这条循环不变式是:
注意到我描述的是每次循环都成立,这种描述方式和数学归纳法及其相似:n = 1成立,若由n = k时成立可推出n = k + 1时也成立,则对于每个n,结论都成立。证明此恒等式亦可采用相似的形式。为描述方便,约定m表示循环次数,对应的,表示第m次循环和m+1此循环的i, b, r值:
i = 0时循环结束,b^i = 1, r = a^n。
好奇的我,试着看标准库的的pow()函数,惊奇的发现,它也是用循环不变式的,并且代码更为简洁(这是修改后代码,源代码还有n的正负判断):
double power(double a, int n)
{
for (double Z = 1.0; ; a *= a)
{
if ((n & 1) != 0)
Z *= a;
if ((n >>= 1) == 0)
return Z;
}
}
n & 1其实是判断n是否为偶数,是偶数则表达式等于0,否则为1;n >>= 1等价于n = n/2.这个算法效率更高,每次循环都将指数除2。
如果将代码再次用i代替n,b代替a,改写如下:
double power(double a, int n)
{
int i = n;
double b = a;
for (double Z = 1.0; ; b *= b)
{
if ((i & 1) != 0)
Z *= b;
if ((i >>= 1) == 0)
return Z;
}
}
即有如下的循环不变式
虽然,在我以前所编的程序中,并没发现有运用循环不变式的,就算有我也察觉不到。但是你只有了解了一个技能的时候才能有机会学会它。一个程序员总是有完美主义者倾向的,希望自己写的程序简洁效高,循环不变式即是提供这样的一种方法,在特定时候。