数据结构系列内容的学习目录 → \rightarrow →浙大版数据结构学习系列内容汇总。
题目描述: 给定K个整数的序列 { N 1 , N 2 , . . . , N K } \{ N_{1}, N_{2}, ..., N_{K} \} {N1,N2,...,NK}, “连续子列”被定义为 { N i , N i + 1 , … , N j } \{ N_{i} , N_{i+1} , …, N_j \} {Ni,Ni+1,…,Nj},其中 1 ≤ i ≤ j ≤ K 1≤i≤j≤K 1≤i≤j≤K。“最大子列和”则被定义为所有连续子列元素的和中最大者。例如给定序列{ -2, 11, -4, 13, -5, -2 },其连续子列{ 11, -4, 13 }有最大的和20。现编写程序,计算给定整数序列的最大子列和。
本题旨在测试各种不同的算法在各种数据情况下的表现。各组测试数据特点如下:
- 数据1:与样例等价,测试基本正确性;
- 数据2: 1 0 2 10^{2} 102个随机整数;
- 数据3: 1 0 3 10^{3} 103个随机整数;
- 数据4: 1 0 4 10^{4} 104个随机整数;
- 数据5: 1 0 5 10^{5} 105个随机整数。
输入格式: 输入第1行给出正整数K (≤100000);
输入第2行给出K个整数,其间以空格分隔。
输出格式: 在一行中输出最大子列和。如果序列中所有整数皆为负数,则输出0。
输入样例:
6
-2 11 -4 13 -5 -2
输出样例:
20
解题思路: 见数据结构(一)—— 基本概念的第3节(应用实例:最大子列和问题)。
代码实现:
#include<iostream>
using namespace std;
// 算法1:确定子列的首部和尾部,再遍历累加,时间复杂度为O(n^3)
int MaxSubseqSum1(int a[], int n)
{
int ThisSum, MaxSum = 0;
for (int i = 0; i < n; i++) //i为子列左端位置
{
for (int j = 0; j < n; j++) //j是子列右端位置
{
ThisSum = 0; //ThisSum是从a[i]到a[j]的子列和
for (int k = i; k <= j; k++)
{
ThisSum += a[k];
}
if (ThisSum > MaxSum) //如果刚得到的这个子列和更大,则更新结果
{
MaxSum = ThisSum;
}
}
}
return MaxSum;
}
//算法2:确定子列的首部,逐个累加,时间复杂度为O(n^2)
int MaxSubseqSum2(int a[], int n)
{
int ThisSum, MaxSum = 0;
for (int i = 0; i < n; i++) //i为子列左端位置
{
ThisSum = 0; //ThisSum是从a[i]到a[j]的子列和
for (int j = i; j < n; j++) //j是子列右端位置
{
ThisSum += a[j];
if (ThisSum > MaxSum) //如果刚得到的这个子列和更大,则更新结果
{
MaxSum = ThisSum;
}
}
}
return MaxSum;
}
//算法3:分而治之,递归分成两份,分别求每个分割后最大子列和,时间复杂度为O(nlogn)
//返回左边最大子列和、右边最大子列和、横跨划分边界的最大子列和三者中最大值
int MaxSum(int A, int B, int C)
{
return (A > B) ? ((A > C) ? A : C) : ((B > C) ? B : C);
}
//分治
int DivideAndConquer(int a[], int left, int right)
{
if (left == right) //递归结束条件:子列只有一个数字
{
if (a[left] > 0) // 当该数为正数时,最大子列和为其本身
{
return a[left];
}
return 0; // 当该数为负数时,最大子列和为 0
}
//分别递归找到左右最大子列和
int mid = left + (right - left) / 2; //利用left+(right - left)/2求mid是为了防止整数溢出问题
int MaxLeftSum = DivideAndConquer(a, left, mid);
int MaxRightSum = DivideAndConquer(a, mid + 1, right);
//再分别找左右跨界最大子列和
int MaxLeftBorderSum = 0;
int LeftBorderSum = 0;
for (int i = mid; i >= left; i--) //应该从边界出发向左边找
{
LeftBorderSum += a[i];
if (LeftBorderSum > MaxLeftBorderSum)
MaxLeftBorderSum = LeftBorderSum;
}
int MaXRightBorderSum = 0;
int RightBorderSum = 0;
for (int i = mid + 1; i <= right; i++) // 从边界出发向右边找
{
RightBorderSum += a[i];
if (RightBorderSum > MaXRightBorderSum)
MaXRightBorderSum = RightBorderSum;
}
//最后返回分解的左边最大子列和,右边最大子列和,和跨界最大子列和三者中最大的数
return MaxSum(MaxLeftSum, MaxRightSum, MaXRightBorderSum + MaxLeftBorderSum);
}
int MaxSubseqSum3(int a[], int n)
{
return DivideAndConquer(a, 0, n - 1);
}
//算法4:在线处理,直接累加,如果累加到当前的和为负数,置当前值或0,时间复杂度为 O(n)
int MaxSubseqSum4(int a[], int n)
{
int ThisSum = 0;
int MaxSum = 0;
for (int i = 0; i < n; i++)
{
ThisSum += a[i]; //向右累加
if (ThisSum < 0) //如果刚得到的这个子列和为负,则不能使后面的部分和增大,抛弃
{
ThisSum = 0;
}
else if (ThisSum > MaxSum) //如果刚得到的这个子列和更大,则更新结果
{
MaxSum = ThisSum;
}
}
return MaxSum;
}
int main() {
int n;
int a[100000 + 5];
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> a[i];
}
//算法1
//int MaxSum1 = MaxSubseqSum1(a, n);
//cout << MaxSum1 << endl;
//算法2
//int MaxSum2 = MaxSubseqSum2(a, n);
//cout << MaxSum2 << endl;
//算法3
//int MaxSum3 = MaxSubseqSum3(a, n);
//cout << MaxSum3 << endl;
//算法4
int MaxSum4 = MaxSubseqSum4(a, n);
cout << MaxSum4 << endl;
system("pause");
return 0;
}
测试: 输入样例的测试效果如下图所示。
时间复杂度分析: 在某台机器上四种算法在不同输入规模时的运行时间比较结果如下图所示。
可以看出第4种算法是最好的,当n=100,000时,仍可以在小于1秒的时间内得到运行结果。