分治策略结合递归思想求最大子序列和

分治策略结合递归思想求最大子序列和

大家好,又见面了,我是全栈君,祝每个程序员都可以多学几门语言。

我的主力博客:半亩方塘

对于 《数据结构与算法分析——C语言描写叙述》 一书第 20 页所描写叙述的算法 3,相信会有非常多人表示不怎么理解,以下我由详细问题的求解过程出发,谈谈我自己的理解:

首先,什么是分治法呢?所谓 分治法,就是 将一个问题的求解过程分解为两个大小相等的子问题进行求解,假设分解后的子问题本身也能够分解的话,则将这个分解的过程进行下去,直至最后得到的子问题不能再分解为止,最后将子问题的解逐步合并并可能做一些少量的附加工作,得到最后整个问题的解。在求解原来整个问题的算法思想,与求解每个子问题的算法思想全然同样,则能够用到递归来解决问题,在我的博文 关于递归的一些简单想法 中,曾指出,当我们要解决的问题有着 重复运行的基本操作 的时候,能够考虑使用递归,在这里,原来的整个的问题与每个分解后子问题都有着重复运行的算法思想,这就是一个基本操作,所以能够用递归实现,关于递归,在我的博文 由递归思想处理问题的基本原则 中,给出了有关递归思想的部分描写叙述。

回到我们标题所阐述的问题,求最大子序列和,我们能够将求最大子序列和的序列分解为两个大小相等的子序列,然后在这两个大小相等的子序列中,分别求最大子序列和,假设由原序列分解的这两个子序列还能够进行分解的话,进一步分解,直到不能进行分解为止,使问题逐步简化,最后求最简化的序列的最大子序列和,沿着分解路径逐步回退,合成为最初问题的解。我们知道,最大子序列和仅仅可能在三个位置求出:

  1. 序列的左半部分的最大子序列和
  2. 序列的右半部分的最大子序列和
  3. 横跨序列左半部分和右半部分得到的最大子序列和:对包括左半部分的最后一个元素的最大子序列和以及包括右半部分第一个元素的最大子序列和二者求和所得到的值
  4. 比較三者的大小,最大者即为所求的最大子序列和

以下我们通过详细的实例来细致体会一下这样的 分治 的算法思想。

假设我们要求以下序列的最大子序列:

4 -3 5 -2 -1 2 6 -2

将这个子序列存放在一个数组中来考虑,则有 int a[8] = {4, -3, 5, -2, -1, 2, 6, -2}

依照分治法的思想,首先将这个序列分为左右两半部分,分界点 是 序列首元素在数组中的下标和尾元素在数组中的下标的和除以 2 所得到的下标值。在上面的序列中,分界点就是 (0 + 7)/2 = 3,也就是说分界点是下标为 3 的元素,即 -2,依照这个分界点,将序列分为两半部分,左半部分子序列为:

4 -3 5 -2

右半部分子序列为:

-1 2 6 -2

我们要在分解后所形成的两个子序列中,分别求最大子序列和,我们最好还是用左半部分的子序列来分析一下:

4 -3 5 -2

求这个左半部分子序列的最大子序列和,我们还能够将这个左半部分子序列依照上面提到的方法分解为左半部分和右半部分,由上面的分解方法,得到分界点为下标是 1 的元素,即 -3,由此我们得到左半部分的子序列为:

4 -3

右半部分的子序列为:

5 -2

上面得到的左半部分子序列和右半部分子序列要分别求最大子序列和,相同,这两个子序列仍然能够分解为左半部分和右半部分,针对上面得到的左半部分的子序列,由上面的分解方法,这里省略分解过程,得到最后的左半部分子序列为:

4

右半部分子序列为:

-3

针对 5 -2 ,得到左半部分的子序列为:

5

右半部分的子序列为:

-2

针对上面分解所得到的子序列,每个子序列仅仅含有一个元素,这是子序列的最简情形,即首元素在数组中的下标和尾元素在数组中的下标同样(首元素和尾元素为同一元素),此时序列不能再进行分解了( 这样的情况将得到递归的基准情形 )。

考虑上面最后得到的不能分解的子序列,依照最先提到的求最大子序列和的算法思想(1.2.3.4.),能够得到例如以下结论:

显然,针对序列 4 -3,左半部分子序列的最大子序列和是 4(是左半部分子序列本身);右半部分子序列的最大子序列和是 -3(是右半部分子序列本身);左半部分子序列中包括最后一个元素 4 的最大子序列和为 4,右半部分子序列中包括第一个元素 -3 的最大子序列和为 -3,二者求和得到横跨左半部分和右半部分的最大子序列和是 4 + (-3) = 1;在这三者中,左半部分的最大子序列和 4 是最大的,由此得到序列 4 -3 中,最大子序列和是 4。同理,针对序列 5 -2,我们能够用相同的方法得到最大子序列和为 5。

而序列 4 -3 和序列 5 -2 又各自是序列 4 -3 5 -2 的左半部分子序列和右半部分子序列,由此我们得到了序列 4 -3 5 -2 的左半部分子序列的最大子序列和为 4;右半部分的最大子序列和为 5;左半部分子序列中,包括最后一个元素 -3 的最大子序列和是 -3 + 4 = 1,右半部分子序列中,包括第一个元素 5 的最大子序列和为 5,二者求和得到横跨左半部分和右半部分的最大子序列和为 1 + 5 = 6,三者中 6 是最大的,由此,我们得到序列 4 -3 5 -2 的最大子序列和为 6。而序列 4 -3 5 -2 恰好是原序列的左半部分子序列,按照上述求原序列左半部分最大子序列和的方法,同理我们能够非常轻松地求出原序列右半部分子序列 -1 2 6 -2 的最大子序列和为 8(最好还是在草稿纸上演示一下这个过程),经过以上分析过程,我们得到:

原序列的左半部分子序列的最大子序列和是 6;原序列的右半部分子序列的最大子序列和为 8;在原序列的左半部分子序列中,包括最后一个元素 -2 的最大子序列和是 -2 + 5 + (-3) + 4 = 4,在原序列的右半部分子序列中,包括第一个元素 -1 的最大子序列和是 -1 + 2 + 6 = 7,二者求和得到横跨左半部分与右半部分的最大子序列和是 4 + 7 = 11, 6 8 11 中最大的为 11,由此我们能够得到原序列的最大子序列和为 11。

由以上分析能够看到,求一个序列的最大子序列和,是依照分治法的思想将所给序列逐步分解,分解到不能分解为止(即递归的基准情形),然后再逐步回退,分别求各个分解的子序列的最大子序列和,最后将全部的结果合成在一起得到最后的结果,这里涉及到一个 重复进行的基本操作 ,就是 分别求各个分解的子序列的最大子序列和 。

经过对以上个例的分析,我相信能够更好地理解以下由分治法和递归思想相结合的求最大子序列和的代码了:

static int MaxSubSum(const int A[], int Left, int Right)
{
    if (Left == Right)    /* 递归的基准情形 */
        return a[Left];

    int Center;
    Center = (Left + Right) / 2;   /* 求分界点 */
    int MaxLeftSum;
    MaxLeftSum = MaxSubSum(A, Left, Center);   /* 递归,求左半部分子序列的最大子序列和 */
    int MaxRightSum;
    MaxRightSum = MaxSubSum(A, Center + 1, Right);  /* 递归,求右半部分子序列的最大子序列和 */

    /* 求横跨左半部分和右半部分的最大子序列和 */
    /* 首先是左半部分子序列中包括最后一个元素的最大子序列和 */
    int MaxLeftBorderSum = A[Center], LeftBorderSum = A[Center];
    for (int i = Center - 1; i >= Left; --i) {
        LeftBorderSum += A[i];
        if (LeftBorderSum > MaxLeftBorderSum)
            MaxLeftBorderSum = LeftBorderSum;
    }

    /* 接着是右半部分子序列中包括第一个元素的最大子序列和 */
    int MaxRightBorderSum = A[Center + 1], RightBorderSum = A[Center + 1];
    for (int i = Center + 2; i <= Right; ++i) {
        RightBorderSum += A[i];
        if (RightBorderSum > MaxRightBorderSum)
            MaxRightBorderSum = RightBorderSum;
    }

    /* Max3 返回左、右半部分子序列的最大子序列和以及横跨左、右半部分的最大子序列和中的最大者 */
    return Max3(MaxLeftSum, MaxRightSum, 
            MaxLeftBorderSum + MaxRightBorderSum);
}

int MaxSubsequenceSum(const int A[], int N)   /* 求最大子序列和 */
{
    return MaxSubSum(A, 0, N - 1);
}

关于測试代码及其最后的执行结果请移步至 
我的GitHub

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/118410.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • Oracle decode函数

    Oracle decode函数一两种语法格式1decode(expression,value,result1,result2)如果expression=value,则输出result1,否则输出result2例子:(1+2=3,输出a)(1+2≠4,输出b)2decode(expre…

    2022年7月25日
    7
  • origin两个柱状图重叠在一起怎么分开_origin柱状图重叠怎么分开

    origin两个柱状图重叠在一起怎么分开_origin柱状图重叠怎么分开今天论文作图的时候,遇到一个问题:输入两列y数据,想绘制柱状图,但是它们重叠了,我希望他们并列:解决方法:右键点击绘图内容之后选中两列数据,再点击“成组”即可…

    2022年9月27日
    0
  • python hashlib_Python hashlib模块实例使用详解

    python hashlib_Python hashlib模块实例使用详解这篇文章主要介绍了Pythonhashlib模块实例使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下hashlib模块主要的作用:加密保护消息安全,常用的加密算法如MD5,SHA1等。1、查看可用的算法有哪些hashlib_algorithms.py#!/usr/bin/envpython#-*-coding:utf-8-*-i…

    2022年6月6日
    27
  • 谈谈内存映射文件[通俗易懂]

    谈谈内存映射文件[通俗易懂]http://blog.csdn.net/ithzhang/article/details/7001650内存映射文件允许开发人员预订一块地址空间并为该区域调拨物理存储器,与虚拟内存不同的是,内存映射文件的物理存储器来自磁盘中的文件,而非系统的页交换文件。将文件映射到内存中后,我们就可以在内存中操作他们了,就像他们被载入内存中一样。内存映射文件主要有三方面的用途:1:系

    2022年6月17日
    31
  • JSP页面通过URL传递中文参数出现中文乱码问题

    JSP页面通过URL传递中文参数出现中文乱码问题做项目时遇到这个问题问题描述:通过点击这个参数名并进行跳转control层,进行参数接受并处理。${cs.name}里面包含中文&lt;a href="${pageContext.request.contextPath}/recommend_navCategory?name=${cs.name}"&gt;${cs.name}&lt;/a&gt;服务器端:ISO-8859-1是tomca…

    2022年6月13日
    29
  • sop流程图模板_标准作业流程SOP详解,附流程图绘制规范,不愁不会画!

    sop流程图模板_标准作业流程SOP详解,附流程图绘制规范,不愁不会画!原标题:标准作业流程SOP详解,附流程图绘制规范,不愁不会画!什么是SOP?StandardOperationProcedure所谓SOP,是StandardOperationProcedure三个单词中首字母的大写,即标准作业程序。是以文件的形式描述作业员在生产作业过程中的操作步骤和应遵守的事项;是作业员的作业指导书;是检验员用于指导工作的依据。“制造”就是以规定的成本、规定的工时…

    2022年5月26日
    64

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号