[I0A]查找最大子串問題的求解

Question:

假如你能夠預知股票未來幾天的動向,請你選擇合適的買入和售出 日期 以便獲得最大的利益值?

如下爲股票未來幾天的變動情況:



Result:

最優解爲第 7 天買入,第11天賣出 獲得最大單元利益爲 43.


Analysis:

對於這個問題的求解我們當然可以用傳統的排列組合方法來求解,即從以上任意天數中選擇兩天來進行計算

比如 選擇day 3 和 day 9   101- 85 = 16 單元利益爲16

所有的排列組合爲 N*(N-1) / 2  時間複雜度爲O(n^2)

然而此題依然可以有更效率的方式求解出結果,最終時間複雜度爲O(n lg n).

如表格第三欄 change行,我們知道 要活得最大利益 即要求 第三行中 的 求得 最大子串 即 18 + 20 -7 +12 = 43

所以該問題轉化爲求解 最大子串問題. 


How:

如何求解最大子串問題?這裏我們僅使用 分治法(Divide and Conquer)求解

分治法分爲三個步驟:Divide,Conquer,Combine.

Divide 將一個大的問題劃分成若干個小的同類型的問題

Conquer 對劃分後的小問題進行求解

Combine 將求解後的小問題結果合併 得出最後大問題的結果

顯然 如果要將這一長串數列 求出最大子串  將這長子列化分成若干個子列 然後對子列進行求解 然後將結果合併.



我們將數列分成兩個子串 即A[beg] ~ A[mid] 和 A[mid+1] ~ A[end] 兩個部分

假設最大子串 出現在這兩個部分,我們只要求解被劃分後的兩個子串 


所以應該分爲三部分  

1.beg<= i <= j <= mid

2.mid+1<= i <= j <= end

3.beg <= i <= mid < j <= end


首先我們針對於分界處來進行計算

result find_max_crossing_subarrary(int* arr,int beg,int mid,int end){
    int left_sum =  INT_MIN,right_sum =  INT_MIN;
    int left_index,right_index;
    int sum = 0;
    int i;
    for(i = mid;i >= beg; i--){
        sum = sum + arr[i];
        if(sum > left_sum) {
            left_sum = sum;
            left_index = i;
        }
    }
    sum = 0;
    for(i = mid+1;i <= end;i++){
        sum = sum + arr[i];
        if(sum > right_sum) {
            right_sum = sum;
            right_index = i;
        }
    }
    result res = {left_index,right_index,left_sum + right_sum};
    return res;
}


首先計算分界點左部分子串最大值,同時保存子串最大值時的標號index

然後計算分界點右邊子串最大值,同時保存子串最大值時的標號

然後返回左右部分合並後的子串最大值

整個流程思路爲計算左子串最大值,然後再計算右邊子串最大值,然後從中間向兩邊計算交界處子串最大值 如下圖所示





附上源代碼

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

typedef struct {
    int beg;
    int end;
    int sum;
} result;


void
arr_print(int *arr,int len);

int*
arr_convertor(char **argv,int *len);

int*
max_difference_arr(int *arr,int len);

result
find_max_crossing_subarrary(int* arr,int beg,int mid,int end);

result
find_max_subarrary(int *arr,int beg,int end);

/*
 day	0	1	2	3	4	5	6	7	8	9	10	11	12	13	14	15	16
 p 	100	113	110	85	105	102	86	63	81	101	94	106	101	79	94	90	97
 c 	0	13	-3	-25	20	-3	-16	-23	18	20	-7	12	-5	-22	15	-4	7
 */
int main(int argc,char **argv){
    int len;
    int *arr = arr_convertor(argv,&len);
    if(len < 2) {
    	printf("arguments must more than 2.\n");
    	return 0;
    }
    arr_print(arr,len);
    int *dif_arr = max_difference_arr(arr,len);
    arr_print(dif_arr,len-1);
    result res = find_max_subarrary(dif_arr,0,len-2);
    printf("max subarray begins at:%d ends at:%d.\n",res.beg,res.end+1);
    printf("and the max sum of the subarray is %d.\n",res.sum);
    printf("the max array is:");
    int i = 0;
    for(i = res.beg;i <= res.end+1;i++) {
    	printf("%4d",arr[i]);
    }
    free(arr);
    free(dif_arr);
    return 0;
}

result find_max_subarrary(int *arr,int beg,int end){
    
    if(beg == end) {
        result res = {beg,end,arr[beg]};
        return res;
    }
    int mid = (beg + end)/2;
    result left_res = find_max_subarrary(arr,beg,mid);
    result right_res = find_max_subarrary(arr,mid+1,end);
    result cross_res = find_max_crossing_subarrary(arr,beg,mid,end);
    if(left_res.sum >= right_res.sum && left_res.sum > cross_res.sum)
        return left_res;
    else if (right_res.sum >= left_res.sum && right_res.sum >= cross_res.sum)
        return right_res;
    else return cross_res;
    
}


result find_max_crossing_subarrary(int* arr,int beg,int mid,int end){
    int left_sum =  INT_MIN,right_sum =  INT_MIN;
    int left_index,right_index;
    int sum = 0;
    int i;
    for(i = mid;i >= beg; i--){
        sum = sum + arr[i];
        if(sum > left_sum) {
            left_sum = sum;
            left_index = i;
        }
    }
    sum = 0;
    for(i = mid+1;i <= end;i++){
        sum = sum + arr[i];
        if(sum > right_sum) {
            right_sum = sum;
            right_index = i;
        }
    }
    result res = {left_index,right_index,left_sum + right_sum};
    return res;
}


int* max_difference_arr(int *arr,int len){
    int* dif_arr = malloc(sizeof(int) * (len-1));
    int i;
    for(i = 0; i <len-1;i++){
        dif_arr[i] = arr[i+1] - arr[i];
    }
    return dif_arr;
}

int* arr_convertor(char **argv,int *len){
    int i;
    char **ptr = argv;
    int tmp[100];
    while (*++ptr != NULL) {
        tmp[i++] = atoi(*ptr);
    }
    *len = i;
    int *parr = malloc(sizeof(int)*i);
    for (i = 0; i < *len; i++) {
        parr[i] = tmp[i];
    }
    return parr;
}

void arr_print(int *arr,int len){
    int i;
    for(i = 0;i < len;i++) printf("%4d",arr[i]);
    printf("\n");
}


測試結果如下圖:



發佈了58 篇原創文章 · 獲贊 11 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章