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

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

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

我的主力博客:半亩方塘

对于 《数据结构与算法分析——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)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 目录爆破工具_dir是什么意思中文

    目录爆破工具_dir是什么意思中文在github中下下来网址:https://github.com/maurosoria/dirsearch下载下来我是用的kail所以我把它也放在kail里面了dirsearch需要python3,kail中是自带的进入dirsearch目录后执行./dirsearch.py-u10.0.3.45-ephp-u指定url-…

    2022年10月5日
    4
  • java实习生面试题_java实习生面试题.doc

    java实习生面试题[标签:标题]实习生在面试Java岗位时,做好面试准备很重要,那么你了解面试题目了吗?下面阳光网小编已经为你们整理了java实习生面试题,希望可以帮到你。java实习生面试题11.Java有那些基本数据类型,String是不是基本数据类型,他们有何区别。Java语言提供了八种基本类型:六种数字类型(四个整数型,两个浮点型)字节型byte8位短整型short16位整型in…

    2022年4月18日
    53
  • Zookeepers_docker workdir

    Zookeepers_docker workdir文章目录Curator客户端创建会话创建节点获取节点和数据更新数据删除节点事务节点存在事件监听其他工具类开发测试Curator客户端Curator包含了几个包:curator-framework:对zookeeper的底层api的一些封装curator-client:提供一些客户端的操作,例如重试策略等curator-recipes:封装了一些高级特性,如:Cache事件监听、选举…

    2025年8月6日
    2
  • linux下载文件命令sz_linux下载整个文件夹命令

    linux下载文件命令sz_linux下载整个文件夹命令wget是linux下一个从网络上自动下载文件的常用自由工具。它支持HTTP,HTTPS和FTP协议,可以使用HTTP代理。一般的使用方法是:wget+空格+参数+要下载文件的url路径,例如:1wget http://www.linuxsense.org/xxxx/xxx.tar.gz-c参数,这个也非常

    2022年8月24日
    6
  • Java dom4j生成和解析XML

    Java dom4j生成和解析XMLdom4j使用:需先导入dom4j对应的jar,本章用的是dom4j-1.6.1.jar优点:DOM4J使Java开发的灵活性和XML解析代码易于维护dom4j相关操作类Document:表示整个xml文档,是一个树形结构Eelment:表示一个xml的元素,提供方法操作其子元素,它的文本,属性和名称空间Attribute:表示元素的属性Node:表示元素,属性do…

    2022年6月21日
    29
  • SQL学习笔记(基础部分)

    SQL学习笔记(基础部分)SQL学习笔记基础部分SQL语法数据库语言对大小写不敏感,用分号分隔每条数据库语言SQL通用数据类型SQL开发,在创建SQL表时要确定每个列要存储的数据类型,每一列都需要一个列名和确定的数据类型常见数据类型自行查询一些重要的SQL命令selectupdatedeleteinsertintocreatedatabasealterdatab

    2022年8月20日
    8

发表回复

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

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