最大子序列和问题之算法优化
算法一:穷举式地尝试所有的可能
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几乎是完美的算法。 ————《数据结构与算法分析》(中文版第二版)
- [译]Laravel 5.0 之事件及处理程序
- 自相关与偏自相关的简单介绍
- [译]Laravel 5.0 之命令及处理程序
- Deep Photo Styletransfer的一种纯Tensorflow实现,教你如何转换图片风格
- 如何提前体验 Laravel 5.5
- Laravel 4 小技巧两则
- [译]Laravel 5.0 之 ValidatesWhenResolved
- Python机器学习的练习七:K-Means聚类和主成分分析
- [译]Laravel 5.0 之方法注入
- [译]Laravel 5.0 之 Middleware (Filter-Style)
- [译]Laravel 5.0 之目录结构与命名空间
- Python机器学习的练习六:支持向量机
- [译]Laravel 5.0 之路由缓存
- [译]Laravel 5.0 之 表单验证类 (Form Requests)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 数据库备份和恢复
- 在jupyter里面运行conda虚拟环境的R
- 重新夺回对 /etc/resolv.conf 的控制权
- plsql
- 打卡群2刷题总结1008——环形链表
- Oracle数据库的对象
- 打卡群刷题总结1008——加油站
- 打卡群刷题总结1005——跳跃游戏
- 真是活久见,在 Minecraft 的虚拟游戏里竟然还能管理 Kubernetes!
- 打卡群2刷题总结1007——反转链表
- 打卡群2刷题总结1001——两数之和 II - 输入有序数组
- 复杂一点的SQL语句
- PL/SQL Developer连接本地Oracle 11g 64位数据库
- 打卡群刷题总结1007——买卖股票的最佳时机 II
- 事务Transaction