最近看了求解最大子序列和的几种算法,为了巩固自己的算法基础,特整理一下,并分析相关算法的复杂度,理清自己的思路。
最大子序列和问题描述:对于序列a[N] 来说,求其
∑jk=ia[k]
的最大和。
算法一 :基于穷举的思想,将所有的a[k]全部遍历一边,每个子序列的起点均为
i
,终点为
//求最大子序列和的函数,穷举1。
int maxSubQueneSum(int [] a){
int maxSubSum = 0, subSum = 0;
for(int i = 0; i < a.length; i++){
for(int j = i; j < a.length; j++){
for(int k = i; k <= j; k++){
subSum = subSum + a[k];
}
if(subSum > maxSubSum)
maxSubSum = subSum;
}
}
return maxSubSum;
}
该算法利用了三个for循环嵌套来达到遍历穷搜的目的,其复杂度为
O(N3)
,复杂度较高。
算法二:该算法基于上面的算法一去掉内部的第三层for循环,是基于
∑jk=ia[k]=a[j]+∑j−1k=ia[k]
的思想,因为算法一有许多重复的计算,例如:计算a[1]+a[2]和计算a[1]+a[2]+a[3]均重复计算了a[1]+a[2]。而算法二会去除这样的重复。
//求最大子序列和的函数2
int maxSubQueneSum2(int [] a){
int maxSubSum = 0, subSum = 0;
for(int i = 0; i < a.length; i++){
for(int j = i; j < a.length; j++){
subSum = subSum + a[j];
if(subSum > maxSubSum)
maxSubSum = subSum;
}
}
return maxSubSum;
}
很容易分析出该算法的复杂度为
O(N2)
。
上面的两种算法都是基于穷搜而得到的。由上面的两个算法我能够想到一句很好的话:计算任何事情不要超过一次。觉得这个思想很受用。
算法三:算法三就有一定的技巧性了,利用的是分治的思想,分而治之,也就是说将序列人为地从中间一分为二,则其最大和子序列必存在于三个位置,一个是位于左半部的子序列,一个是位于右半部的子序列,一个是位于在左半部和右半部之间过渡的子序列,左半部和右半部子序列又可以分而治之,继续分割直至为一个元素为止。对于过渡位置的子序列可以如下求出:
对于左半部来说,以左半部最后一个元素为起点向左寻找,找出最大子序列和;同理对于右半部子序列,以第一个元素为起点向右寻找,找出最大子序列和。两者相加即可求出过渡位置的最大子序列和。依次递归调用即可。
//求最大子序列和的函数3
int maxSubQueneSum3(int [] a, int left, int right){
//递归的终止条件
if(left == right)
return a[left];
int center = (left + right)/2;
//求左半部子序列和
int maxLeftSubBoundSum = maxSubQueneSum3(a, left, center);
//计算右半部子序列和
int maxRightSubBoundSum = maxSubQueneSum3(a,center+1,right);
//计算中间过渡部分的最大子序列和
int leftBoundSum = 0, maxLeftBoundSum = 0;
for(int i = center; i<= left; i--){
leftBoundSum = leftBoundSum + a[i];
if(leftBoundSum > maxLeftBoundSum)
maxLeftBoundSum = leftBoundSum;
}
int rightBoundSum = 0, maxRightBoundSum = 0;
for(int i = center; i<= left; i--){
rightBoundSum = rightBoundSum + a[i];
if(rightBoundSum > maxRightBoundSum)
maxRightBoundSum = rightBoundSum;
}
int maxMidSubSum = maxLeftBoundSum + maxRightBoundSum;
//求出三者中最大的一个子序列和
return max(maxLeftSubBoundSum, maxRightSubBoundSum, maxMidSubSum);
}
private int max(int a, int b, int c) {
// TODO 自动生成的方法存根
return a>b?(a>c?a:b):(b>c?b:c);
}
该算法通过递归调用而得,设该函数本身的花费为
T(N)
,出去常数项花费,求左半部分的最大子序列和花费为
T(N/2)
右半部分的最大子序列和花费为
T(N/2)
,在计算中间过渡部分的最大子序列和是会将N个数全部遍历一边,所以花费为
O(N)
,因此总的花费为
T(N)=2T(N/2)+O(N)
。
所以复杂度为
O(Nlog2N)
。
算法四: 该算法取的是这么一种思想:如果
a[i]
是负数,则最大子序列和绝不会是以
a[i]
开头的,同理如果
a[i]
到
a[j]
(
a[j]
是使得
a[i]
到
a[j]
为负的第一个数)和为负数,则
a[i]
到
a[j]
不可能是最大子序列和的前缀,所以可以将最大子序列和的起始位置推到
a[j+1]
。基于这一原理,可得:
//求最大子序列和的函数4
int maxSubQueneSum4(int [] a){
int maxSum = 0, sum = 0;
for(int i = 0; i < a.length; i++){
sum = sum + a[i];
if(sum > maxSum){
maxSum = sum;
}
else
sum = 0;
}
return maxSum;
}
由上可见,该算法有点聪明,但是总感觉有点投机取巧之嫌,其复杂度为
O(N)
,是线性复杂度,又由于该算法总是能计算出当前序列的最大子序列和,无需在整个序列输入完以后才计算,所以又具有“联机算法”的特性。
但是我有点不明白这算法不能计算当
a[N]
都为负数时最大子序列和,哪位高人能够讲一讲?小弟感激不尽,还有对于算法三如何定位最大子序列和的位置?