最大子数组问题的分治求解算法

最大子数组问题,就是寻找一个数组内连续元素之和最大的子数组,如数组[1,-2,3,4,5,-6],则最大子数组为[3,4,5]。当然,只有当数组内有负数值时才有意义,如果全部为正数,则最大子数组就是其本身。

假如有数组arr下标从lowhigh,以中点索引mid作为区分,索引low~mid为左部分,mid+1~high为右部分,则最大子数组可能存在于左部分、右部分以及横跨左右部分。

假如最大子数组在左部分或右部分,其仍是一个最大子数组问题,只是规模更小,因此可递归地求解arr[low~mid]arr[mid+1~high]的最大子数组。
假如数组横跨左右两个部分,则从中间索引mid开始,向lowhigh延伸,先找到arr[mid~low]中的最大子数组(注意,从mid开始向两边找),然后找到arr[mid+1~high]的最大子数组。最后将两边的最大子数组进行合并,即求出横跨左右部分的最大子数组。

现在我们可以分别求得三个部分的最大子数组:左部的最大子数组、右部的最大子数组、横跨左右部的最大子数组。然后对其元素和进行比较。拥有最大元素和的最大子数组即所求。

下面是算法实现:

#include<iostream>
using namespace std;

//用于将求得的最大子数组信息进行打包
struct subarray_info{
  int left;  //左界
  int right;  //右界
  int sum;  //最大子数组和
};

//寻找横跨左右两部分的最大子数组,并打包成subarray_info结构
void find_max_crossing_subarray(int* arr,int low,int mid,int high,subarray_info* psub){
  int sum=0;
  int left_sum=-10000;  //左部分最大子数组和
  int right_sum=-10000;  //右部分最大子数组和
  for(int i=mid;i>=low;i--){  //从mid开始,到low,求得左部的最大子数组
    sum+=arr[i];

    //和大于left_sum,则求得更大的子数组
    if(left_sum<sum){
      left_sum=sum;
      psub->left=i;  //更新最大子数组左界信息,左界即此最大子数组在arr中的左部索引
    }
  }

  sum=0;
  for(int i=mid+1;i<=high;i++){  //从mid+1开始,到high,求得右部的最大子数组
    sum+=arr[i];
    if(right_sum<sum){  //同上
      right_sum=sum;
      psub->right=i;  //更新最大子数组右界信息
    }
  }
  psub->sum=left_sum+right_sum;  //最大子数组的和即左右部分最大子数组元素和的和
}

//寻找arr下标从low到high的最大子数组,并将其信息打包成subarray_info结构
void find_maximum_subarray(int* arr,int low,int high,subarray_info* psub){
  if(low==high){  //所求的数组只有一个元素值
    psub->left=low;
     psub->right=high;
     psub->sum=arr[low];
  }
  else{  //所求的数组有多个元素
    int mid=(low+high)/2;  //获取中间索引

    //分别用不同的subarray_info来保存不同部分的最大子数组
    subarray_info* p1=new subarray_info();
    subarray_info* p2=new subarray_info();
    subarray_info* p3=new subarray_info();

    find_maximum_subarray(arr,low,mid,p1);  //左部最大子数组
    find_maximum_subarray(arr,mid+1,high,p2);  //右部最大子数组
    find_max_crossing_subarray(arr,low,mid,high,p3);  //横跨左右部分的最大子数组

    int sum1=p1->sum;  //左部最大子数组的和
    int sum2=p2->sum;  //右部
    int sum3=p3->sum;  //横跨左右部
    if(sum1>=sum2 && sum1>=sum3){  //左部的最大,更新psub
      psub->left=p1->left;
      psub->right=p1->right;
      psub->sum=p1->sum;
    }
    else if(sum2>=sum1 && sum2>=sum3){  //右部的最大
      psub->left=p2->left;
      psub->right=p2->right;
      psub->sum=p2->sum;
    }
    else{  //横跨两部分的最大
      psub->left=p3->left;
      psub->right=p3->right;
      psub->sum=p3->sum;
    }
  }
}
int main(){
  int arr[16]={13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7};
  subarray_info* psub=new subarray_info();
  find_maximum_subarray(arr,0,15,psub);
  cout<<"左界:"<<psub->left<<endl;
  cout<<"右界:"<<psub->right<<endl;
  cout<<"最大子数组元素和:"<<psub->sum<<endl;
  return 0;
}

RESULT:
左界:7
右界:10
最大子数组元素和:43
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值