最大子数组和问题分析总结

来源:互联网 发布:最优化方法好过吗 编辑:程序博客网 时间:2024/05/21 12:47

问题:对给定义数组A,其长度为n,找出数组A中的最大子数组,例如数组A={-23,18,20,-7,12},则最大子数组为{18,20,-7,12}。

应用:比如一段时间内股票如何低买高卖,获取最大利润。

解题思路

       (1)蛮力法(brute-force ):通过二重循环计算每个子数字和,同时进行比较,最后的结果是最大子数组和及其开始和结束的坐标,即从数组中选择一个数A(i),然后计算以A(i)开始的所有子数组的和,计算的次数为(n-i),选择的次数为n。该方法的时间复杂度为O(n2)。

       (2)分治法(Divide-Conquer ):用分治策略来求解。假设要寻找数组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]中的最大子数组,然后计算跨越中间元素的最大子数组,剩下的问题就是找出这三个最大子数组中的最大子数组。时间复杂度为O(nlogn2)。

代码实现如下:

  1. #include <stdio.h>  
  2. #include <string.h>  
  3.   
  4. #ifndef INT_MIN  
  5. #define INT_MIN (-((int)(~0U>>1)) - 1)  
  6. #endif  
  7.   
  8. struct subarray {  
  9.     int start;  
  10.     int end;  
  11.     int sum;  
  12. };  
  13.       
  14. void find_max_cross_subarray(int *a, int low, int mid, int high, void *p)  
  15. {  
  16.     struct subarray *sa = (typeof(sa))p;  
  17.     int max_left, max_right;  
  18.     int left_sum, right_sum;  
  19.     int sum;  
  20.     int i;  
  21.       
  22.     left_sum = INT_MIN;  
  23.     sum = 0;  
  24.     for (i = mid; i >= low; --i) {  
  25.         sum += a[i];  
  26.           
  27.         if (sum > left_sum) {  
  28.             max_left = i;  
  29.             left_sum = sum;  
  30.         }  
  31.     }  
  32.       
  33.     right_sum = INT_MIN;  
  34.     sum = 0;  
  35.     for (i = mid + 1; i <= high; ++i) {  
  36.         sum += a[i];  
  37.           
  38.         if (sum > right_sum) {  
  39.             max_right = i;  
  40.             right_sum = sum;  
  41.         }  
  42.     }  
  43.       
  44.     sa->start = max_left;  
  45.     sa->end = max_right;  
  46.     sa->sum = left_sum + right_sum;  
  47. }  
  48.   
  49. void find_max_subarray(int *a, int low, int high, void *p)  
  50. {  
  51.     struct subarray *sa = (typeof(sa))p;  
  52.     struct subarray *left_sa, __left_sa;  
  53.     struct subarray *right_sa, __right_sa;  
  54.     struct subarray *cross_sa, __cross_sa;  
  55.     struct subarray *tmp;  
  56.     int mid = (low + high) / 2;  
  57.       
  58.     if (low > high) {  
  59.         fprintf(stderr, "Invalid argument.\n");  
  60.         return;  
  61.     }  
  62.       
  63.     memset(sa, 0, sizeof(*sa));  
  64.       
  65.     if (high == low) {  
  66.         sa->sum = a[low];  
  67.         sa->start = sa->end = low;  
  68.         return;  
  69.     }  
  70.       
  71.     left_sa = &__left_sa;  
  72.     right_sa = &__right_sa;  
  73.     cross_sa = &__cross_sa;  
  74.       
  75.     find_max_subarray(a, low, mid, left_sa);  
  76.     find_max_subarray(a, mid + 1, high, right_sa);  
  77.     find_max_cross_subarray(a, low, mid, high, cross_sa);  
  78.       
  79.     if ((left_sa->sum >= right_sa->sum) &&  
  80.         (left_sa->sum >= cross_sa->sum)) {  
  81.         tmp = left_sa;  
  82.     } else if ((right_sa->sum >= left_sa->sum) &&   
  83.         (right_sa->sum >= cross_sa->sum)) {  
  84.         tmp = right_sa;  
  85.     } else {  
  86.         tmp = cross_sa;  
  87.     }  
  88.       
  89.     memcpy(sa, tmp, sizeof(*sa));  
  90. }  
  91.   
  92. int main(void)  
  93. {  
  94.     int source[] = {13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5,  -22, 15, -4, 7};  
  95.     struct subarray sa;  
  96.       
  97.     find_max_subarray(source, 0, sizeof(source) / sizeof(source[0]) - 1, &sa);  
  98.     printf("Max sum: %d, start: %d, end: %d.\n", sa.sum, sa.start, sa.end);  
  99.     return 0;  

       (3)动态规划法(Dynamic Programming)从数组的左边界开始,由左至右处理,记录到目前为止已经处理过的最大子数组。若已知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)。

代码实现如下所示:

  1. #include <stdio.h>  
  2. #include <string.h>  
  3.   
  4. struct subarray {  
  5.     int start;  
  6.     int end;  
  7.     int sum;  
  8. };  
  9.   
  10. #define max(__x, __y) ((__x) > (__y) ? (__x) : (__y))  
  11.   
  12. static void max_sumarray(int *a, int len, void *p)  
  13. {  
  14.     struct subarray *sa = (typeof(sa))p;  
  15.     int i;  
  16.     int max_sum, prev, tmp;  
  17.     int start, end;  
  18.       
  19.     if (!sa || (len <= 0)) {  
  20.         fprintf(stderr, "Invalid argument.\n");  
  21.         return;  
  22.     }  
  23.       
  24.     memset(sa, 0, sizeof(*sa));  
  25.       
  26.     max_sum = a[0];  
  27.     prev = a[0];  
  28.     start = end = 0;  
  29.     for (i = 1; i < len; ++i) {  
  30.         prev = max(a[i], prev + a[i]);  
  31.           
  32.         if (prev < max_sum) {  
  33.             if (prev == a[i]) {  
  34.                 start = i;  
  35.             }  
  36.             continue;  
  37.         }  
  38.           
  39.         max_sum = prev;  
  40.           
  41.         if (prev == a[i]) {  
  42.             sa->start = sa->end = i;  
  43.         } else {  
  44.             sa->start = start;  
  45.             sa->end = i;  
  46.         }  
  47.     }  
  48.       
  49.     sa->sum = max_sum;  
  50. }  
  51.   
  52. int main(void)  
  53. {  
  54.     int source[] = {13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5,  -22, 15, -4, 7};  
  55.     struct subarray sa;  
  56.       
  57.     max_sumarray(source, sizeof(source) / sizeof(source[0]), &sa);  
  58.     printf("Max sum: %d, start: %d, end: %d.\n", sa.sum, sa.start, sa.end);  
  59.     return 0;  
  60. }  

各种方法分析:

方法(1)简单直观,但是效率及其低下;方法(2)时间复杂度为O(nlogn2),为解决问题的一种方法,递归消耗的堆栈比较多,空间开销比较大,但不是效率最佳;方法(3)时间复杂度O(n),效率最佳,可能理解起来想对比较难。在实际编码中有限选择第(3)方法。