最大子数组和问题

算法导论在分治策略一章中提到了最大子数组和问题,我用c++实现了一下,还是挺简单的,只不过要return最大子数组的起始下标、结束下标和最大子数组和这三个数有点麻烦,如要使用引用的话,因为要递归传值所以不好实现,一个可行的办法是使用数组,将这三个值放在数组中传递。lz这里并没有写这一过程。

分治算法找最大子数组和将情况分成三种:

1.最大和数组在中间元素左侧;

2.最大和子数组在中间元素右侧;

3.最大和子数组跨越中间元素;

前两个问题的子问题仍是最大子数组问题,只是规模更小,于是我们剩下的全部工作就是寻找跨越中间元素的最大子数组。然后在三种情况下选择和最大者。

 

代码如下:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 int find_max_crossing_subarray(int A[], int low, int mid, int high)  //处理最大子数组在中间元素两侧情况
 5 {
 6     int left_sum = -1000;
 7     int right_sum = -1000;
 8     int sumleft = 0;
 9     int sumright =0;
10     int max_left;
11     int max_right;
12 
13     for(int i = mid; i >= low; -- i)   //找中间元素左侧最大子数组和
14     {
15         sumleft = sumleft + A[i];
16         if(sumleft > left_sum)
17         {
18             left_sum = sumleft;
19             max_left = i;
20         }
21     }
22 
23     for(int i = mid + 1; i <= high; ++ i)  //找中间元素右侧最大子数组和
24     {
25         sumright = sumright + A[i];
26         if(sumright > right_sum)
27         {
28             right_sum = sumright;
29             max_right = i;
30         }
31     }
32 
33     return left_sum + right_sum;   //两侧和相加再return回去
34 }
35 
36 int find_max_subarray(int A[], int low, int high)
37 {
38     int mid;
39     int left_sum, right_sum, cross_sum;
40 
41     if(high == low)   //仅有一个元素,即分治的base case
42         return A[low];
43     else mid = (low + high)/2;
44     {
45         left_sum = find_max_subarray(A, low, mid);   //若最大子数组和在中间元素左侧
46         right_sum = find_max_subarray(A ,mid + 1, high);  //若最大子数组和在中间元素右侧
47         cross_sum = find_max_crossing_subarray(A, low, mid, high);  //若最大子数组和跨越中间元素。从该行开始到该函数结尾其实是完成合并的过程。
48         if (left_sum >= right_sum && left_sum >= cross_sum)
49             return left_sum;
50         else
51             if(right_sum >= left_sum && right_sum >= cross_sum)
52                 return right_sum;
53             else return cross_sum;
54     }
55 }
56 
57 int main()
58 {
59     int Array[10] = {1,3,-4,2,-1,-3,-1,-3,6,-1};
60 
61     cout << find_max_subarray(Array, 0, 9) << endl;
62 
63     return 0;
64 }

用分治法解决这一问题的时间复杂度为O(nlgn),还有非递归且时间线性的算法。在网上找到一个写的比较好的程序,仅加了注释列在下面:

 1 int main()
 2 {
 3     int *ip;
 4     int j, length, max, sum;
 5     int start1 = 0, start2 = 0;      //start1记录当前找到的最大子数组的起始值。start2向前探测
 6 
 7     printf("Please enter the array's length:");
 8     scanf("%d",&length);
 9     if((ip = (int*)malloc(length*sizeof(int)))==NULL)
10     {
11             fprintf(stderr,"Malloc memory failed !");
12             exit(1);
13     }
14     printf("Enter each element:");
15     for(j = 0; j < length ; j ++)
16     scanf("%d",ip+j);
17 
18     max = INT_MIN;
19     for(sum = j = 0; j < length; j ++)   //sum初始为0,即默认数组中不全为负数。
20     {
21             sum += *(ip+j);
22             if(max < sum)    //当前值能让当前的最大子数组和增大
23             {
24                 start1 = start2;  //若这是第一次找到的最大数组,则start1为初始值0。
25                                   //若这时经历了sum<0后重新找到的新的最大子数组,
26                                   //则把新找到的最大子数组的起始值赋给start1
27                 max = sum;
28             }
29             if(sum < 0){   //sum小于0说明从start1开始到当前值组成的子数组降低的幅度比增加的大,
30                            //该子数组不可能成为最大子数组,所以start2放到当前值的下一个位置,
31                            //重新开始寻找最大子数组。因为要重新寻找,所以sum初始化为0
32                 start2 = j+1;
33                 sum = 0;
34             }
35      }
36      for(j = start1,sum = 0; sum != max; j ++)   //从最大子数组第一个元素开始,一直到实现最大子数组的最大值时结束
37             sum += *(ip+j);
38      printf("\nThe subsequence from %d to %d,max sum is %d\n",start1,j-1,max);
39   return 0;
40 }

若不考虑记录最大子数组的其实位置,则更加简单,主要代码仅10行,主要思想就是负数不加到已得到的最大数值中,一旦发现已得到的最大子数组到当前元素不可能成为最大子数组,就尝试寻找新的最大子数组。

 1   for(int i = 0;  i < n;  i ++)  
 2   {  
 3        if(sum < 0)   //sum<0即说明组成sum的这些元素不能成为最大子数组,但是使sum值降低的这些元素并没有加到已得到的最大子数组中。
 4              //出现sum<0这种情况就开始寻找新的最大数组         
 5          sum = a[i];  
 6        else  
 7          sum += a[i];  
 8        if(max < sum)  
 9          max = sum;  
10    }  
11    return max;  

 

 

 

 

 

转载于:https://www.cnblogs.com/cxl0417/p/3679374.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值