1. 程式人生 > >最大子列和(時間複雜度)程式碼實現及結果對比

最大子列和(時間複雜度)程式碼實現及結果對比

題目:給定N個整數的序列{A1,A2,A3...ANA_1,A_2,A_3...A_N}, 求函式f(i,j)=max(0,Σk=1jAk)f(i,j)=max(0,\Sigma_{k=1}^jA_k)的最大值

這一部分是浙大資料結構第一章節的一道題目,主要目的是講解時間複雜度。

方法一

  • 直接使用迴圈遍歷的方法。
int MaxSubseqSum1( int A[], int N)
{
	int ThisSum, MaxSum = 0;
	int i,j,k;
	for (i=0; i<N, i++){//左邊最小值
for (j=i; j<N; j++){//右邊最小值 ThisSum = 0; for (k=i; k<=j; k++)//中間全部加和 ThisSum +=A[k]; if(ThisSum > MaxSum)//如果大於最大值 MaxSum = ThisSum;//替換 } } }
  • 這種方法比較暴力,需要全部遍歷,因為嵌套了3層迴圈,所以時間複雜度為T(N)=O(N3)T(N)=O(N^3)

方法二

  • 第一步在進行k迴圈時很顯然是沒有必要的,因為每次增加一個j的時候,只需要在上一步ThisSum上加一個A[j]A[j]
    就可以了。
int MaxSubseqSum1( int A[], int N)
{
	int ThisSum, MaxSum = 0;
	int i,j,k;
	for (i=0; i<N, i++){
		for (j=i; j<N; j++){
			ThisSum += A[j];//在上一個的基礎上直接加一個A[j]就可以了
			if(ThisSum > MaxSum)
				MaxSum = ThisSum;
		}
	}
}
  • 這種方法只嵌套了兩個迴圈,所以時間複雜度為T(N)=O(n2)T(N)=O(n^2).

方法三:分而治之

前兩種方法都比較好理解,這種分而治之的方法就有一定的難度了。這個問題的本質是一個遞迴的問題。

把一個複雜的問題分成兩個或更多的相同或相似的子問題,再把子問題分成更小的子問題……直到最後子問題可以簡單的直接求解,原問題的解即子問題的解的合併.

分治法在每一層遞迴上都有三個步驟:

  1. 分解:將原問題分解為若干個規模較小,相互獨立,與原問題形式相同的子問題;

  2. 解決:若子問題規模較小而容易被解決則直接解,否則遞迴地解各個子問題

  3. 合併:將各個子問題的解合併為原問題的解。

對於這一個問題,可以簡化先進行分析: 在這裡插入圖片描述

  1. 首先,假設陣列中有8個元素,如上圖所示,我們先沿紅色的線從中間分開,得到兩部分,發現可以進一步的劃分,直到使用黃色的線劃分完畢。
  2. 首先看4,-3,我們可以看出最大的值為4,這裡我們記下最大值為4,然後看5,-2,記下最大值5.同樣的道理可以得到最大值2,6。
  3. 下一步,我們來看跨越分割線的最大值,首先是4,-3,5,-2這4個數。從-3開始向右,得到最大的值要加到4,然後向左,得到最大的值為5,這樣得到跨越邊界的最大值為6,比較紅線右側得到的最大值,得到最大值為6。同理我們可以得到左側的最大值為8. 4.同理繼續向下,跨越紅色的最大值為11,這樣得到所有子空間的最大值為11。

以上就是使用分而治之的思想得到的最大值的結果。

這一部分的程式碼見完整程式碼部分。

方法四:線上處理

線上處理的方法是這些的方法中時間複雜度最小的演算法。就我的理解寫一些。 這種演算法只需要遍歷一遍陣列,其核心的思想是置零的那一部分。分為

  1. 首先從第一個非零數字開始,向後加和。
  2. 在加的過程中不斷的判斷是否為最大值,將最大值儲存。
  3. 直到遇到子列和負值,將已經得到的最大值儲存,然後結束這一部分,直到遇到下一個正數再重新進行第1步。

帶入到上圖中的例子,第一個數為-1,直接舍掉,第二個為正3,最大值記為3,加上第三個數-2,為1小於3,最大值仍然為3,加上4,最大值變為5,大於3,此時最大值變為5.因為加上下一個數之後小於零,所以這一部分計算結束,這部分子空間的最大值為5.從下一個1開始,可以得到最大值為7,所以最大的子空間的加和為7.

int MaxSubseqSum4( intA[], 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 MaxSum;
}

這種方法只使用了一次迴圈,所以時間複雜度為T(N)=O(N)T(N) = O(N),已經是時間複雜度最小的方法。

完整程式碼及對比

#include <stdio.h>
#include <time.h>
#include <math.h>

clock_t start, stop;

double duration;

int MaxSubseqSum1( int A[], int N)
{
	int ThisSum, MaxSum = 0;
	int i,j,k;
	for (i=0; i<N; i++){
		for (j=i; j<N; j++){
			ThisSum = 0;
			for (k=i; k<=j; k++)
				ThisSum +=A[k];
			if(ThisSum > MaxSum)
				MaxSum = ThisSum;
		}
	}
	return MaxSum;
}

int MaxSubseqSum2( int A[], int N)
{
	int ThisSum, MaxSum = 0;
	int i,j,k;
	for (i=0; i<N; i++){
		for (j=i; j<N; j++){
			ThisSum +=A[j];
			if(ThisSum > MaxSum)
				MaxSum = ThisSum;
		}
		ThisSum = 0;
	}
	return MaxSum;
}

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 );
}

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 MaxSum;
}

int main(){
	int N = 8;
	int max1, max2, max3, max4;
	int A[8] = {-1, 3, -2, 4, -6, 1, 6, -1};

	start = clock();
	max1 = MaxSubseqSum1(A, N);
	stop = clock();
	duration = ((double)(stop-start))/CLOCKS_PER_SEC;
	printf("Max1 = %d\n",max1);
	printf("duration1 = %6.2e\n",duration);

	start = clock();
	max2 = MaxSubseqSum2(A, N);
	stop = clock();
	duration = ((double)(stop-start))/CLOCKS_PER_SEC;
	printf("Max2 = %d\n",max2);
	printf("duration2 = %6.2e\n",duration);

	start = clock();
	max3 = MaxSubseqSum3(A, N);
	stop = clock();
	duration = ((double)(stop-start))/CLOCKS_PER_SEC;
	printf("Max3 = %d\n",max3);
	printf("duration3 = %6.2e\n",duration);

	start = clock();
	max4 = MaxSubseqSum4(A, N);
	stop = clock();
	duration = ((double)(stop-start))/CLOCKS_PER_SEC;
	printf("Max4 = %d\n",max4);
	printf("duration4 = %6.2e\n",duration);
}
  • 再貼一下執行的結果,其實已經可以看出來速度的差距了
Max1 = 7
duration1 = 3.00e-06
Max2 = 7
duration2 = 2.00e-06
Max3 = 7
duration3 = 2.00e-06
Max4 = 7
duration4 = 1.00e-06
[Finished in 2.1s]

參考文獻