算法分析:以最大子序列和为例
算法一:
int
MaxSquenceSum(const std::vector<int>& A)
{
int ThisSum, MaxSum{0}, N=A.size();
for(int i=0;i<N;i++) {
for(int j=i;j<N;j++) {
ThisSum = 0;
for(int k=i;k<=j;k++) {
ThisSum += A[k];
}
if(ThisSum > MaxSum) {
MaxSum = ThisSum;
}
}
}
return MaxSum;
}
上面这段代码利用穷举的手段穷举了一个数组A里面的全部的子列的可能性,然后输出最大子列和
复杂度:
∑ i = 0 N − 1 ∑ j = i N − 1 ∑ k = i j 1 = ∑ i = 0 N − 1 ∑ j = i N − 1 j − i + 1 = ∑ i = 0 N − 1 ( N − i ) ( N − i + 1 ) 2 = N 3 + 3 N 2 + 2 N 6 = Θ ( N 3 ) \hspace{0.1cm}\sum_{i=0}^{N-1}\sum_{j=i}^{N-1}\sum_{k=i}^{j}1\\ \hspace{0.3cm}=\sum_{i=0}^{N-1}\sum_{j=i}^{N-1}{j-i+1}\\ \hspace{1.3cm}=\sum_{i=0}^{N-1}{\frac{(N-i)(N-i+1)}{2}}\\ \hspace{1.7cm}=\frac{N^3+3N^2+2N}{6}=\Theta(N^3) i=0∑N−1j=i∑N−1k=i∑j1=i=0∑N−1j=i∑N−1j−i+1=i=0∑N−12(N−i)(N−i+1)=6N3+3N2+2N=Θ(N3)
可以看出复杂度很高,因此不是一种有效的算法。
对于同样的这个算法,我们有第二种实现:
int
MaxSquenceSum(const std::vector<int>& A)
{
int ThisSum, MaxSum{0}, N=A.size();
for(int i=0;i<N;i++)
{
ThisSum=0;
for(int j=i;j<N;j++) {
ThisSum+=A[j];
MaxSum = (ThisSum > MaxSum)?ThisSum: MaxSum;
}
}
return MaxSum;
}
复杂度:
算法二在每次遍历j的时候,我们每次做一次比较,这样子就减少了对k的那一次遍历
从而易得其复杂度为:
∑
i
=
0
N
−
1
∑
j
=
i
N
−
1
1
=
O
(
N
2
)
\sum_{i=0}^{N-1}\sum_{j=i}^{N-1}1 = O(N^2)
i=0∑N−1j=i∑N−11=O(N2)
接下来还有算法三,一种更优秀的解法,使用了分治的想法
static int
MaxSubSum(const std::vector<int>& A,int Left,int Right)
{
int MaxLeftSum, MaxRightSum;
int MaxLeftBorderSum, MaxRightBorderSum;
int LeftBorderSum, RightBorderSum;
int Center;
if(Left==Right)
{
if (A[Left] > 0) {
return A[Left];
} else {
return 0;
}
}
Center = (Left+Right)/2;
MaxLeftSum = MaxSubSum(A,Left,Center);
MaxRightSum = MaxSubSum(A,Center+1,Right);
MaxLeftBorderSum=0;
LeftBorderSum=0;
for(int i=Center;i>=Left;i--) {
LeftBorderSum += A[i];
MaxLeftBorderSum = std::max(MaxLeftBorderSum,LeftBorderSum);
}
MaxRightBorderSum=0;
RightBorderSum=0;
for(int i=Center+1;i<=Right;i++) {
RightBorderSum += A[i];
MaxRightBorderSum = std::max(MaxRightBorderSum,RightBorderSum);
}
return std::max(std::max(MaxLeftSum,MaxRightSum),MaxLeftBorderSum+MaxRightBorderSum);
}
int
MaxSubsequence(const std::vector<int>& A)
{
return MaxSubSum(A,0,(int)A.size()-1);
}
原理:
使用了递归,基准情况为Left == Right
时的情况,如果这时候A[Left]<0
那么相当于我们取的子列长度为0,所以返回0,否则返回A[left]
然后,还有下面几部,用于处理 Left != Right时,我们需要把中间的边界和给加上去,也就是带有A[Center]的数组左边的最大子列和加上带有A[Center+1]的数组右边的最大子列和,然后我们比较此时我们的左子列和,右子列和,中间子列和取最大值,并将其返回出去。显然,这就是在这一层递归里面我们的最大子列和。
至于为什么在处理中间子列和的时候我们不需要考虑要不要将其赋值为0,是因为左子列和 和 右子列和的最小值是0, 而在下面紧接着就有一次比较。
复杂度:
根据极限的原则,我们取 N = 2 k {N = 2^k} N=2k的情况(因为极限必然存在),记求N个整数的最大子列和的时间开销为T(N)
我么显然可以得到以下的递推关系:
T
(
N
)
=
2
∗
T
(
N
2
)
+
O
(
N
)
T(N) = 2*T(\frac{N}{2})+O(N)
T(N)=2∗T(2N)+O(N)
O(N)来自于我们代码结尾求中间子列和的部分
然后不妨令此处的O(N) = N(方便计算)
就有T(N) = 2*T(N/2)+N,T(1) = 1
那么,就有
T
(
N
)
=
N
∗
l
o
g
2
N
+
N
=
O
(
N
∗
l
o
g
2
N
)
T(N) = N*log_2N+N = O(N*log_2N)
T(N)=N∗log2N+N=O(N∗log2N)
最后,由于算法三还是有些太长了,我们有算法四
int
MaxSubsequence(const std::vector<int>& A)
{
int ThisSum{0},MaxSum{0},N=A.size();
for(int j=0;j<N;j++)
{
ThisSum+=A[j];
MaxSum = std::max(MaxSum,ThisSum);
ThisSum = std::max(ThisSum,0);
}
return MaxSum;
}
原理:
命 题 : S j 表 示 带 上 A [ j ] 后 的 A 的 从 [ 0 , j ] 的 最 大 子 列 和 ( 或 者 一 个 元 素 都 不 带 , 那 么 S j = 0 ) , M a x S u m j 表 示 A 的 从 [ 0 , j ] 的 最 大 子 列 和 命题:S_j表示带上A[j]后的A的从[0,j]的最大子列和(或者一个元素都不带,那么S_j = 0),MaxSum_j表示A的从[0,j]的最大子列和 命题:Sj表示带上A[j]后的A的从[0,j]的最大子列和(或者一个元素都不带,那么Sj=0),MaxSumj表示A的从[0,j]的最大子列和
然后要证明一下递推关系成立:
S
j
+
1
=
m
a
x
(
S
j
+
A
[
j
+
1
]
,
0
)
M
a
x
S
u
m
j
+
1
=
m
a
x
(
M
a
x
S
u
m
j
,
S
j
+
1
)
S_{j+1} = max(S_j+A[j+1],0)\\ MaxSum_{j+1} = max(MaxSum_j,S_{j+1})
Sj+1=max(Sj+A[j+1],0)MaxSumj+1=max(MaxSumj,Sj+1)
使用数学归纳法,在j=0的时候,显然,然后设j=k的时候成立,然后在j=k+1的时候,显然,递推关系成立。
那么命题的证,从而我们的算法有效性得到了验证。这里实际上是使用到了动态规划的想法。