循环不变式

       
先看引用自由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;
	}
}

即有如下的循环不变式

《循环不变式》


虽然,在我以前所编的程序中,并没发现有运用循环不变式的,就算有我也察觉不到。但是你只有了解了一个技能的时候才能有机会学会它。一个程序员总是有完美主义者倾向的,希望自己写的程序简洁效高,循环不变式即是提供这样的一种方法,在特定时候。




点赞