算法导论在分治策略一章中提到了最大子数组和问题,我用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;