算法设计与分析--求解最大连续子序列和问题(分治法、蛮力法、动态规划法)

一、问题描述

给定有n(n ≥ 1)个整数的序列,求出其中最大连续子序列的和。
例如:序列{-2, 11, -4, 13, -5, -2}的最大连续子序列和为20
序列{-6, 2, 4, -7, 5, 3, 2, -1, 6, -9, 10, -2}的最大连续子序列和为16
规定:一个序列的最大连续子序列和至少是0,如果小于0,其结果为0

二、问题求解(三种方法)

1. 分治法

算法思路:
对于含有n个整数的序列a[0…n - 1]:

  1. 若n=1,表示该序列仅含一个元素,若该元素大于0,则返回该元素,否则返回0

  2. 若n>1,先求出该序列的中间位置mid=在这里插入图片描述
    ,该序列的最大连续子序列和所在的子序列只可能位于三个位置:
    (1)该子序列完全位于序列的左半部,即a[0…mid]中。采用递归的方法求出其最大连续子序列和,命名为:max_left_sum。

    (2)该子序列完全位于序列的右半部,即a[mid + 1…n - 1]中。采用递归的方法求出其最大连续子序列和,命名为:max_right_sum。

    (3)该子序列跨越中部而占据左右两部分,即a[mid]被包含在该子序列中。求出左半部从a[mid]开始的最大连续子序列和,命名为:max_left_boarder_sum = 在这里插入图片描述
    {0 ≤ i ≤ mid}
    ;再求出右半部从a[mid + 1]开始的最大连续子序列和,命名为:max_right_boarder_sum = 在这里插入图片描述
    {mid + 1 ≤ j ≤ n - 1}
    。这种情况下序列的最大连续子序列和为:max_left_boarder_sum + max_right_boarder_sum,命名为:max_mid_sum。
    求出三种情况中的最大值(即max_left_sum, max_right_sum, max_mid_sum中的最大值),即为所求。
    分治法求解最大连续子序列和的过程

代码展示:

#include <iostream>
using namespace std;

int max3(int num1, int num2, int num3) {
	if (num1 > num2) {
		num2 = num1;
	}
	return num2 > num3 ? num2 : num3;
}

int max_sub_sum(int a[], int str, int end) {
	if (str == end) {
		if (a[str] < 0) {
			return 0;
		}
		else {
			return a[str];
		}
	}
	else {
		int max_left_sum, max_right_sum;
		int mid = (str + end) / 2;
		max_left_sum = max_sub_sum(a, str, mid);
		max_right_sum = max_sub_sum(a, mid + 1, end);
		
		int max_left_boarder_sum = 0, max_right_boarder_sum = 0;
		int left_boarder_sum = 0, right_boarder_sum = 0;
		for (int i = mid; i >= str; i--) {
			left_boarder_sum += a[i];
			if (left_boarder_sum > max_left_boarder_sum) {
				max_left_boarder_sum = left_boarder_sum;
			}
		}
		for (int i = mid + 1; i <= end; i++) {
			right_boarder_sum += a[i];
			if (right_boarder_sum > max_right_boarder_sum) {
				max_right_boarder_sum = right_boarder_sum;
			}
		}
		int max_mid_sum = max_left_boarder_sum + max_right_boarder_sum;

		return max3(max_left_sum, max_mid_sum, max_right_sum);
	}
}

int main() {
	int a[] = { -2, 11, -4, 13, -5 ,-2 };
	int len = sizeof(a) / sizeof(a[0]);
	int value = max_sub_sum(a, 0, len - 1);
	cout << "最大连续子序列和为:" << value << endl;
	return 0;
}

在这里插入图片描述

算法分析:
设求解序列a[0…n - 1]最大连续子序列和的执行时间为T(n)。

  1. 若n = 1,则T(n) = 1。
  2. 若n > 1,则T(n) = 2 * T(n / 2) + O(n)

根据主定理,该算法时间复杂度为O(n*logn)

2. 蛮力法

蛮力法一共包括三种解法:
解法1:
算法思路:
设含有n个整数的序列a[0…n - 1]和其中任何连续子序列a[i…j](i ≤ j,0 ≤ i ≤ n - 1,i ≤ j ≤ n - 1),求出它的所有元素之和this_sum,并通过比较将最大值存放在max_sum中,最后返回max_sum。
例如:对于a[0…5] = {-2, 11, -4, 13, -5, -2},求出的a[i…j](i ≤ j)的所有元素和如图所示,其中20是最大值,即最大连续子序列和。
在这里插入图片描述

代码展示:

#include <iostream>
using namespace std;
#define INF 9999

int max_sub_sum(int a[], int len) {
	int this_sum = -INF, max_sum = -INF;
	for (int i = 0; i <= len - 1; i++) {
		for (int j = i; j <= len - 1; j++) {
			this_sum = 0;
			for (int k = i; k <= j; k++) {
				this_sum += a[k];
			}
			if (this_sum > max_sum) {
				max_sum = this_sum;
			}
		}
	}
	return max_sum;
}

int main() {
	int a[] = { -2, 11, -4, 13, -5 ,-2 };
	int len = sizeof(a) / sizeof(a[0]);
	int value = max_sub_sum(a, len);
	cout << "最大连续子序列和为:" << value << endl;
	return 0;
}

在这里插入图片描述

算法分析:
因为算法中使用了三重循环,所以有:
T(n) = O(n^3)

解法2:
算法思路:
改进前面的算法,在求两个相邻子序列之和时他们之间相互关联。例如:设Sum(a[i…j])表示a[i…j]中所有元素的和。Sum(a[0…3]) = a[0] + a[1] + a[2] + a[3],而Sum(a[0…4]) = a[0] + a[1] + a[2] + a[3] + a[4],在前者计算出来的情况下求后者只需要在前者的基础上加上a[4]即可,没有必要每次都重复计算,即求Sum(a[i…j])时的递推关系如下:

代码展示:

#include <iostream>
using namespace std;
#define INF 9999

int max_sub_sum(int a[], int len) {
	int this_sum = -INF, max_sum = -INF;
	for (int i = 0; i <= len - 1; i++) {
		this_sum = 0;
		for (int j = i; j <= len - 1; j++) {
			this_sum += a[j];
			if (this_sum > max_sum){
				max_sum = this_sum;
			}
		}
	}
	return max_sum;
}

int main() {
	int a[] = { -2, 11, -4, 13, -5 ,-2 };
	int len = sizeof(a) / sizeof(a[0]);
	int value = max_sub_sum(a, len);
	cout << "最大连续子序列和为:" << value << endl;
	return 0;
}

在这里插入图片描述

算法分析:
由于算法仅使用了两层循环,时间复杂度如下:
T(n) = O(n^2)

解法3:
算法思路:
进一步改进解法2,从头开始扫描数组a,用this_sum记录当前子序列之和,用max_sum记录最大连续子序列和。若在扫描中遇到负数,当前子序列和this_sum将会减小。若this_sum为负数,则可以放弃该子序列,重新开始下一个子序列的分析,并置this_sum = 0。若this_sum不断增加,则max_sum也不断增加。

代码展示:

#include <iostream>
using namespace std;
#define INF 9999

int max_sub_sum(int a[], int len) {
	int this_sum = 0, max_sum = -INF;
	for (int i = 0; i <= len - 1; i++) {
		this_sum += a[i];
		if (this_sum < 0) {
			this_sum = 0;
		}
		if (this_sum > max_sum) {
			max_sum = this_sum;
		}
	}
	return max_sum;
}

int main() {
	int a[] = { -2, 11, -4, 13, -5 ,-2 };
	int len = sizeof(a) / sizeof(a[0]);
	int value = max_sub_sum(a, len);
	cout << "最大连续子序列和为:" << value << endl;
	return 0;
}

在这里插入图片描述

算法分析:
由于算法仅使用了一层循环,时间复杂度如下:
T(n) = O(n)

3. 动态规划法

算法思路:
对于含有n个整数的序列a[0…n - 1],设bj(1 ≤ j ≤ n)表示a[0…j - 1]的前j个元素中包含a[j - 1]的最大连续子序列和,bj-1表示a[0…j - 2]的前j-1个元素中包含a[j - 2]的最大连续子序列和。
显然,当bj-1 > 0时,bj = bj-1 + a[j - 1];当bj-1 ≤ 0时,放弃前面选取的元素,从a[j - 1]重新开始选取,bj = a[j ]。用一维动态规划数组dp存放b,对应的状态转移方程如下:
dp[0] = 0;
dp[j] = MAX{dp[j - 1] + a[j - 1], a[j - 1]} 1 ≤ j ≤ n
则序列a的最大连续子序列和等于dp[j](1 ≤ j ≤ n)中的最大者。
从中看到,若序列a的最大连续子序列和等于dp[maxj],在dp中从该位置向前找,值小于或等于0的元素dp[k],则a序列中从k + 1开始到maxj位置的所有元素恰好构成最大连续子序列。

例如:a序列为{-2, 11, -4, 13, -5, -2},dp[0] = 0,求其他元素如下:
在这里插入图片描述

其中,dp[4] = 20为最大值,向前找到dp[1] ≤ 0,所以由a2 ~ a4的元素即(11, -4, 13)构成最大连续子序列,和为20

代码展示:

#include <iostream>
using namespace std;
#define MAXN 20

int dp[MAXN];

void max_sub_sum(int a[], int len) {
	dp[0] = 0;
	for (int i = 1; i <= len; i++) {
		dp[i] = max(dp[i - 1] + a[i - 1], a[i - 1]);
	}
}

void show_result(int a[], int len) {
	int maxi = 0;
	for (int i = 1; i <= len; i++) {
		if (dp[i] > dp[maxi]) {
			maxi = i;
		}
	}
	int str;
	for (str = maxi; str >= 0; str--) {
		if (dp[str] < 0) {
			break;
		}
	}
	cout << "求解结果" << endl;
	cout << "\t最大连续子序列和:" << dp[maxi] << endl;
	cout << "\t最大连续子序列:";
	for (int i = str + 1; i <= maxi; i++) {
		cout << a[i - 1] << " ";
	}
	cout << endl;
}

int main() {
	int a[] = { -2, 11, -4, 13, -5, -2 };
	int len = sizeof(a) / sizeof(a[0]);
	max_sub_sum(a, len);
	show_result(a, len);
	return 0;
}

在这里插入图片描述

算法分析:
由于算法仅包含一重循环,时间复杂度为:T(n) = O(n)

  • 10
    点赞
  • 85
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
最大子段和问题是指在一个数列中找到一个子序列,使得该子序列中所有元素的和最大。以下是三种常见的算法实现: 1. 蛮力 蛮力是最朴素的解,它的时间复杂度为 $O(n^2)$。具体实现如下: ```c++ int maxSubArray(int nums[], int n) { int ans = INT_MIN; for (int i = 0; i < n; i++) { int sum = 0; for (int j = i; j < n; j++) { sum += nums[j]; ans = max(ans, sum); } } return ans; } ``` 2. 分治法 分治法的时间复杂度为 $O(n\log n)$,它将问题分成三个部分:求解左半部分的最大子段和、求解右半部分的最大子段和、求解跨越中点的最大子段和。具体实现如下: ```c++ int maxSubArray(int nums[], int left, int right) { if (left == right) return nums[left]; int mid = left + (right - left) / 2; int leftMax = maxSubArray(nums, left, mid); int rightMax = maxSubArray(nums, mid + 1, right); int crossMax = nums[mid]; int sum = nums[mid]; for (int i = mid - 1; i >= left; i--) { sum += nums[i]; crossMax = max(crossMax, sum); } sum = crossMax; for (int i = mid + 1; i <= right; i++) { sum += nums[i]; crossMax = max(crossMax, sum); } return max(leftMax, max(rightMax, crossMax)); } ``` 3. 动态规划 动态规划的时间复杂度为 $O(n)$,它定义了一个状态数组 $dp$,其中 $dp[i]$ 表示以 $i$ 结尾的最大子段和。具体实现如下: ```c++ int maxSubArray(int nums[], int n) { int dp[n]; dp[0] = nums[0]; int ans = nums[0]; for (int i = 1; i < n; i++) { dp[i] = max(dp[i - 1] + nums[i], nums[i]); ans = max(ans, dp[i]); } return ans; } ``` 以上是三种常见的算法实现,需要注意的是,在实际应用中,我们还可以使用其他优化方,如前缀和、后缀和、单调栈等,以进一步提高算法效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值