分治法,即分而治之,将一个复杂问题划分成若干个相同性质的子问题进行求解,最后再”合治“为初始问题的解。在数据结构课程中求解”最大子列和“的问题中就有用到这一经典算法,而分治法往往需要通过递归进行实现。
接下来,我们就以”最大子列和“问题为切入点,通过一步步书写代码,进一步理解这一算法思想及实现过程。
【问题描述】给定n个整数的序列{a1,a2,…,an},求函数f(i,j)=max{0, ∑ k = i j a k \sum_{k=i}^j a_k ∑k=ijak}的最大值。
在这里,”子列“被定义为原始序列中连续的一段数字,我们要找出的是具有最大和的一段连续的子列,并且返回它的和。如果这个最大和是负数,那么我们取0为最终答案。
【算法实现】分治,即把大问题分成小问题进行解决,那么求一整数序列的最大子列和是不是可以看成把该整数序列分成左右两半,分别求得左半部分和右半部分的最大子列和,以及跨越左右边界的最大子列和,这三个最大子列和中最大的那个呢?
那么,我们的程序可以写为:
/*与主函数的接口 长度为N的整数序列*/
int MaxSubseqSum(int List[], int N)
{
return DivideAndConquer(List,0,N-1);
}
/*分治法求最大子列和*/
int DivideAndConquer(int List[], int left, int right)
{
/*分*/
int center, i;
center = (left + right) / 2; /*找到中分点*/
int MaxLeftSum, MaxRightSum; /*左半部分与右半部分的最大子列和*/
int MaxLeftBorderSum, MaxRightBorderSum; /*从边界向两侧扫描的跨越边界的最大子列和*/
/*治*/
return Max(MaxLeftSum, MaxRightSum, MaxLeftBorderSum + MaxRightBorderSum); /*问题的结果:三个最大子列和中最大的那个*/
}
/*返回三个数中最大的那个数*/
int Max(int A, int B, int C)
{
return A > B ? A > C ? A : C : B > C ? B : C;
}
那么,确定了程序的主体框架后,怎么进一步求解这三个最大子列和呢?
对于这个问题,我们可以采用递归的思想进行解决,即把锅甩给左右两个整数序列,让它们进行求解,得到结果后再返回来,并与跨越边界的结果进行比较,返回这三个子列和中最大的数作为最终结果。
那怎么通过程序语言实现甩锅呢?
我们知道这里的甩锅其实就是递归,把问题交给子问题进行求解,得到结果后再返回来,具体怎么实现交给计算机去解决,我们等着收割就好了。其中计算机能否一步一步向前迭代,最后再返回我们想要的值,还需要我们正确设置好两个约束。
a)递推关系 和 b)终止条件。
对于本问题,我们可以进行如下分析:
其中D表示DivideAndConquer函数,下标k表示第k次迭代,k+1、k-1同理,下标L、R表示左、右半部分整数序列,上标MLS、MRS分别表示MaxLeftSum和MaxRightSum,MLBS+MRBS表示MaxLeftBorderSum + MaxRightBorderSum,绿色箭头表示返回。
如上图所示,取第k次迭代进行分析, D k L D_{kL} Dk