算法导论例程——最大子数组问题

本文详细阐述了最大子数组问题的求解过程,包括递归与分治法,以及动态规划方法。通过实例代码展示不同算法的实现,并对比它们的时间复杂度。文章最后提出改进后的算法,不仅降低了复杂度,还支持全负数数组,能够输出最大子数组的起始位置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        最大子数组描述的是这样的一个问题:给定一个整型数组a[],规模为n,求其中连续的m个元素之和使其最大。其中这m个元素被称为是数组a的子数组。

        最大子数组问题的求解过程可以分成三种情况,对给定数组,找到中点一分为二,它的最大子数组有可能在左边的部分,有可能在右边的部分,还有可能跨过(cross)左边和右边,那么对于最大子数组在左边或右边的情况,我们又可以把他们看作原问题的一个较小规模的子问题,即对左数组或右数组再次求最大子数组,那么这是递归问题的部分,它的基本问题是当待考察数组规模为1时,那么最大子数组就是它本身,这是递归的终止条件。接下来我们需要考虑的问题就是跨中点的子数组的问题。

        对于跨中点的最大子数组,我们可以设定max值,从中点起不断向两侧进行累加并与flag值比较,若大于flag值则替换,而flag值最初始时为一个广义的无穷小值,保证第一次循环时可以替换。

#include <stdio.h>
int max_subarray(int a[], int start, int end);
int max_crossing_subarray(int a[], int start, int mid, int end);

int main()
{
	int a[1000] = { 0 }, n = 0, i = 0, length = 0;
	printf("请输入待查找数组的规模:");
	scanf("%d", &n);
	length = n;
	while (n--)
		scanf("%d", &a[i++]);
	printf("%d\n", max_subarray(a, 0, length - 1));

	return 0;
}

int max_subarray(int a[], int start, int end)
{
	int mid, left_max, right_max, crossing_max;
	if (start == end)
		return a[start];
	else
	{
		mid = (start + end) / 2;
		left_max = max_subarray(a, start, mid);
		right_max = max_subarray(a, mid + 1, end);
		crossing_max = max_crossing_subarray(a, start, mid, end);

		if (left_max > right_max&&left_max > crossing_max)
			return left_max;
		else if (right_max > left_max&&right_max > crossing_max)
			return right_max;
		else
			return crossing_max;
	}
}

int max_crossing_subarray(int a[], int start, int mid, int end)
{
	int i = mid, j = mid + 1, sum = 0, left_sum = -10000, right_sum = -10000;
	for (i; i > start-1; i--)
	{
		sum += a[i];
		if (sum > left_sum)
			left_sum = sum;
	}
	
	sum = 0;                                                            //sum在进行新的计算的时候一定要初始化
	for (j; j < end + 1; j++)
	{
		sum += a[j];
		if (sum > right_sum)
			right_sum = sum;
	}

	return left_sum + right_sum;
}


          如果想要更精确的知道最大子数组的下标以及sum值,可以声明结构体变量,让两个函数的返回值为该结构体即可。

          接下来看dp的解法,非常巧妙。这里考察两个属性,一个是我们要求的最大子数组ms,一个是尾数组tms,其中tms[k,...i] = a[k] + ... + a[i]。我们不考虑整个数组中全是负数元素的情况,那么容易证明,ms和tms的最小值是0。对于数组规模为1的时候,ms = a[0],tms = a[0],而当数组规模大于1时,我们得到这样的关系:

          tms[i] = max{0,tms[i-1] + a[i]}

          ms[i] = max{tms[0],tms[1],...,tms[i]}

          也就是说,当尾数组不大于零时,他对于最大子数组是没有贡献的,而最大子数组其实就是尾数组中最大的。

#include <stdio.h>
int main()
{
	int a[1000] = { 0 }, i = 0, n = 0, length = 0, tail_max_subarray = 0, max_subarray = 0;
	printf("请输入待考察数组的规模:");
	scanf("%d", &n);
	length = n;
	while (n--)
		scanf("%d", &a[i++]);
	
	for (i = 0; i < length; i++)
	{
		tail_max_subarray += a[i];
		if (tail_max_subarray < 0)
			tail_max_subarray = 0;
		if (max_subarray < tail_max_subarray)
			max_subarray = tail_max_subarray;
	}
	printf("最大子数组sum值:%d\n", max_subarray);

	return 0;
}
         这样写完之后,复杂度一下子就降到o(n)。dp大法好。


         改进了之后的,支持全负数,可以输出起始位置。

#include <iostream>
using namespace std;
int main()
{
	int start = 0, end = 0, i = 0, tms = 0, ms = 0, T = 0, size = 0, a[100010] = { 0 }, num = 1, temp1 = 0;
	cin >> T;
	int temp = T;
	while (T--)
	{
		cin >> size;
		for (i = 1; i <= size; i++)
			cin >> a[i];
		tms = 0;
		ms = a[1];
		start = 1;
		end = 1;
		temp1 = 1;
		for (i = 1; i <= size; i++)
		{
			tms += a[i];
			if (tms > ms)
			{
				ms = tms;
				end = i;
				start = temp1;
			}
			if (tms <= 0)
			{
				tms = 0;
				temp1 = i + 1;
			}
		}
		cout << "Case " << num++ << ":" << endl << ms << " " << start << " " << end << endl;
		if (num <= temp)
			cout << endl;
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值