`
cq520
  • 浏览: 164618 次
  • 性别: 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
分享到:
评论

相关推荐

    算法导论中文版

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

    数据结构与算法分析第二版 ---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章 ...

    数据结构与算法分析

     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 检验你的分析 ...

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

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

    数据结构与算法分析 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_2

    2.4 运行时间计算 2.4.1 一个简单的例子 2.4.2 一般法则 2.4.3 最大子序列和问题的求解 2.4.4 运行时间中的对数 2.4.5 检验你的分析 2.4.6 分析结果的准确性 小结 练习 参考文献第3章 表、栈和队列...

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

    2.4 运行时间计算 2.4.1 一个简单的例子 2.4.2 一般法则 2.4.3 最大子序列和问题的求解 2.4.4 运行时间中的对数 2.4.5 检验你的分析 2.4.6 分析结果的准确性 小结 练习 参考文献第3章 表、栈和队列...

    python 实现 数学中经典问题 课程设计 代码

    3N加1问题,绝对值,加法,无算术加法,约数和,分配数,弧长,面积,曲线下...卡德恩算法,Karatsuba算法,克里希纳穆蒂数,第K个字典序排列,非常大数的最大值,最大子数组和,最小公倍数,线段长度,Liouville函数等

    leetcode2-Algorithms:算法导论

    最大子数组问题 chapter 6 堆 数据结构 堆排序 chapter 7 快速排序 快速排序(随机增强版) chapter 8 计数排序 桶排序 基数排序 chapter 9 期望为线性时间的选择算法(基于快排思想) 最坏为 O(n) 的选择选择算法...

Global site tag (gtag.js) - Google Analytics