题目:给定义数组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; }