您的位置:首页 > 其它

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

2016-02-03 01:42 323 查看
最大子数组描述的是这样的一个问题:给定一个整型数组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;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: