最大子段和之分治法
阿新 • • 發佈:2020-12-10
最大子段和之C語言
問題描述:
給定一個數組,找出其中可以構成最大數的子段,需要注意的是,這個不同於最大子序列求和
—— 最大欄位求和:欄位必須是連續的
—— 最大子序列求和:子序列只要是包含在原來的序列中即可
舉個例子:
-1 4 -3 1 5 -1 4 -5 2
求上述的陣列中的最大欄位和,不難得知,最大子段和就是 10 ,也就是子段 4 -3 1 5 -1 4
思路:
首先,列舉?沒有列舉解決不了的問題好吧。但是真的列舉麼?在時間和空間複雜度上都會有很大的消耗;
分治?該怎麼分治呢?由於在一個子段中,分治的話必須將原來的陣列劃分成幾個部分,在本題中,大致有三種情況:
- 當最大子段和在所選的界定值左邊的時候
- 當最大欄位和在所選的界定值右邊的時候
- 當最大子段和包含所選的界定值,也就是在界定值的兩側的時候
在第一種情況下,當最大子段和位於陣列最左邊的時候,通過不斷地遞迴,保留最大的子段和,最後相加便可得到
同樣,在第二種情況下,也就是說最大子段和在右邊的時候,類似與上面的,通過不斷地遞迴,相加可以求得
第三種情況相對來說比較複雜,當界定值包含在最大子段和中的時候,看上去就是類似於與問題了,但是,根據上述的想法,取得的界定值往左右兩邊分別尋找,相加便可以求得
在每一個劃分的區間,都是採用上述三個步驟,可以得到 MAX Sum
程式碼演示:
#include<stdio.h>
#include<stdlib.h>
#define n 5
int a[n];
int MaxSum(int a[],int l,int r, int *sitel, int *siter)
{
int c; //中間位置
int lsum,rsum,csum; //左邊、右邊、中間最大和
if(l==r)
{
*sitel=l;
*siter=l;
return a[l];
}
else
{
c=(l+r)/2;
//遞迴求左右兩邊的最大欄位和
lsum= MaxSum(a,l,c, sitel, siter); //左邊最大欄位和
//左邊大時的位置臨時儲存
int ltemp_sitel, ltemp_siter;
ltemp_sitel=*sitel;
ltemp_siter=*siter;
rsum=MaxSum(a,c+1,r, sitel, siter); //右邊最大欄位和
//右邊大時的位置臨時儲存
int rtemp_sitel, rtemp_siter;
rtemp_sitel=*sitel;
rtemp_siter=*siter;
//計算中間最大欄位和
//求左半部份
int i;
int csuml=0,cleft=0;
int templ=c;
for(i=c;i>=l;i--)
{
cleft=cleft+a[i];
if(cleft>csuml){
csuml=cleft;
templ=i;
}
}
//求右半部份
int j;
int csumr=0,cright=0;
int tempr=c;
for(j=c+1;j<=r;j++)
{
cright=cright+a[j];
if(cright>csumr){
csumr=cright;
tempr=j;
}
}
//中間最大和
csum=csuml+csumr;
//位置確定
if(csum>lsum)
if(csum>rsum){ //中間最大
*sitel=templ;
*siter=tempr;
return csum;
}else{ //右邊最大
*sitel=rtemp_sitel;
*siter=rtemp_siter;
return rsum;
}else if(lsum>rsum){ //左邊最大
*sitel=ltemp_sitel;
*siter=ltemp_siter;
return lsum;
}
}
}
void input()
{
int i;
printf("請輸入一組數字:");
for(i=0;i<n;i++)
scanf("%d",&a[i]);
}
void main()
{
int Sum;
int sitel=0;
int siter=n-1;
int i;
input();
Sum=MaxSum(a,0,n-1,&sitel, &siter);
printf("最大和為:%4d\n",Sum);
printf("構成最大和的數值:");
for(i=sitel; i<=siter; i++)
printf("%4d",a[i]);
}
結果如下所示:
小結:
在分治法解決問題的時候,不斷地拆分問題,將問題拆分成我們可以解決的問題即可,類似於上題,將問題最後拆分成最小的子集,可以直接判斷最大子段和
遞迴的思想還是很重要!!!