最大子列和問題
這裡摘自慕課浙大陳越姥姥的課程問題
介紹對於一個問題,以不通的演算法來計算的效率比較。
首先我們使用演算法一:暴力演算法
int MaxSubseqSum1(int A[],int N) { int Thissum,MaxSum = 0; int i,j,k; for ( i= 0;i<N;i++) /* i是子列左端位置 */ { for(j=i;j<N;j++) /* j 是子列右端位置 */ { Thissum = 0; /*Thissum是A[i]到A[j]的子列和*/ for(k = i;k<=j;k++) Thissum += A[k]; if(Thissum >MaxSum) MaxSum = Thissum; } } return MaxSum; }
額。。。這個演算法的時間複雜度為 O(N^3)貌似對於處理一些大型資料程式會被中斷。我們嘗試將時間複雜度降低。
由此來到演算法二
int MaxSubseqSum2(int A[],int N) { int Thissum,MaxSum = 0; int i,j,k;for ( i= 0;i<N;i++) /* i是子列左端位置 */ { for(j=i;j<N;j++) /* j 是子列右端位置 */ { Thissum = Thissum + A[j]; if(Thissum >MaxSum) MaxSum = Thissum; } } return MaxSum; }
這裡演算法使用遞加替換了k的for迴圈,使時間複雜度為O(N^2)
呃。。一個專業的程式猿會將 時間複雜度為 n^2的演算法轉換為 N*log N
呢麼這裡我們就請出重點人物,分而治之
int Max3( int A, int B, int C ) { /* 返回3個整數中的最大值 */ return A > B ? A > C ? A : C : B > C ? B : C; } int DivideAndConquer( int List[], int left, int right ) { /* 分治法求List[left]到List[right]的最大子列和 */ int MaxLeftSum, MaxRightSum; /* 存放左右子問題的解 */ int MaxLeftBorderSum, MaxRightBorderSum; /*存放跨分界線的結果*/ int LeftBorderSum, RightBorderSum; int center, i; if( left == right ) { /* 遞迴的終止條件,子列只有1個數字 */ if( List[left] > 0 ) return List[left]; else return 0; } /* 下面是"分"的過程 */ center = ( left + right ) / 2; /* 找到中分點 */ /* 遞迴求得兩邊子列的最大和 */ MaxLeftSum = DivideAndConquer( List, left, center ); MaxRightSum = DivideAndConquer( List, center+1, right ); /* 下面求跨分界線的最大子列和 */ MaxLeftBorderSum = 0; LeftBorderSum = 0; for( i=center; i>=left; i-- ) { /* 從中線向左掃描 */ LeftBorderSum += List[i]; if( LeftBorderSum > MaxLeftBorderSum ) MaxLeftBorderSum = LeftBorderSum; } /* 左邊掃描結束 */ MaxRightBorderSum = 0; RightBorderSum = 0; for( i=center+1; i<=right; i++ ) { /* 從中線向右掃描 */ RightBorderSum += List[i]; if( RightBorderSum > MaxRightBorderSum ) MaxRightBorderSum = RightBorderSum; } /* 右邊掃描結束 */ /* 下面返回"治"的結果 */ return Max3( MaxLeftSum, MaxRightSum, MaxLeftBorderSum + MaxRightBorderSum ); } int MaxSubseqSum3( int A[], int N ) { /* 保持與前2種演算法相同的函式介面 */ return DivideAndConquer( A, 0, N-1 ); }
首先我們來分析一下這段程式碼,一個 比較大小Max3函式,沒有問題,第三個保持和前面演算法介面相同的函式沒有問題,重點在第二個函式我們要理解這段程式碼我們首先要理解分而治之的思想,
如圖,給出一個數組含有8個元素,將之一分為二,遞迴呼叫分分分分,分成最後只有一個 4 | -3,4在 | 中算最大,-3<0即為0,這是分,然後我們看 4 | -3的治,從 | 開始 分別向左和右走(加),明顯結果為 1,呢麼治為 1 , 1和 0 和 4相比較得出 ,4最大,再舉一個 我們看 4 -3 | 5 -2 注意 | 的位置,由分 得出左半邊 最大為 4,右半邊最大為5,我們看治,從 | 開始 向左右兩邊加 ,得出 治 為 4 + -3 +5 = 6.
最後一種方法為線上處理 時間複雜度為 O (N)
int MaxSubseqSum4(int A[], int N) { int Thissum,MaxSum; int i; Thissum = MaxSum =0; for(i=0;i<N;i++) { Thissum += A[i]; /*向右累加*/ if(Thissum>MaxSum) MaxSum = ThisSum;//發現更大和則更新當前結果 else if(Thissum<0) //如果為負數則 Thissum = 0; } return MsxSum; } } }
這裡我們分析這個陣列, 從 -1 開始 小於 0呢麼 直接跳過 從 3開始 3 -2 = 1>0並令MaxSum = 1沒有問題, 1+4 =5 >0沒有問題,5-6 <0 呢麼跳過從 -6後面的1開始1+6 =7 ,MaxSum = 7
個人總結,對於一個問題來講,我們程式設計師能解決的情況下,可以將它的時間複雜度 降低到 O(N*logN)甚至 O(N) 會大大提高演算法的效率。