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