题目:给定义数组A,长度为n,找出数组A中的最大子数组,例如数组A={-23,18,20,-7,12},则最大子数组为{18,20,-7,12}。
解题思路:
①很容易想到的方案是简单地尝试每对可能的组合,然后从这些组合中找出最大的子数组。从数组中选择一个数A(i),然后计算以A(i)开始的所有子数组的和,计算的次数为(n-i),选择的次数为n,因此该算法的时间复杂度为Θ(n^2),该算法不可取。
②使用分治策略来求解。假设要寻找数组A[low,high]的最大子数组,将数组分为规模相同的两部分,中间的位置假设为mid。数组A所有的连续子数组A[i..j]所处的位置必然是以下三种情况之一:
a. 完全位于子数组A[low,mid]中,因此low≤i≤j≤mid
b.完全位于子数组A[mid+1,high]中,因此mid+1≤i≤j≤high
c.跨越了中间元素,因此low≤i≤mid<j≤high。
因此数组A的最大子数组所处的位置必然是这三种情况的一种。参考归并排序中递归思想,递归地求解A[low,mid]和A[mid+1,high]中的最大子数组,然后计算跨越中间元素的最大子数组,剩下的问题就是找出这三个最大子数组中的最大子数组。
代码实现如下:
#include <stdio.h>
#include <string.h>
#ifndef INT_MIN
#define INT_MIN (-((int)(~0U>>1)) - 1)
#endif
struct subarray {
int start;
int end;
int sum;
};
void find_max_cross_subarray(int *a, int low, int mid, int high, void *p)
{
struct subarray *sa = (typeof(sa))p;
int max_left, max_right;
int left_sum, right_sum;
int sum;
int i;
left_sum = INT_MIN;
sum = 0;
for (i = mid; i >= low; --i) {
sum += a[i];
if (sum > left_sum) {
max_left = i;
left_sum = sum;
}
}
right_sum = INT_MIN;
sum = 0;
for (i = mid + 1; i <= high; ++i) {
sum += a[i];
if (sum > right_sum) {
max_right = i;
right_sum = sum;
}
}
sa->start = max_left;
sa->end = max_right;
sa->sum = left_sum + right_sum;
}
void find_max_subarray(int *a, int low, int high, void *p)
{
struct subarray *sa = (typeof(sa))p;
struct subarray *left_sa, __left_sa;
struct subarray *right_sa, __right_sa;
struct subarray *cross_sa, __cross_sa;
struct subarray *tmp;
int mid = (low + high) / 2;
if (low > high) {
fprintf(stderr, "Invalid argument.\n");
return;
}
memset(sa, 0, sizeof(*sa));
if (high == low) {
sa->sum = a[low];
sa->start = sa->end = low;
return;
}
left_sa = &__left_sa;
right_sa = &__right_sa;
cross_sa = &__cross_sa;
find_max_subarray(a, low, mid, left_sa);
find_max_subarray(a, mid + 1, high, right_sa);
find_max_cross_subarray(a, low, mid, high, cross_sa);
if ((left_sa->sum >= right_sa->sum) &&
(left_sa->sum >= cross_sa->sum)) {
tmp = left_sa;
} else if ((right_sa->sum >= left_sa->sum) &&
(right_sa->sum >= cross_sa->sum)) {
tmp = right_sa;
} else {
tmp = cross_sa;
}
memcpy(sa, tmp, sizeof(*sa));
}
int main(void)
{
int source[] = {13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7};
struct subarray sa;
find_max_subarray(source, 0, sizeof(source) / sizeof(source[0]) - 1, &sa);
printf("Max sum: %d, start: %d, end: %d.\n", sa.sum, sa.start, sa.end);
return 0;
}
③《算法导论》中提出的一个解题思路,从数组的左边界开始,由左至右处理,记录到目前为止已经处理过的最大子数组。若已知A[1..j]的最大子数组基于如下性质将解扩展为A[1..j+1]的最大子数组:A[1..j+1]的最大子数组要么是A[1..j]的最大子数组,要么是某个子数组A[i..j+1](1≤i≤j+1)。在已知A[1..j]的最大子数组的情况下,可以在线性时间内找出形如A[i..j+1]的最大子数组。该算法的时间复杂度为因此该算法的时间复杂度为Θ(n)。
代码实现如下所示:
#include <stdio.h>
#include <string.h>
struct subarray {
int start;
int end;
int sum;
};
#define max(__x, __y) ((__x) > (__y) ? (__x) : (__y))
static void max_sumarray(int *a, int len, void *p)
{
struct subarray *sa = (typeof(sa))p;
int i;
int max_sum, prev, tmp;
int start, end;
if (!sa || (len <= 0)) {
fprintf(stderr, "Invalid argument.\n");
return;
}
memset(sa, 0, sizeof(*sa));
max_sum = a[0];
prev = a[0];
start = end = 0;
for (i = 1; i < len; ++i) {
prev = max(a[i], prev + a[i]);
if (prev < max_sum) {
if (prev == a[i]) {
start = i;
}
continue;
}
max_sum = prev;
if (prev == a[i]) {
sa->start = sa->end = i;
} else {
sa->start = start;
sa->end = i;
}
}
sa->sum = max_sum;
}
int main(void)
{
int source[] = {13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7};
struct subarray sa;
max_sumarray(source, sizeof(source) / sizeof(source[0]), &sa);
printf("Max sum: %d, start: %d, end: %d.\n", sa.sum, sa.start, sa.end);
return 0;
}