分治法1--最大序列和

分治法基本概念

分治法是构建基于多项分支递归的一种很重要的算法范式。字面上的解释是「分而治之」,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。

分治法的适用场景:对于一个规模为 n 的问题,若该问题可以容易地解决(比如说规模 n 较小)则直接解决,否则将其分解为 k 个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。

下面我通过一个简单的实例题目对分治进行详解:

例题:

**最大连续和问题:**给出一个长度为n的序列A1,A2,···,An,求最大连续和。换句话说,要求找到 1<=i<=j<=n,使得Ai+···+Aj尽量大。

解题步骤

分治算法解题一般分为三个步骤:

划分问题: 把问题的实例划分成子问题;

递归求解: 递归解决子问题;

合并问题: 合并子问题的解得到原问题的解。

“划分”就是把序列分成元素个数尽量相等的两半;

“递归求解”就是分别求出完全位于左半边或则完全位于右半边的最佳序列;

“合并”就是求出起点位于左半、终点位于右半的最大连续和序列,并和子问题的最优解比较。

“划分”和“递归求解”基本上都能理解,关键在于“合并” 步骤。既然起点位于左半,终点位于后半,则可以认为把这样的序列分成两个部分,然后独立求解:先寻找最佳起点,然后再寻找最佳终点。

实例演示

**最大序列和问题:**给出一个长度为 n 的序列$$A_1$$,$$A_2$$,···,$$A_n$$,求最大连续和。换句话说,要求找到 1<=i<=j<=n,使得$$A_i$$+$$A_{i+1}$$+···+$$A_j$$尽量大。

假如现在有一组数[2,4,-2,1],那么这组数的连续和有以下几种情况:

2=2
4=4
-2=-2
1=1
2+4=6
4-2=2
-2+1=-1
2+4-2=4
4-2+1=3
2+4-2+1=5

不难看出,组数[2,4,-2,1]的最大序列和为 6。这里采用的是穷举法,列举出了所有可能的结果,当数组长度比较大时,运算复杂度会非常大。

很容易看出,最大序列和问题适用于分治法。现在介绍一下如何用分治法来求解最大序列和问题,根据分治法的解题步骤,对应的解题思路如下:

  • “划分”就是把序列分成元素个数尽量相等的左右两半;

  • “递归求解”就是分别求出完全位于左半边或者完全位于右半边的最佳序列;

  • “合并”就是求出起点位于左半、终点位于右半的最大连续和序列,并和子问题的最优解比较。

“划分”和“递归求解”基本上都能理解,关键在于“合并” 步骤。既然起点位于左半,终点位于右半,则可以认为把这样的序列分成两个部分,然后独立求解:先寻找最佳起点,然后再寻找最佳终点。

这里要求一组数的最大序列和,我们可以把数组分成两半,求另外两半数据的最大序列和,如果还是不能够解决问题,我们就再分两半,直到我们很容易做出判断为止,此时我们的“划分”,“递归求解”结束。例如:[2,1,-1,5,-1],第一次“划分”后分成了两小块[2,1][-1,5,-1];很容易看出,[2,1]小块的最大序列和是为3[-1,5,-1]小块的最大序列和是3。那么[2,1,-1,5,-1]的最大序列和是多少呢?我们肉眼可见就是7。下面讲述一下“合并的过程”!

首先,我们需要明确一点,最大序列和有三种情况:

1.分两半之后,左边的最大序列和(简写“Lmax”)直接是整个序列的最大连续和;

2.分两半之后,右边的最大序列和(简写“Rmax”)直接是整个序列的最大连续和;

3.分两半之后,整个序列的最大连续和是整个序列的中间部位(简写“L+R”),即最大连续和包含左边的一部分数据和右边的一部分数据。

由上可得,[2,1,-1,5,-1]的最大连续序列和 = MAX(Lmax,Rmax,L+R)。

其次,我们要确定每次“划分”的分界点,这里用 m 表示,计算公式如下所示:

m=(序列长度-序列第一个值的索引)/2    # m 需要取整

例如:数组[2,1,-1,5,-1]第一次“划分”时,序列长度是 5,第一个序列值是 2,它的索引是 0。由公式可得,m=(5-0)/2=2 (需要取整)。

在“划分”时,遵循“左开右闭”的规则。以数组[2,1,-1,5,-1]为例,第一次“划分”的 m 值为 2,根据“左开右闭”的规则,可以划分为两小块[2,1][-1,5,-1],即索引值为 2 的值被右小块取到了。

然后,需要依次求取 Lmax、Rmax、L 和 R 的值:

  • 左小块[2,1]的 Lmax 值为 3;

  • 右小块[-1,5,-1]的 Rmax 值为 5;

  • L=以索引m-1为起点的左边序列和的最大值,索引每次向左移一步,再累加求和,对于左小块[2,1]L=max(1,1+2)=3

  • R=以索引m为起点的右边序列和的最大值,索引每次向右移一步,再累加求和,对于右小块[-1,5,-1]R=max(-1,-1+5,-1+5-1)=4

最后,进行合并求解,best=max(Lmax,Rmax,L+R)=max(3,5,3+4)=7。这里没有进行递归演示,其实在求解右小块[-1,5,-1]的 Rmax 值时,用到了公式best=max(Lmax,Rmax,L+R)

分治法流程演示

关键代码

经上面的演算,分治法的 C++ 代码直接推导为:

int maxsum(int* A,int x,int y){  // 返回数组在左闭右开区间[x,y)中最大连续和
    int v,L,R,maxs;
    if(y-x==1) return A[x];  // 只有一个元素,直接返回
    int m=x+(y-x)/2;  // 分治第一步:划分成[x,m)和[m,y)
    maxs=max(maxsum(A,x,m),maxsum(A,m,y));  //  分治第二步:递归求解
    v=0;L=A[m-1];  //  分治第三步:合并(1)——从分界点开始往左的最大连续和L
    for(int i=m-1;i>=x;i--) L=max(L,v+=A[i]);
    v=0;  //分治第三步:合并(2)——从分界点开始往右的最大连续和R
    R=A[m];
    for(int i=m;i<y;i++) R=max(R,v+=A[i]);
    return max(maxs,L+R);  //  把子问题的解与L和R比较
}

数据结构、算法嘛!讲究效率。虽然对于最大序列和问题,用分治法相比于传统的方法看上去很巧妙,时间复杂度上也还过得去,分治法的时间复杂度是 O(n logn) 。但是对于这道题,分治法还不是最高效的,算法还可以再优化,时间复杂度可以达到 O(n) 。由于本章讲的是分治法,这里我就不做过多的阐述了。

一个在下游疯狂打怪的少年。希望早点遇见大魔王。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值