最大子序列和

给定一个整数序列,a0, a1, a2, …… , an(项可以为负数),求其中最大的子序列和。

即输入一组整数,求出这组数字子序列和中的最大值,只要求出最大子序列的和,不必求出最大值对应的序列。

最大子序列和:整数序列A1, A2,... An (可能有负数),求A1~An的一个子序列Ai~Aj,使得Ai到Aj的和最大。

例如:

序列:-2, 11, -4, 13, -5, 2, -5, -3, 12, -9,则最大子序列和为21。

序列:0, -3, 6, 8, -20, 21, 8, -9, 10, -1, 3, 6, 5,则最大子序列和为43。

 

方法一:

用三层循环,想要找到这个子序列,肯定是需要循环遍历,有点类似于冒泡排序的感觉。

所以第一层循环确定起点,第二层循环确定终点,第三层循环在起点和终点之间遍历。

时间复杂度:O(N^3)

即算法的运行时间是以输入长度的立方增长,当输入长度过长,程序的运行效率将变得越来越大。

void MaxSumOne(int a[], int N)
{
	int ThisSum, MaxSum, i, j, k;
	MaxSum = a[0];
	for (i = 0; i < N; i++)
	{
		for (j = i; j < N; j++)
		{
			ThisSum = 0;
			for (k = i; k <= j; k++)
			{
				ThisSum += a[k];
			}
			if (ThisSum > MaxSum)
			{
				MaxSum = ThisSum;
			}
		}
	}
	printf("第一种方式求最大子序和为:%d  \n",MaxSum);
}

 

方法二:

相当于改进第一种方法,只使用两层循环,就是不用每次都重新加一遍,每次加完比较一遍。

时间复杂度:O(N^2)

算法的运行时间是以输入长度的平方增长。

void MaxSumTwo(int a[], int N)
{
	int ThisSum, MaxSum, i, j;
	MaxSum = a[0];
	for (i = 0; i < N; i++)
	{
		ThisSum = 0;
		for (j = i; j < N; j++)
		{
			ThisSum += a[j];
			if (ThisSum > MaxSum)
				MaxSum = ThisSum;
		}
	}
	printf("第二种方式求最大子序和为:%d  \n", MaxSum);
}	

 

方法三:

采用递归的思想,把序列分成左右两个序列,分别求两个序列的最大序列和,这个序列大最大序列的位置只有三种情况:

1、最大序列和完全在左边部分;

2、最大序列和完全在右边部分;

3、最大序列和跨越左右两部分。

所以为了求出结果,可以分别求出左部分的最大序列和、右部分的最大序列和和跨越左右两部分的最大序列和,然后进行比较,最大的即为所求。

求左半部分的最大子序列和,可把左半部分作为新的输入序列通过该算法递归求出。右半部分的最大子序列和也同理。

跨越左右两部分的最大子序列和即为两者相加。

时间复杂度:O(N logN)

int MaxSumThree(int a[], int left, int right)
{
	//数组只有一个元素
	if (left == right)
	{
		//空序也是一种情况,返回0
		if (a[left] > 0)
			return a[left];
		else
			return 0;
	}

	//左右递归求出部分序列最大和
	int center = (left + right) / 2;
	int maxLeftSum = MaxSumThree(a, left, center);
	int maxRightSum = MaxSumThree(a, center + 1, right);

	//左
	int maxLeftBorderSum = 0, leftBorderSum = 0;
	for (int i = center; i >= left; i--) 
	{
		leftBorderSum += a[i];
		if (leftBorderSum > maxLeftBorderSum) 
		{
			maxLeftBorderSum = leftBorderSum;
		}
	}

	//右
	int maxRightBorderSum = 0, rightBorderSum = 0;
	for (int i = center + 1; i <= right; i++)
	{
	rightBorderSum += a[i];
		if (rightBorderSum > maxRightBorderSum)
		{
			maxRightBorderSum = rightBorderSum;
		}
	}

	//跨越两边的
	int maxLeftRightSum = maxLeftBorderSum + maxRightBorderSum;

	//求三个方面的最大序列
	int maxSubSum = 0;
	maxSubSum = maxLeftSum > maxRightSum ? maxLeftSum : maxRightSum;
	maxSubSum = maxSubSum > maxLeftRightSum ? maxSubSum : maxLeftRightSum;

	return maxSubSum;
}

 

方法四:

设序列为a,长度为n,从a(0)开始求和并记录最大值,即开始计算a(0) , a(0)+a(1) , a(0)+a(1)+a(2)......,直到出现小于0的停止,若加到a(i)以后求和小于0,便从a(i+1)之后重新求和。

因为a(i-1)一定是大于0的,加上a(i)求和小于0,即a(i)是小于0的,之后求和的序列不必加上这个小于0的和值。

时间复杂度:O(N)

void MaxSumFour(int a[], int N)
{
	int ThisSum, MaxSum, i;
	MaxSum = a[0];
	ThisSum = 0;
	for (i = 0; i < N; i++)
	{
		ThisSum += a[i];
		if (ThisSum < 0)
			ThisSum = 0;
		else if (ThisSum > MaxSum)
			MaxSum = ThisSum;
	}
	printf("第四种方式求最大子序和为:%d  \n", MaxSum);
}

 

最后调试使用的主程序为:

#include<stdio.h>
#include<windows.h>

//时间复杂度为N**3
void MaxSumOne(int a[], int N)
{
	int ThisSum, MaxSum, i, j, k;
	MaxSum = a[0];
	for (i = 0; i < N; i++)
	{
		for (j = i; j < N; j++)
		{
			ThisSum = 0;
			for (k = i; k <= j; k++)
			{
				ThisSum += a[k];
			}
			if (ThisSum > MaxSum)
			{
				MaxSum = ThisSum;
			}
		}
	}
	printf("第一种方式求最大子序和为:%d  \n",MaxSum);
}


//时间复杂度为N**2
void MaxSumTwo(int a[], int N)
{
	int ThisSum, MaxSum, i, j;
	MaxSum = a[0];
	for (i = 0; i < N; i++)
	{
		ThisSum = 0;
		for (j = i; j < N; j++)
		{
			ThisSum += a[j];
			if (ThisSum > MaxSum)
				MaxSum = ThisSum;
		}
	}
	printf("第二种方式求最大子序和为:%d  \n", MaxSum);
}	

//递归求解(N * logN)
int MaxSumThree(int a[], int left, int right)
{
	//数组只有一个元素
	if (left == right)
	{
		//空序也是一种情况,返回0
		if (a[left] > 0)
			return a[left];
		else
			return 0;
	}

	//左右递归求出部分序列最大和
	int center = (left + right) / 2;
	int maxLeftSum = MaxSumThree(a, left, center);
	int maxRightSum = MaxSumThree(a, center + 1, right);

	//左
	int maxLeftBorderSum = 0, leftBorderSum = 0;
	for (int i = center; i >= left; i--) 
	{
		leftBorderSum += a[i];
		if (leftBorderSum > maxLeftBorderSum) 
		{
			maxLeftBorderSum = leftBorderSum;
		}
	}

	//右
	int maxRightBorderSum = 0, rightBorderSum = 0;
	for (int i = center + 1; i <= right; i++)
	{
	rightBorderSum += a[i];
		if (rightBorderSum > maxRightBorderSum)
		{
			maxRightBorderSum = rightBorderSum;
		}
	}

	//跨越两边的
	int maxLeftRightSum = maxLeftBorderSum + maxRightBorderSum;

	//求三个方面的最大序列
	int maxSubSum = 0;
	maxSubSum = maxLeftSum > maxRightSum ? maxLeftSum : maxRightSum;
	maxSubSum = maxSubSum > maxLeftRightSum ? maxSubSum : maxLeftRightSum;

	return maxSubSum;
}

//时间复杂度为N
void MaxSumFour(int a[], int N)
{
	int ThisSum, MaxSum, i;
	MaxSum = a[0];
	ThisSum = 0;
	for (i = 0; i < N; i++)
	{
		ThisSum += a[i];
		if (ThisSum < 0)
			ThisSum = 0;
		else if (ThisSum > MaxSum)
			MaxSum = ThisSum;
	}
	printf("第四种方式求最大子序和为:%d  \n", MaxSum);
}


int main()
{
	int arr1[] = { 0, -3, 6, 8, -20, 21, 8, -9, 10, -1, 3, 6, 5};
	int N1 = sizeof(arr1) / sizeof(arr1[0]);

	MaxSumOne(arr1,N1);

	MaxSumTwo(arr1,N1);

	printf("第三种方式求最大子序和为:%d  \n", MaxSumThree(arr1, 0, N1));

	MaxSumFour(arr1, N1);

	system("pause");
	return 0;
}

 

其实第一个和第二个方法是最容易想出来的,但是用来解决例子是没有问题的,但是放到实际应用中就显得捉襟见肘了。后面两种方法比较难理解,但是是一种思想。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值