最大子序列和问题之算法优化

算法一:穷举式地尝试所有的可能

int maxSubsequenceSum(const int a[], int n) { int i, j, k; int thisSum, maxSum = 0; for (i = 0; i < n; i++) for (j = i; j < n; j++) { thisSum = 0; for (k = i; k < j; k++) thisSum += a[k]; if (thisSum > maxSum) maxSum = thisSum; } return maxSum; }

算法复杂度为O(n^3)(三重for循环)


算法二:算法一的改进

int maxSubsequenceSum(const int a[], int n) { int i, j; int thisSum, maxSum = 0; for (i = 0; i < n; i++) { thisSum = 0; for (j = i; j < n; j++) { thisSum += a[j]; if (thisSum > maxSum) maxSum = thisSum; } } return maxSum; }

该算法去除了算法一中不必要的计算,时间复杂度为O(n^2)(两重for循环)。


算法三:分治(divide-and-conquer)策略

分治策略:

分:把问题分成若干个(通常是两个)规模相当的子问题,然后递归地对它们求解。

治:将若干个问题的解4合并到一起并可能再做少量的附加工作,最后得到整个问题的解。

在这个问题中,最大子序列和可能在三处出现:即左半部序列、右半部序列、穿过中部从而占据左右两半部分的序列。前两种情况可以通过递归求解。而递归的基准情况(base cases)是序列只有一个元素(left == right),若该元素大于0,则返回该元素,否则返回0。第三种情况的最大和可以通过分别求出左边部分(包含左半部分最后一个)的最大和以及右边部分(包含右边部分的第一个)的最大和,再将它们相加得到。

int maxSubsequenceSum(const int a[], int left, int right) { int i, mid, maxLeftSum, maxRightSum; int maxLeftBorderSum, leftBorderSum; int maxRightBorderSum, rightBorderSum; if (left == right) { /*基准情况*/ if (a[left] >= 0) return a[left]; else return 0; } mid = left + (right - left) / 2; maxLeftSum = maxSubsequenceSum(a, left, mid); /*左半部分的最大和*/ maxRightSum = maxSubsequenceSum(a, mid+1, right); /*右半部分的最大和*/ /*下面求穿过中点的最大和*/ maxLeftBorderSum = 0, leftBorderSum = 0; for (i = mid; i >= left; i--) /*中点及其以左的最大和*/ { leftBorderSum += a[i]; if (leftBorderSum > maxLeftBorderSum) maxLeftBorderSum = leftBorderSum; } maxRightBorderSum = 0, rightBorderSum = 0; for (i = mid+1; i <= right; i++) /*中点以右的最大和*/ { rightBorderSum += a[i]; if (rightBorderSum > maxRightBorderSum) maxRightBorderSum = rightBorderSum; } /*返回三部分中的最大值*/ return max3(maxLeftSum, maxRightSum, maxLeftBorderSum+maxRightBorderSum); } int max3(int a, int b, int c) { int maxNum = a; if (b > maxNum) maxNum = b; if (c > maxNum) maxNum = c; return maxNum; }

以序列2,4,-1,-5,4,-1为例,其左半部分最大和为2 + 4 = 6;右半部分最大和为4,穿过中心的最大和为(-1 + 4 + 2)+ (-5 + 4)= 0。故该序列的最大子序列和为max(6,4,0)= 6。 时间复杂度分析: 假设T(n)为求解大小为n的最大子序列和问题所花费的时间。当n = 1是,T(1) = O(1);当n > 1时,两次递归花费的总时间为2T(n/2),两个并列的for循环花费的时间是O(len(left)+len(right)) = O(n),一共为2T(n/2)+O(n)。综上可列如下方程组:

T(1) = 1 T(n) = 2T(n/2) + O(n)

事实上,上述方程组常常通用于分治算法,由方程组可算出T(n) = O(nlogn)。


算法四:

算法三利用递归较好的解决了最大子序列和问题,但仔细分析,在递归过程中,同一个元素很可能多次被操作,有没有更高效的算法?先上代码

int maxSubsequenceSum(const int a[], int n) { int i; int maxSum, thisSum; maxSum = thisSum = 0; for (i = 0; i < n; i++) { thisSum += a[i]; if (thisSum > maxSum) maxSum = thisSum; else if (thisSum < 0) thisSum = 0; } return maxSum; }

可以简单的分析出上述代码的时间复杂度是O(n),比前三种都高效。它为什么是正确的?从直观上理解:首先for循环的if语句保证了每次更新后最大和保存在maxSum中,而我们从i = 0开始扫描,假设扫描到i = t(t < n),且此时的最大和已经保存在maxSum中,而当前的和(thisSum)如果大于0,不管当i > t的元素大小如何,加上thisSum总会使之后的和变大,而如果thisSum小于0,肯定会使之后的和变小,既然还会变小,那干脆就重新来过(thisSum = 0),有些另起炉灶的意味。


该算法一个附带的优点是,它只对数据进行一次的扫描,一旦a[i]被读入并被处理,它就不再需要记忆。因此,如果数组在磁盘或磁带上,它就可以被顺序读入,在主存中不必储存数组的任何部分。不仅如此,在任意时刻,该算法都能对它已经读入的数据给出子序列问题的正确答案(其他算法即前三种不具有这个特性)。具有这种特性的算法叫做联机算法(online algorithm)。仅需要常量空间并以线性时间运行的online algorithm几乎是完美的算法。————《数据结构与算法分析》(中文版第二版)

原文发布于微信公众号 - Spark学习技巧(bigdatatip)

原文发表时间:2018-05-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏码匠的流水账

java9系列(五)Stack-Walking API

java9新增这个类的目的是提供一个标准API用于访问当前线程栈,之前只有Throwable::getStackTrace、Thread::getStackTr...

421
来自专栏码匠的流水账

聊聊HystrixThreadPool

hystrix-core-1.5.12-sources.jar!/com/netflix/hystrix/HystrixThreadPool.java

771
来自专栏c#开发者

XML Encryption in .Net

XML Encryption in .Net One of the new features being introduced with the Whidbey...

4367
来自专栏Pulsar-V

Save Camera Document

#pragma once #include "HCCamera.h" #include <time.h> #include <cstdio> #incl...

2828
来自专栏码匠的流水账

spring security reactive获取security context

本文主要研究下reactive模式下的spring security context的获取。

1762
来自专栏WOLFRAM

错觉艺术的巅峰,错觉图形大师M.C. Escher的不可能方块的可能模型

1333
来自专栏linux驱动个人学习

高通Audio中ASOC的machine驱动

ASoC被分为Machine、Platform和Codec三大部分,其中的Machine驱动负责Platform和Codec之间的耦合以及部分和设备或板子特定的...

9764
来自专栏增长技术

App Guide相关

##TourGuide https://github.com/worker8/TourGuide

702
来自专栏余生开发

echarts太阳分布图-饼图来回穿梭

var dom = document.getElementById("container");

1172
来自专栏前端儿

Web 前端颜色值--字体--使用,整理整理

颜色值 CSS 颜色使用组合了红绿蓝颜色值 (RGB) 的十六进制 (hex) 表示法进行定义。对光源进行设置的最低值可以是 0(十六进制 00)。最高值是 2...

2282

扫码关注云+社区