最大子段和问题
问题描述如下:
多重循环求解
问题的核心是求解连续的一段子序列使其和最大。最容易想到的便是通过循环遍历寻找最优的下标i,j使得a[i]+…+a[j]的和最大。很容易看出时间复杂度为O(n^3)。
#include <iostream>
#include<stdlib.h>
#include<stdio.h>
using namespace std;
//最大字段和,暴力破解法,三重循环o(n^3)
int GetSum(int *a,int start,int end)
{
int ret=0;
for(int i=start;i<=end;i++)
ret = ret+a[i];//返回从a[i]到a[j]的和
if(ret<0) return 0;
else return ret;
}
int MaxSum(int *a,int n)
{
int i,j;
int temp=0;
for(i=1;i<=n;i++){
for(j=i;j<=n;j++){
if(GetSum(a,i,j)>temp)
temp = GetSum(a,i,j);
//寻找最优的i和j,可以保存输出
}
}
return temp;
}
int main()
{
int n;
cin>>n;
int *a = new int[n+1];
for(int i=1;i<=n;i++)
cin>>a[i];
cout << MaxSum(a,n);
return 0;
}
多重循环改进
利用上面两层循环的结果,可以省去GetSum()函数,能充分利用已经得到的结果,避免重复计算,节省了计算时间。时间复杂度为O(n^2)
#include <iostream>
#include<stdlib.h>
#include<stdio.h>
using namespace std;
//核心思想:是找下标i,j使得字段和最大
//暴力破解法就是在不断的遍历寻找最优值
//用两个循环,借助之前的for循环求得sum(a,i,j)再与中间值进行比较
int MaxSum(int *a,int n)
{
int i,j;
int temp=0;//定义初值
for(i=1;i<=n;i++){
int thisnum = 0;
for(j=i;j<=n;j++){
thisnum = thisnum + a[j];
//进行到这一句thisnum统计的值为a[i]+...a[j],实现了GetSum()函数的功能
if(thisnum>temp)
temp = thisnum;
}
}
return temp;
}
int main()
{
int n;
cin>>n;
int *a = new int[n+1];
for(int i=1;i<=n;i++)
cin>>a[i];
cout << MaxSum(a,n);
return 0;
}
分治算法,分成两半递归
通过分治的思想求最大子段和,将数组分平均分为两个部分,则最大子段和会存在于三种情况下:
1.最大子段和出现在左端
2.最大子段和出现在右端
3.最大子段和横跨在左右段 通过比较大小得到最大子段和
时间复杂度:O(nlogn)
即采用二分法进行二分,然后进行递归求解,分别求出左边连续子段和最大值,右边连续子段和最大值,以及左边和右边连续子段和最大值之和,三者进行比较,从中选择一个最大值进行返回!(这个值即就是当前划分的小区间中最大值)
#include <iostream>
using namespace std;
int MaxSum(int *a,int left,int right)
{
if(left==right){ //递归出口
if(a[left]>0) return a[left];
else return 0;
}
int mid=(left+right)/2;
int leftsum=MaxSum(a,left,mid);//第一种情况,递归左半部分
int rightsum=MaxSum(a,mid+1,right);//第二种情况,递归右半部分
//第三种情况,最大字段和是左半部分最大的一部分与右半部分最大的一部分左连续子段和之和。
int s1=0,s2=0;
int temp=0;
for(int i=mid;i>=left;i--){//从mid开始,逆序计算左边连续最大字段和
temp = temp+a[i];
if(temp>s1) s1=temp;
}
temp=0;
for(int j=mid+1;j<=right;j++){//从mid+1开始,顺序计算右边连续最大子段和
temp = temp+a[j];
if(temp>s2) s2=temp;
}
int midsum = s1+s2;//这是第三种情况的值
if(midsum<leftsum) midsum=leftsum;//比较第一种情况和第三种情况,更新最大值
if(midsum<rightsum) midsum=rightsum;//比较第二种情况的第三种情况,更新最大值
return midsum;
}
int main()
{
int n;
cin>>n;
int a[n+1];
for(int i=1;i<=n;i++){
cin>>a[i];
}
cout<<MaxSum(a,1,n);
return 0;
}
写出算法时间复杂度的递归表达式,由递归方程可得时间复杂度为O(nlogn)
动态规划算法
运用了动态规划的思想来解决最大子段和问题:
通过遍历累加这个数组元素,定时的更新最大子段和,
如果当前累加数为负数,直接舍弃,重置为0,然后接着遍历累加。
#include <iostream>
using namespace std;
#include<stdio.h>
#include<stdlib.h>
//动态规划算法
int main()
{
int a[101];
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
int nowsum=0,sum=0;
//不断更新nowsum,与之同sum比较
for(int i=1;i<=n;i++){
//如果上一段累加和为负数,那么起点更新为当前i值,nowsum=a[i]
if(nowsum<0) nowsum=a[i];
//如果为正数,更新nowsum
else if(nowsum>=0)nowsum=nowsum+a[i];
//nowsum与sum比较
if(nowsum>sum)
sum = nowsum;
}
cout<<sum;
return 0;
}
只有一个for循环,时间复杂度为O(n)。通过上面算法的一步步简化,时间复杂度从O(n^3) 到 O(n^2)再到O(nlogn) 最后降为O(n),充分说明的研究算法的必要性和算法思想的神奇。