最大子列和算法
最大子列和算法
标签(空格分隔): 算法 数据结构
PS:今天开始在中国大学MOOC上看浙江大学的数据结构课,难度确实很大,但是收获也很大。
最大子列和算法被许多数据结构教材用来演示算法时间复杂度的分析,但是其中的后两种算法都不好懂,这里对所有算法都做一下说明。
先上代码,里面囊括了所有的四种算法。代码虽是用C++写的,但是其中对C++特殊性质的应用只有一处,就是用max函数实现计算三个数中的最大数,这两行也很好懂,所以只会C语言的人也能看得懂。
#include <iostream> //subsequence 子序列
#include <algorithm>
#define N 1005
using namespace std;
int Maxsubsequencesum1(int List[],int n); //暴力法,复杂度o(n^3)
int Maxsubsequencesum2(int List[],int n); //利用每一次的加和,复杂度o(n^2)
int Maxsubsequencesum3(int List[],int n); //分治法,复杂度o(nlogn)。这里的函数为接口,为了统一函数的格式,这样定义。
int DAC(int List[],int left,int right); //及时处理法,复杂度o(n),但是想出来不容易。
int Maxsubsequencesum4(int List[],int n);
int max3(int a,int b,int c) {return max(a,max(b,c));} //比较三个数大小的函数
int main() {
int List[N]={1,2,3,4,5,6};
cout << Maxsubsequencesum3(List,6) << endl;
return 0;
}
int Maxsubsequencesum1(int List[],int n) {
int i,j,k,thissum,maxi=0;
for (i=0;i<n;i++) {
for (j=i;j<n;j++) {
thissum=0;
for (k=i;k<=j;k++) {
thissum+=List[k];
if (maxi<thissum)
maxi=thissum;
}
}
}
return maxi;
}
int Maxsubsequencesum2(int List[],int n) {
int i,j,thissum,maxi=0;
for (i=0;i<n;i++) {
thissum=0;
for (j=i;j<n;j++) {
thissum+=List[j];
if (maxi<thissum)
maxi=thissum;
}
}
return maxi;
}
int Maxsubsequencesum3(int List[],int n) {return DAC(List,0,n-1);} //接口标准化
//此处对DAC函数中的英文字母做一下说明。
//D:divide 分开
//A:and
//C:conquer 统治
//M:max
//L:left
//R:right
//B:border 边界
//S:sum
int DAC(int List[],int left,int right) {
int MLS,MRS;
int mid=(left+right)/2;
int MLBS,MRBS;
int LBS,RBS;
if (left==right) { //递归的边界条件,达到时终止递归
if (List[left]>0) return List[left];
else return 0;
}
MLS=DAC(List,left,mid); //这两行
MRS=DAC(List,mid+1,right); //一直想不到,光理解都花了很久,更不用说想到了
MLBS=0,LBS=0; //求中轴左侧子序列的最大和
for (int i=left;i<=mid;i++) {
LBS+=List[i];
if (LBS>MLBS)
MLBS=LBS;
}
MRBS=0,RBS=0; //求中轴右侧子序列的最大和
for (int i=mid+1;i<=right;i++) {
RBS+=List[i];
if (RBS>MRBS)
MRBS=RBS;
}
return max3(MLS,MRS,MLBS+MRBS);
}
int Maxsubsequencesum4(int List[],int n) {
int maxi=0,sum=0;
for (int i=0;i<n;i++) {
sum+=List[i];
if (maxi<sum)
maxi=sum;
else if (sum<0)
sum=0;
}
}
程序中的子序列求和函数一共有4个,我分别编号为了1,2,3,4,以下叙述也以序号指代。
1:
由上可知,总共用了三个循环,时间复杂度为o(n^3),慢的不行。稍微分析一下,会发现这个算法画蛇添足地为每一次执行添加了一个加和上限j,其实这个界限根本没用。所以,去掉这个上限,就有了算法2
2:
这个算法就是正常的遍历算法了。没有了遍历到每个i时的上限的限制,我们就能去掉一次循环,此时算法复杂度是o(n^2),但还不是最快的。
3:
这是个典型的分治算法,也是我不怎么会的递归。每次碰到稍微麻烦一点的递归,我就歇菜了。这个硬着头皮看了好久,才算弄明白了一点。首先,我们可以思考得到,如果我们每次都把序列从中间一分为二,那么我们想要的最大和子序列一定要么在前半段,要么在后半段,要么经过正中间的元素横跨前后半段。那么,我们每次都计算前半段的最大和子序列,后半段的最大和子序列,以及经过中间元素的最大和子序列,比较取最大的那个,就可以得到答案了。这个算法每次都将序列折半,显然应该和复杂度logn有关。写出并分析之后,得到复杂度o(nlogn),比2还要快得多。但是,这仍然不是最快的。
4:
这部分算法代码异常之简单,我们如何知道它是正确的呢?首先,如果我们要的序列是A={ai,ai+1,ai+2,……,ai+j}(这里的i,i+1等都是下标的形式),我们思考后可知,这个序列肯定满足,他的每一个子序列的和不小于零。想想,如果A的某个子序列和小于零了,那我们去掉这个子序列,不就可以得到一个更大的和了吗?这与已知矛盾。则我们有:如果某个序列的最大子序列和对应的子序列是A={ai,ai+1,ai+2,……,ai+j}(这里的i,i+1等都是下标的形式),这个序列肯定满足,它的每一个子序列的和不小于零。那么我们就可以有结论:如果存在一个子序列和小于零,那它肯定不是最大和子序列的一部分。到目前为止,我们可以写一个算法,求每一次得到的累加和,保留最大值,当求到令子序列的和第一次小于0时停下,作为一个分支。此时,我们可以肯定,B={ai,ai+1,ai+2,……,ai+j,ai+j+1}一定不是最大和子序列的一部分。那我们就可以重新计数最大和了,则有了这个算法。