演算法分析初步-最大連續和問題(二)
本節使用剛學到的分治法來解決這個問題。再來回顧一下這個演算法:
分治演算法一般分為如下3個步驟。
劃分問題:把問題的例項劃分成子問題。
遞迴求解:遞迴解決子問題。
合併問題:合併子問題的解得到原問題的解。
在本例中,劃分就是把序列分成元素個數儘量相等的兩半:遞迴求解就是分別求出完全位於左半或者完全位於右半的最佳序列:合併就是求出起點位於左半、終點位於右半的最大連續和序列,並和子問題的最優解比較。
前兩部分沒有什麼特別之處,關鍵在於合併步驟,既然起點位於左半。終點位於右半。則可以人為的把這樣的序列分成兩部分,然後獨立求解:先尋找最佳起點,然後再尋找最佳終點。
int maxsum3(int *A,int x,int y) { //分治 //把問題劃分成子問題 //遞迴解決子問題 //合併子問題的解得到原問題的解 //O(nlogn) int i,v,L,R,m,maxs; if(y-x==1) //只有一個元素,直接返回 return A[x]; m=x+(y-x)/2; //第一步 劃分成[x,m)和[m,y) /取整為向0取整 相比(x+y)/2可以保證靠近起點! maxs=max(maxsum3(A,x,m),maxsum3(A,m,y)); //第二步 遞迴求解 v=0; //第三步 合併(1) 從分界點開始往左的最大連續和L L=A[m-1]; for(i=m-1;i>=x;i--) L=max(L,v+=A[i]); v=0; //第三步 合併(2) 從分界點開始往右的最大連續和R R=A[m]; for(i=m;i<y;i++) R=max(R,v+=A[i]); return max(maxs,L+R);//把子問題的解與L和R比較 }
上面的程式碼用到了 複製運算本身具有返回值的特點。在一定程度上簡化了程式碼,但不會犧牲可讀性。
在上面的程式中,L和E分別為從分界線往左、往右能達到的最大連續和。對於n=1000,tot值僅為9976,在前面的演算法基礎上又有大幅度改進。
是否可以像前面那樣,得到tot的數學表示式呢?注意求和技巧已經不再適用。需要用遞迴的思路進行分析:設序列長度為n時的tot值為T(n),則T(n)=2T(n/2) + n, T(1) = 1.其中2T(n/2)是兩次長度為n/2的遞迴呼叫,而最後的n是合併的時間(整個序列恰好掃描一遍)。注意這個方程是近似的,因為當n為奇數時兩次遞迴的序列長度分別為(n-1)/2和(n+1)/2,而不是n/2。幸運的是,這樣的近似對於最終結果影響很小,在分析演算法時總是可以忽略它。
解剛才的方程,可以得到.由於nlogn增長很慢,當n擴大兩倍時,執行時間的擴大倍數只是略大於2。
在結束對分治演算法的討論之前,有必要再談談上述程式中的兩個細節。首先是範圍表示,上面的程式用左閉右開區間來表示一個範圍,好處是在處理陣列分割時比較自然:區間[x,y)被分成的是[x,m)和[m,y),不需要在任何地方加減1,另外,空區間表示為[x,x),比[x,x-1]順眼多了。
另一個細節是分成元素個數儘量相等的兩半時分界點的計算。在數學上,分界點應當是x和y的平均數 m=(x+y)/2,此處卻用的是 x+(y-x)/2.
在數學上兩者相等,但在計算機中卻有差別。不知讀者是否注意到,運算子 “/” 的 取整是朝零方向的取整
對於最大連續和問題,本篇先後介紹了時間複雜度為 的演算法,每個新演算法較前一個來說,都是重大的改進。儘管分治法看上去很巧妙,但並不是最高效的。把O(n^2)演算法稍作修改,便可以得到一個O(n)演算法:當j確定時,S[j] - S[i-1]最大相當於 S[i-1]最小,因此只需要掃描一次陣列,維護目前遇到過的最小S即可。
int maxsum4(int *A)
{
//優化 連續子序列之和等於字首和之差 當j確定時,
//S[j]-S[i-1]最大便是相當於S[i-1]最小,只要維護
//目前遇到的最小S即可
//O(n)
int i,j,best,S[maxn],mins;
best=A[1];
mins=S[0]=0;
for(i=1;i<=n;i++)
S[i]=S[i-1]+A[i]; //遞推字首和S
for(i=1;i<=n;i++)
{
mins=min(mins,S[i-1]); //j之前的最小的S
best=max(best,S[i]-mins); //獲得當前的最大值
}
return best;
}
漸進時間複雜為多項式的演算法稱為多項式時間演算法,也稱有效演算法:而n!或者2^n這樣的低效的演算法稱為指數時間演算法。
不過需要注意的是,上界分析的結果在趨勢上能反應演算法的效率,但有兩個不精確性:一是公式本身的不精確性。例如,“非主流”基本操作的影響、隱藏在大O記號後的低次項和最高項係數:二是對程式實現細節與計算機硬體的依賴性。例如,對複雜表示式的優化計算,把記憶體訪問方式設計得更加cache友好等。