`
cq520
  • 浏览: 165436 次
  • 性别: Icon_minigender_1
  • 来自: 长沙
社区版块
存档分类
最新评论

最大子序列和问题从O(N^3)到线性的算法

阅读更多

       算法复杂度,从开始学习算法分析之后就一直在讨论着这个问题,很多人都认为,计算机相关人才只是“高级蓝领”,“技术民工”,那为什么计算机的大牛们依然乐此不疲呢?我想,是因为他们发现了思考的乐趣。

       有时候,稍加思考,你所做的事情就会变得格外的美妙,有时候,更简短的代码带来的却是更高的执行效率,生活,恰是需要这样的点睛之笔。

       好了,前奏铺垫的有点长,下面进入正题,通过对大家所熟知的一道题的分析,最大子序列和的问题,让我们来初步感受一下编程的美丽:

       题目是这样描述的,给定指定的整数序列(可能是负数)A1A2…..An,寻找(并标识)使得AiAj的连续的和值最大的子序列。

       首先初步分析一下,假设输入的是1,3,-5,6,-3这五个数,那么最大的明显是13-5,6所组成的子序列,包含四项,数组下标从0开始,到3为止。而对于一个全为负数的序列,最长公共子序列的和值应该是0,理由在于,由0个整数所组成的空子序列也是一个子序列。

       问题分析到这里,大家应该对问题有了初步了解了,那么对于一道寻值的问题,我们一般第一反应所想到的恐怕就是“暴力求解”了,将每一个可能的子序列都找出来,然后一一比对,比如一个长度为6的序列,他的子序列的长度就有067种大情况,再把每一种情况细分(如长度为3的子序列,数组的下标索引可以是0,1,2,3),把所有的情况都列出来,这样毫无疑问是可以找到答案的,通过这样的分析,我们的O(N^3)的算法就出炉了,如下:

      

/**
* O(N^3)算法
* @return 最大公共子序列的和
*/
public int maxSubsequenceSum1(int a[]){
	int maxSum=0;
	for(int i=0;i<a.length;i++){
		for(int j=i;j<a.length;j++){
			int thisSum=0;
			for(int k=i;k<=j;k++){
				thisSum+=a[k];
				if(thisSum>maxSum){
					maxSum=thisSum;
					seqStart=i;//最大公共子序列索引的起始坐标
					seqEnd=j;//最大公共子序列索引的末尾坐标
				}
			}
		}
	}
	System.out.println(seqStart);
	System.out.println(seqEnd);
	return maxSum;
}

       不难看出,这个算法的运行时间完全由最内层的for循环决定,而里层执行的次数正好等于满足1<=i<=k<=j<=N的有序三元组(i,j,k)的个数,大家可以自己用数学公式证明一下,这个有序三元组的个数是N(N+1)(N+2)/6。这种“暴力求解”的优点是,编码非常简单,理解起来也很容易,算法越简单就越容易正确地编程实现。不过这样的方法很明显效率偏低,三层嵌套因此导致算法的复杂度是立方级的,不过我们要注意的是,三个连续的循环表现出的是线性行为,也就是说,不是简单的N*N*N的算法。那么我们是不是可以去掉一个循环来提高程序的执行效率呢?请看以下分析:

       很明显,上面的算法有很多不必要的计算,假设我们已经计算出了i,…,j-1的和,那么计算子序列i,…j的和是不是很简单呢?只需要再进行一次计算。立方算法恰恰就丢掉了这一个信息,如下的改进算法,使用的是两个而不是三个循环嵌套:

 

/**
 * O(N^2)算法
 */
public int maxSubsequenceSum2(int a[]){
	int maxSum=0;
	for(int i=0;i<a.length;i++){
		int thisSum=0;
		for(int j=i;j<a.length;j++){
			thisSum+=a[j];
			if(thisSum>maxSum){
				maxSum=thisSum;
				seqStart=i;
				seqEnd=j;
			}
		}
	}
	System.out.println(seqStart);
	System.out.println(seqEnd);
	return maxSum;
} 

       很多人可能做到上面一步就停止了,我们已经完成了从N^3N^2的飞跃,那么是不是还可以删除一个循环来达到线性算法呢?想法是美妙的,但是具体实施起来却没有那么简单。假设Ai,j是满足Si,j<0的第一个子序列,如下图(图画的不好,请见谅):

      

       容易证明,在假设的情况下,如果q>j,那么Ai,q不是最大连续子序列,因为序列的索引完全可以从下一个大于0的数开始。所以当我们得到了第一个小于0的子序列之后,完全可以从这个序列里面跳出来,继续计算接下来的序列段,避免重复的计算,那么这一次的算法就需要大家仔细的思考一下了,下面给出一种方法:

       

/**
 * 线性算法
 */
public int maxSubsequenceSum3(int a[]){
	int maxSum=0;
	int thisSum=0;
	for(int i=0,j=0;j<a.length;j++){
		thisSum+=a[j];
		if(thisSum>maxSum){
			maxSum=thisSum;
			seqStart=i;
			seqEnd=j;
		}
		else if(thisSum<0){
			thisSum=0;
			i=j+1;
		}
	}
	System.out.println(seqStart);
	System.out.println(seqEnd);
	return maxSum;
}

 

       虽然这种算法通过跳过不可能的条件而达到了更高的效率,不过,相比“暴力求解”来说,正确性就没那么明显了,相反,其中的很多条件都需要我们证明,我们已经通过一个简单的数学运算证明了它的正确性。数学将我们带入了编程的世界,又为我们提供了坚实的基础。编程其实很多时候不是方法的照搬,通过思考,我们能够得到更优的方法,就像我们热爱探索世界的眼睛。

       走进编程的世界,感受编程之美吧。

 

  • 大小: 17.7 KB
分享到:
评论

相关推荐

    第一次算法初步.pdf

    暴力枚举的时间复杂度为O(n^3),优化枚举通过减少一层循环降低到O(n^2),而贪心法则只需O(n)的时间复杂度,通过每次累加当前元素并更新最大子数组和,当和变为负数时清零重新开始。 作业题1设计一个队列,要求支持...

    计算机算法设计与分析练习题.docx

    3. 使用线性时间选择算法,例如Median of Medians算法,可以在O(n)时间内找到带权中位数。 4. 邮局位置问题,带权中位数是最优解,最小化所有点到中位数的距离之和。 三.磁带存储问题 1. 贪心策略按程序长度非降序...

    算法导论中文版

     4.1 最大子数组问题  4.2 矩阵乘法的Strassen算法  4.3 用代入法求解递归式  4.4 用递归树方法求解递归式  4.5 用主方法求解递归式  4.6 证明主定理  4.6.1 对b的幂证明主定理  4.6.2 向下取整和...

    PTA习题:中国大学MOOC-陈越、何钦铭-数据结构-2017秋1

    * 最大子序列和问题的解决方法 * 时间复杂度的分析 线性结构 1. 两个有序链表序列的合并(15分) 这是一个典型的链表操作问题,要求学生合并两个有序链表。该问题的时间复杂度为O(n+m),其中n和m分别为两个链表的...

    912_18最全版本1

    - 最大子序列和问题可以通过动态规划解决,如Kadane's算法,它在每次迭代中比较当前和累积最大值,保持较大者,时间复杂度为`O(n)`。 - 伪代码描述大致如下: ```python max_sum = nums[0] current_sum = nums...

    《数据结构基础与算法分析》知识总结

    大O记法描述了算法最坏情况下的时间复杂度上界,小o记法则表示算法增长速度比某个函数更快,而Θ记法则表示算法的平均和最坏情况下的时间复杂度都在某个函数的上下界内。W记法是大O记法和小o记法的结合,表示算法的...

    java算法有哪些jif

    - 最大子数组和问题:找出数组中连续子数组的最大和。 5. **字符串匹配算法**: - 暴力匹配:简单地逐字符比较目标字符串与模式字符串,时间复杂度较高。 - KMP算法:避免了不必要的回溯,提高了匹配效率。 - ...

    现代计算机常用数据结构和算法

    此外,还有贪心算法、分治策略、回溯法、动态规划等设计技巧,它们广泛应用于各种复杂问题,如最短路径、最小生成树、最大子序列和等。 《Introduction to Algorithms》第二版详细介绍了这些概念,不仅涵盖了基本...

    数据结构与算法分析第二版 ---C语言描述(附加答案)

    算法分析2.1 数学基础2.2 模型2.3 要分析的问题2.4 运行时间计算2.4.1 一个简单的例子2.4.2 一般法则2.4.3 最大子序列和问题的解2.4.4 运行时间中的对数2.4.5 检验你的分析2.4.6 分析结果的准确性总结练习第3章 ...

    清华大学-912-2018年真题1

    4. 最大子序列和: - 可以使用Kadane's Algorithm解决,初始化两个变量,一个记录当前子序列和,一个记录全局最大和,遍历向量更新这两个变量。 - 伪代码:max_sum = current_sum = A[0],对于i从1到n-1,current_...

    数据结构与算法分析

     2.4.3 最大子序列和问题的解   2.4.4 运行时间中的对数   2.4.5 检验你的分析   2.4.6 分析结果的准确性   小结   练习   参考文献  第3章 表、栈和队列   3.1 抽象数据类型(ADT)  ...

    python 实现 leetcode 各种问题 课程设计 代码

    活动选择(Activity Selection) 备选列表排列(Alternative List Arrange) Davis–Putnam–Logemann–Loveland算法 Dijkstra银行家算法(Dijkstra ...最大子序列(Maximum Subsequence) 嵌套括号(Nested Brackets

    数据结构与算法分析_Java语言描述(第2版)

    2.4.3 最大子序列和问题的求解 2.4.4 运行时间中的对数 2.4.5 检验你的分析 2.4.6 分析结果的准确性 小结 练习 参考文献 第3章 表、栈和队列 3.1 抽象数据类型 3.2 表ADT 3.2.1 表的简单数组实现 3.2.2 简单链表 ...

    数据结构与算法分析Java语言描述(第二版)

    算法分析2.1 数学基础2.2 模型2.3 要分析的问题2.4 运行时间计算2.4.1 一个简单的例子2.4.2 一般法则2.4.3 最大子序列和问题的求解2.4.4 运行时间中的对数2.4.5 检验你的分析2.4.6 分析结果的准确性小结练习参考文献...

    数据结构与算法分析_Java语言描述(第2版)]

    算法分析2.1 数学基础2.2 模型2.3 要分析的问题2.4 运行时间计算2.4.1 一个简单的例子2.4.2 一般法则2.4.3 最大子序列和问题的求解2.4.4 运行时间中的对数2.4.5 检验你的分析2.4.6 分析结果的准确性小结练习参考文献...

    数据结构与算法分析_Java_语言描述

    参考文献 第2章 算法分析 2.1 数学基础 2.2 模型 2.3 要分析的问题 2.4 运行时间计算 2.4.1 一个简单的例子 2.4.2 一般法则 2.4.3 最大子序列和问题的解 2.4.4 运行时间中的对数 2.4.5 检验你的分析 ...

    清华20181

    4. 最大子序列和: * 说明算法。 * 使用伪代码描述算法。 * 分析算法的空间和时间复杂度。 组成原理 1. 判断题: * CPU 的主频越高,指令执行的越快。 * 内存逻辑地址连续的,物理地址不一定连续。 2. 填空题...

    数据结构与算法分析C描述第三版

     2.4.3 最大子序列和问题的解   2.4.4 运行时间中的对数   2.4.5 检验你的分析   2.4.6 分析结果的准确性   小结   练习   参考文献  第3章 表、栈和队列   3.1 抽象数据类型(ADT)   3.2 ...

Global site tag (gtag.js) - Google Analytics