第二章:递归与分治策略

3 篇文章 0 订阅
3 篇文章 0 订阅

7-4 最大子段和

给定n个整数(可能为负数)组成的序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的子段和的最大值。当所给的整数均为负数时,定义子段和为0。

要求算法的时间复杂度为O(n)。

输入格式:

输入有两行:

第一行是n值(1<=n<=10000);

第二行是n个整数。

输出格式:

输出最大子段和。

输入样例:

在这里给出一组输入。例如:

6
-2 11 -4 13 -5 -2

输出样例:

在这里给出相应的输出。例如:

20

一、7-4 最大字段和的分治算法描述

1. 解题思路

        本题使用分治法解决,首先将数组分成左右两段,然后递归求解左半段和右半段的最大字段和,再计算跨越中间点的最大字段和,最终返回这三者中的最大值作为整个数组的最大字段和。

2.代码展示

2.1 条件判断

如果只有一个数字,则最大字段和就是该数字的值,且a[left] = a[right],两者可以互换

if(left == right) return a[left];

2.2 取中间值

求出中间值mid,以mid为中心,将原问题分成两个字问题 

int mid = (left + right) / 2;

2.3 第一、二种情况:最大子段和可能出现在左半段或右半段

递归调用函数,分别求出左右半段的最大字段和left_max和right_max

int left_max = maxNum(left, mid); //递归调用函数,求出左半段(left到mid)的最大字段和 left_max
int right_max = maxNum(mid + 1, right); //递归调用函数,求出右半段(mid+1到right)的最大字段和 right_max

2.4 第三种情况:最大子段和跨越左右半段

分别求出左右半段的最大子段和再相加,结果即为跨越mid两端的最大子段和fmax

int lmax = a[mid]; //定义lmax的初始值为a[mid],与前面的字段和逐一比较大小 
	int tmp = a[mid]; //用于记录累加过程中的子段和的值,并与lmax比较大小
	for (int i = mid - 1; i >= left; i--) {
		tmp += a[i];
		if (tmp > lmax) {
			lmax = tmp; //更新lmax的值
		}
	}
	
	int rmax = a[mid + 1]; //定义rmax的初始值为a[mid + 1],与后面的字段和逐一比较大小
	tmp = a[mid + 1]; //用于记录累加过程中的子段和的值,并与rmax比较大小
	for (int i = mid + 2; i <= right; i++) {
		tmp += a[i];
		if (tmp > rmax) {
			rmax = tmp; //更新rmax的值 
		}
	}
	
	int fmax = lmax + rmax; //左右半段的最大子段和相加

2.5 求得最大子段和

比较三种情况的结果,求得最大子段和max_num并返回该值

int max_num = max(fmax, max(left_max, right_max));
return max_num;

2.6 完整函数代码

int maxNum(int left, int right) 
{
	if(left == right) return a[left];
	
	int mid = (left + right) / 2; 
	int left_max = maxNum(left, mid);
	int right_max = maxNum(mid + 1, right);
	
	int lmax = a[mid]; 
	int tmp = a[mid];
	for (int i = mid - 1; i >= left; i--) {
		tmp += a[i];
		if (tmp > lmax) {
			lmax = tmp;
		}
	}
	
	int rmax = a[mid + 1];
	tmp = a[mid + 1];
	for (int i = mid + 2; i <= right; i++) {
		tmp += a[i];
		if (tmp > rmax) {
			rmax = tmp; 
		}
	}
	
	int fmax = lmax + rmax; 
	int max_num = max(fmax, max(left_max, right_max)); 
	return max_num;
}

3.完整代码

#include<iostream>
using namespace std;

int a[10010];

int maxNum(int left, int right) 
{
	if(left == right) return a[left];
	
	int mid = (left + right) / 2; 
	int left_max = maxNum(left, mid);
	int right_max = maxNum(mid + 1, right);
	
	int lmax = a[mid]; 
	int tmp = a[mid];
	for (int i = mid - 1; i >= left; i--) {
		tmp += a[i];
		if (tmp > lmax) {
			lmax = tmp;
		}
	}
	
	int rmax = a[mid + 1];
	tmp = a[mid + 1];
	for (int i = mid + 2; i <= right; i++) {
		tmp += a[i];
		if (tmp > rmax) {
			rmax = tmp; 
		}
	}
	
	int fmax = lmax + rmax; 
	int max_num = max(fmax, max(left_max, right_max)); 
	return max_num;
}

int main()
{
	int n;
	int flag = 0;
	cin >> n;
	for(int j = 0; j < n; j++) {
		cin >> a[j];
		if(a[j] > 0) {
			flag = 1;
		} 
	}
	if(flag == 1) {
		cout << maxNum(1, n) << endl;
	}
	else {
		cout << "0" << endl;
	}
	return 0;
}

3.1 运算结果

二、分析该算法的时间复杂度

1. 递归部分:算法使用递归方式来处理左半部分和右半部分。每次递归都将问题分成两半,所以递归树的高度为 O(log n)。

2. 在每个递归步骤中,算法需要线性时间来计算左半部分、右半部分和跨越中间的最大子段和。每个部分的计算都需要遍历一次该部分的元素。因此,每个递归步骤的时间复杂度是 O(n)。

3. 递归树的高度是 O(log n),每个递归步骤的时间复杂度是 O(n),所以总的时间复杂度是 O(n * log n)。

综上所述,该代码的时间复杂度为 O(n * log n)。

三、对分治法的体会和思考

        1. 问题分解和递归:分治法的核心思想是将一个大问题分解成多个相似的小问题,并通过递归的方式解决这些小问题。这种方法有助于将问题拆分成更容易处理的部分,同时也使问题的结构更清晰。

        2. 空间复杂度:分治法通常需要额外的空间来存储子问题的中间结果。这可能会导致较高的空间复杂度,特别是在递归调用深度较大的情况下。在处理大规模问题时,需要考虑内存消耗。

        3. 算法设计的灵活性:分治法的思想可以激发算法设计的灵感,有助于解决各种问题。它不仅仅是一种解决问题的方法,还是一种问题建模和分析的工具。

        总之,分治法是一种有力的算法设计和问题求解策略,尤其在大规模问题、并行计算和高效性方面具有潜力。然而,选择使用分治法时需要谨慎考虑问题的特性,以确保它是解决问题的最佳方法之一。然而分治法并不总是最高效的解决方法。在某些情况下,了解其他算法和技术可以帮助优化和改进分治法的应用,如动态规划。因此,在解决问题时,我们需要根据问题的性质和规模选择最合适的算法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值