最近買了本《算法 第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取任意數值的時候程序的預測運行時間。