【分治策略】最大子数组问题

来源:互联网 发布:阿里云数据库外网 编辑:程序博客网 时间:2024/05/22 18:55

在分治策略中,我们递归求解一个问题,在每一层递归应用中如下三个步骤:

 分解(Divide)步骤将问题划分为一些子问题,子问题的形式与原问题一样,只是规模更小。

 解决(Conquer)步骤递归地求解出子问题。如果子问题的规模足够小,则停止递归,直接求解。

 合并(Combine)步骤将子问题的解组合成原问题的解。

最大子数组问题

    寻找子数组A[low..high]的最大子数组。使用分治技术意味着我们将子数组划分为两个规模尽量相等的子数组。也就是说,找到子数组的中央位置,比如mid,然后考虑求解两个子数组A[low..mid]和A[mid+1..high]。如图(a)所示,A[low..high]的任何连续子数组A[i..j]所处的位置比如是以下三种情况之一:

    完全位于子数组A[low..mid]中,因此low<=i<=j<=mid。

    完全位于子数组A[mid+1..high]中,因此mid<i<=j<=high。

    跨越了中点,因此low<=i<=mid<j<=high。

   因此,A[low..high]的一个最大子数组所处的位置必然是这三种情况之一。实际上,A[low..high]的一个最大子数组必然完全位于A[low..mid]中、完全位于A[mid+1..high]中或者跨越中点的所有子数组中和最大者。我们可以递归地求解A[low..mid]和A[mid+1..high]的最大子数组,因为这两个子问题仍是最大子数组问题,只是规模更小。因此剩下的全部工作就是寻找跨越中点的最大子数组,然后在三种情况中选取和最大者。


   我们可以很容易地在线性时间(相对于子数组A[low..high]的规模)内求出跨越中点的最大子数组。此问题并非原问题规模更小的实例,因为它加入了限制——求出子数组必须跨越中点。如图b所示,任何跨越中点的子数组都由两个子数组A[i..mid]和A[mid+1..j]组成,其中low<=i<=mid且mid<j<=high。因此,我们只需要找出形如A[i..mid]和

A[mid+1..j]的最大子数组,然后将其合并即可。过程FIND-MAX-CORSSING-SUBARRAY接收数组A和下标low、mid和high为输入,返回一个下标元组划定跨越中点的最大子数组的边界,并返回最大子数组中值的和。

注:这里简单的实现,返回最大子数组中值的和,并没有返回下标

int Find_Max_Crossing_SubArray(int A[], int low, int mid, int high){   int left_sum = -0xff;   int sum = 0;   for (int i = mid; i >= low; i --)   {      sum += A[i];      if (sum >left_sum)      {         left_sum = sum; /* max_left=i */      }   }   int right_sum = -0xff;   sum = 0;   for (int j = mid + 1; j <= high; j ++)   {      sum += A[j];      if (sum > right_sum)      {         right_sum = sum; /* max_right=j */      }   }   return left_sum + right_sum; /* return (max-left,max-right,left_sum + right_sum)*/}

      此过程的工作方式如下所述。第3-12行求出左半部A[low..mid]的最大子数组。由于此子数组必须包含A[mid],第5-12行的for循环的循环变量i是从mid开始,递减直至达到low,因此,它所考察的每个子数组都具有A[i..mid]的形式。第3-5初始化变量left-sum和sum,前者保存目前为止找到的最大和,后者保存A[i..mid]中所有值的和。每当第8行找到一个字数组A[i..mid]的和大于left-sum时,我们在第9行将left-sum更新为这个子数组的和,并记录下标i。第13-23行的求右半部A[mid+1..high]的最大子数组,过程与左半部类似。

有了一个线性时间的FIND-MAX-CORSSING-SUBARRAY在手,我们就可以设计求解最大子数组问题的分治算法的代码了:

int Find_Maximum_SubArray(int A[], int low, int high){   int left_sum, right_sum, cross_sum;   if (high == low)   {      return A[low];   }   else   {      int mid = (low + high) / 2;      left_sum = Find_Maximum_SubArray(A, low, mid);      right_sum = Find_Maximum_SubArray(A, mid + 1, high);      cross_sum = Find_Max_Crossing_SubArray(A, low, mid, high);      if (left_sum >= right_sum && left_sum >= cross_sum)      {         return left_sum;      }      else if (right_sum >= left_sum && right_sum >= cross_sum)      {         return right_sum;      }      else      {         return cross_sum;      }   }}

[完整代码]

#include <stdio.h>int Find_Max_Crossing_SubArray(int A[], int low, int mid, int high){   int left_sum = -0xff;   int sum = 0;   for (int i = mid; i >= low; i --)   {      sum += A[i];      if (sum >left_sum)      {         left_sum = sum;      }   }   int right_sum = -0xff;   sum = 0;   for (int j = mid + 1; j <= high; j ++)   {      sum += A[j];      if (sum > right_sum)      {         right_sum = sum;      }   }   return left_sum + right_sum;}int Find_Maximum_SubArray(int A[], int low, int high){   int left_sum, right_sum, cross_sum;   if (high == low)   {      return A[low];   }   else   {      int mid = (low + high) / 2;      left_sum = Find_Maximum_SubArray(A, low, mid);      right_sum = Find_Maximum_SubArray(A, mid + 1, high);      cross_sum = Find_Max_Crossing_SubArray(A, low, mid, high);      if (left_sum >= right_sum && left_sum >= cross_sum)      {         return left_sum;      }      else if (right_sum >= left_sum && right_sum >= cross_sum)      {         return right_sum;      }      else      {         return cross_sum;      }   }}int main(){    int A[100];    int n;    printf("Please input the number of numbers:");    scanf("%d",&n);    for (int i = 0; i < n; i ++)    {       scanf("%d",&A[i]);    }    printf("最大子序列的和为:%d",Find_Maximum_SubArray(A, 0, n - 1));    return 0;}


其中时间复杂度为O(nlgn),附最大子数组的暴力求法方法

[代码实现]

#include <iostream>using namespace std;int main(){int n,temp,max,begin,end;int a[10001];while(cin>>n,n!=0){for(int i=0;i<n;i++){cin>>a[i];}max=INT_MIN;begin=a[0];end=a[n-1];for(int i=0;i<n;i++)    /* i控制开始的位置 */{temp=0;for(int j=i;j<n;j++)    /* j控制结束的位置 */{temp+=a[j];if(temp>max){max=temp;begin=i;end=j;}}}cout<<"max_sum:"<<max<<" begin:"<<begin+1<<" end:"<<end+1<<endl;}return 0; }

可使用分治法求解的一些经典问题

1)二分搜索

2)大整数乘法

3)Strassen矩阵乘法

4)棋盘覆盖

5)合并排序

6)快速排序

7)线性时间选择

8)最接近点对问题

9)循环赛日程表

10)汉诺塔

1 0
原创粉丝点击