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

我的主力博客:半亩方塘

对于 《数据结构与算法分析——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);
}

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

时间: 2024-10-07 12:24:20

分治策略结合递归思想求最大子序列和的相关文章

分治策略(求解递归式的方法)

分解:将原问题划分成形式相同的子问题,规模可以不等,对半或2/3对1/3的划分. 解决:对于子问题的解决,很明显,采用的是递归求解的方式,如果子问题足够小了,就停止递归,直接求解. 合并:将子问题的解合并成原问题的解. 这里引出了一个如何求解子问题的问题,显然是采用递归调用栈的方式.因此,递归式与分治法是紧密相连的,使用递归式可以很自然地刻画分治法的运行时间.所以,如果你要问我分治与递归的关系,我会这样回答:分治依托于递归,分治是一种思想,而递归是一种手段,递归式可以刻画分治算法的时间复杂度.

五大常见算法策略——递归与分治策略

摘要:递归与分治策略是五大常见算法策略之一,分治策略的思想就是分而治之,即先将一个规模较大的大问题分解成若干个规模较小的小问题,再对这些小问题进行解决,得到的解,在将其组合起来得到最终的解.而分治与递归很多情况下都是一起结合使用的,能发挥出奇效(1+1>2),这篇文章我们将先从递归说起,再逐渐向分治过渡,主要讲解方式是通过9个例题来说明问题的,问题都是根据难度由简到难,由浅入深,对递归与分治能有个大概的了解雏形,当然最重要还是要做大量练习才能掌握. 1.递归 我们第一次接触递归一般都是在初学C语

算法导论第四章分治策略编程实践(二)

在上一篇中,通过一个求连续子数组的最大和的例子讲解,想必我们已经大概了然了分治策略和递归式的含义,可能会比较模糊,知道但不能用语言清晰地描述出来.但没关系,我相信通过这篇博文,我们会比较清楚且容易地用自己的话来描述. 通过前面两章的学习,我们已经接触了两个例子:归并排序和子数组最大和.这两个例子都用到了分治策略,通过分析,我们可以得出分治策略的思想:顾名思义,分治是将一个原始问题分解成多个子问题,而子问题的形式和原问题一样,只是规模更小而已,通过子问题的求解,原问题也就自然出来了.总结一下,大致

分治策略 - 最大子序列问题

自开始学习算法起,我感觉就是跪着把<算法导论>的代码看一遍.理解一遍然后敲一遍...说实话自己来写并且要求时间复杂度达到要求,我肯定是不能做到的,但我想前辈们辛苦积累的研究成果贡献出来也是为了让后人少走一些弯路,所以我的作用就是把前辈们的成果学习之后加以理解,然后积累经验,领悟到他们解决问题时的思路和灵感.还有就是把个人理解后的知识存储在不会忘记的地方作为复习备用... 当然什么是写博客呢,我个人认为是把所学的知识加上自己的理解然后用较为通俗的语言来解释一遍,至少这样才有可能把学到的东西变为自

递归与分治策略(一)---算法设计与分析

递归与分治策略(一) 简而言之,递归就是自己调用自己. 递归算法:直接或者间接地调用自身的算法. 递归函数:用函数自身给出定义的函数. 注意:每个递归函数都必须有非递归定义的初始值,以确保递归函数完成计算. 下面通过两个例子来介绍递归的特点 例1 阶乘函数 阶乘函数递归地定义为: n!=1   (n=0) 或者 n!=n(n-1)!  (n>0) 下面用一段简单的Java代码实现 这里是递归实现: public static int facterial(int n) { if (n == 0)

分治策略---求最大子数组

只有当数组中包含负数时,最大子数组问题才有意义.如果所有元素都是非负的,最大子数组问题没有任何意义,因为整个数组和肯定是最大的 1 public class FindMaxSubArrayDemo { 2 public static void main(String[] args) { 3 int[] arr = {13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7}; 4 int[] result_arr = fi

计算机算法设计与分析之递归与分治策略——二分搜索技术

递归与分治策略 二分搜索技术 我们所熟知的二分搜索算法是运用分治策略的典型例子,针对这个算法,先给出一个简单的案例. 目的:给定已排好序的n个元素a[0:n-1],现要在这n个元素中找出一特定的元素x. 我们首先想到的最简单的是用顺序搜索方法,逐个比较a[0:n-1]中元素,直至找出元素x或搜索遍整个数组后确定x不在其中.这个方法没有很好地利用n个元素已排好序的这个条件,因此在最坏的情况下,顺序搜索方法需要O(n)次比较. 而二分搜索方法充分利用了元素间的次序关系,采用分治策略,可在最坏情况下用

分治递归:求数组元素的最大值,最小值

//分治递归,求数组元素的最大值,最小值 /** * 保存产生的最大值,最小值 * @author Administrator * */ public class Values { private int max; private int min; public Values(int max,int min){ this.max=max; this.min=min; } public int getMax() { return max; } public void setMax(int max)

算法分析之递归与分治策略

递归与分治策略 直接或间接地调用自身的算法称为递归算法.用函数自身给出定义的函数称为递归函数. 在计算机算法设计与分析中,使用递归技术往往使函数的定义和算法的描述简洁且易于理解. 例1 阶乘函数 可递归地定义为:其中:n=0 时,n!=1为边界条件n>0 时,n!=n(n-1)!为递归方程边界条件与递归方程是递归函数的二个要素,递归函数只有具备了这两个要素,才能在有限次计算后得出结果. 例2 Fibonacci数列无穷数列1,1,2,3,5,8,13,21,34,55,…,被称为Fibonacc