一个小问题:计算3层循环的执行次数
最近买了本《算法 第4版》打算看看算法,本来抱着割草的心理想快速过完第一章的基础知识然后去看红黑树神马的,结果居然被第一章的算法分析里一个小问题卡住了半天时间。决定记录一下。
问题描述
问题灰常简单,可以简单描述为:请问下面代码当n=100的时候,最终count等于多少(即最内层的for循环执行了多少次)。
public static int count(int n) { int count = 0; for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { for (int k = j + 1; k < n; k++) { count++; } } } return count; }
相信大多数人的第一感觉就是这还不简单,但是相信稍仔细看后,就会发现没那么简单。明显知道有个3次方,但是要想知道具体执行了多少次直接看真的看不出来。
求解
必须动笔了,我画出了下图:
可以看到我画出了3层循环,最下面是第3层的执行次数,因此要想知道count最终的值,就需要把最下面第3层所有的执行次数加起来。
分析图可以知道:
第三层的N-2 只出现在 第二层的N-1中
第三层的N-3 出现在第二层的 N-1和N-2
第三层的N-4 出现在第二层的 N-1和N-2和N-3
...
第三层的1 出现在第二层除了1和0的其他次循环
依次类推,因此可以得出公式:
第三层总执行次数S=1*(N-2)+2*(N-3)+3*(N-4)+...+(N-2)*1
目前得出了一个公式,但是这个公式显然不能用来算100这样比较大的N。
因此需要想办法化简,观察到公式里的系数1、2、3...一直到(N-2)是等差数列,因此可以把S拆成N行,每行的系数都是1:
S=
(N-2)+(N-3)+(N-4)+...+1+ //(N-1)*(N-2)/2
(N-3)+(N-4)+...+1+
+(N-4)+...+1+
... + 3 + 2 +1+ //6
... + 2 +1+ //3
+1 //1
观察到每行都是等差数列,每行都可以用等差数列计算公式来表示,等差数列公式如下:
因此:
根据公式:(这个公式在文章最后会给出推导过程)
得:
推导完毕。
代入N=100,计算得161700,运行程序 调用count(100) 验证结果正确。
附
最后附上中间用的公式12+22+32+...+n2的推导过程:
我们知道:(n+1)3=n3+3n2+3n+1,于是有:
23=13+3*12+3*1+1
33=23+3*22+3*2+1
...
(n+1)3=n3+3*n2+3*n+1
将各行相加,有:
23+33+...+(n+1)3=(13+23+...+n3)+3(12+22+...+n2)+3(1+2+...+n)+n
消去左右重复项,有
(n+1)3=13+3(12+22+...+n2)+3(1+2+...+n)+n
(n+1)3-13-3(1+n)n/2-n=3(12+22+...+n2)
解得:
总结
虽然因为一个小问题花了很多时间,但是我觉得是值得的,算法的一大核心就是分析算法的性能,如果连程序运行起来执行了多少次都搞不清楚,是没法去分析算法性能的。
对这个程序来说,有了这个公式,我们就可以很快计算出这个程序在N是一万、一百万的时候,程序将会执行count++的次数,而程序的运行时间和程序的运行次数是线性相关的,因此现在只要运行一次程序得到N=10的时候程序的运行时间,就可以手动计算N取任意数值的时候程序的预测运行时间。