求数组的最大和子数组是经典问题,许多书籍都提到,比如算法导论,编程珠玑。更是覆盖很多面试书,比如编程之美,剑指Offer等。以下是我关于该问题的理解与实现。
1 #include<iostream> 2 #include<cstdlib> 3 #include<climits> 4 using namespace std; 5 6 class SubArray{ 7 private: 8 int left; 9 int right; 10 int sum; 11 public: 12 void set(int l,int r,int s){ 13 14 this->left = l; 15 this->right = r; 16 this->sum = s; 17 } 18 void write(){ 19 20 cout<<this->left<<" "; 21 cout<<this->right<<" "; 22 cout<<this->sum<<endl; 23 } 24 void setLeft(int l){ 25 26 this->left = l; 27 } 28 void setRight(int r){ 29 30 this->right = r; 31 } 32 void setSum(int s){ 33 34 this->sum = s; 35 } 36 int getSum(){ 37 38 return this->sum; 39 } 40 int getLeft(){ 41 42 return this->left; 43 } 44 int getright(){ 45 46 return this->right; 47 } 48 void equal(SubArray sa){ 49 50 this->set(sa.getLeft(),sa.getright(),sa.getSum()); 51 } 52 }; 53 54 55 SubArray max(SubArray a,SubArray b){ 56 return a.getSum() > b.getSum() ? a : b; 57 } 58 59 60 61 //1、暴力求解最大子数组问题 62 SubArray FindMaximumSubArray1(int a[],int low,int high){ 63 64 int sum = INT_MIN; 65 SubArray sa; 66 sa.set(low,low,sum); //初始化子数组 67 for(int i=low;i<=high-1;++i){ 68 69 int temp = 0; 70 for(int j=i;j<=high;++j){ 71 72 temp += a[j]; 73 74 if(sa.getSum()<temp){ 75 76 sa.setSum(temp); 77 sa.setLeft(i); 78 sa.setRight(j); 79 } 80 81 } 82 83 } 84 return sa; 85 } 86 //2、递归(分治法) 87 //2.1 88 //求得跨中点的最大子数组 89 SubArray FindMaxCrossingSubArray(int a[],int low,int mid,int high){ 90 91 int LeftSum = INT_MIN; 92 int RightSum = INT_MIN; 93 int sum = 0; 94 int Left = 0; 95 int Right = 0; 96 SubArray sa; 97 for(int i=mid;i>=low;--i){ 98 99 sum += a[i]; 100 if(LeftSum < sum){ 101 102 LeftSum = sum; 103 Left = i; 104 } 105 } 106 sum = 0; 107 for(int i=mid+1;i<=high;++i){ 108 109 sum += a[i]; 110 if(RightSum < sum){ 111 112 RightSum = sum; 113 Right = i; 114 } 115 } 116 sa.set(Left,Right,LeftSum+RightSum); 117 return sa; 118 119 } 120 SubArray FindMaximumSubArray2(int a[],int low,int high){ 121 122 SubArray sa,Lsa,Rsa; 123 int mid; 124 if(low == high){ 125 126 sa.set(low,high,a[low]); 127 return sa; 128 }else{ 129 130 mid = (low+high)/2; 131 132 Lsa = FindMaximumSubArray2(a,low,mid); 133 Rsa = FindMaximumSubArray2(a,mid+1,high); 134 sa = FindMaxCrossingSubArray(a,low,mid,high); 135 136 if(Lsa.getSum()>Rsa.getSum() && Lsa.getSum()>sa.getSum()) 137 return Lsa; 138 else if(Lsa.getSum()<Rsa.getSum() && sa.getSum()<Rsa.getSum()) 139 return Rsa; 140 else 141 return sa; 142 143 } 144 145 } 146 //线性时间内求得最大子数组 (动态规划) 147 SubArray FindMaximumSubArray3(int a[],int low,int high) { 148 /*以下来自编程之美(从后向前分析) 149 从分治法的到提示: 150 考虑a[low]与a[low]~a[high]的最大和子数组(假设为a[i]~a[j])间的三种关系: 151 1、i=j=low,即a[low]本身就是最大和子数组。 152 2、i=low,j>i,即最大和子数组包含a[low]。 153 3、j>i>low,即最大和子数组与a[low]无关系 154 那么我们可以减问题规模缩减 1 。 155 假设all[1ow+1]表示a[low+1]~a[high]的最大和子数组的和且为已知,所以all[low]即为所求的最大和子数组的和 156 start[low+1]表示以a[low+1]开始的a[low+1]~a[high]的最大和子数组的和 157 all[high] 就是 a[high] 158 start[high]就是 a[high] 159 因此all[low]=max{a[low],a[low]+start[low+1],all[low+1]} 因此用动态规划解决 160 */ 161 /*这里将同时获得子数组位置,因此用上面的自定义的"SubArray"类型进行改写 162 假设sall[i]表示a[i]~a[high]的最大和字数组,所以sall[low]即为所求 163 sstart[i]表示以a[i]开始的a[i]~a[high]的最大和子数组 164 因此sall[high] sstart[high]都为(high,high,a[high]) 165 */ 166 int length = high-low+1; 167 int temp; 168 SubArray *sall,*sstart,sa,rsa; 169 sall = (SubArray *)malloc(sizeof(SubArray)*(length)); 170 sstart = (SubArray *)malloc(sizeof(SubArray)*(length)); 171 sall[high].set(high,high,a[high]); 172 sstart[high].set(high,high,a[high]); 173 for(int i=high-1;i>=low;--i){ 174 175 sa.set(i,i,a[i]); 176 rsa.set(i,sstart[i+1].getright(),sstart[i+1].getSum()+a[i]); 177 //已知sstart[i+1],求得sstart[i] 178 sstart[i] = max(sa,rsa); 179 //已知sstart[i]和sall[i+1]求得sall[i] 180 sall[i] = max(sall[i+1],sstart[i]); 181 } 182 return sall[low]; 183 184 185 } 186 SubArray FindMaximumSubArray4(int a[],int low,int high){ 187 188 /*观察算法,我们利用了以下递推式: 189 sstart[i] = max(sa,rsa); 190 sall[i] = max(sall[i+1],sstart[i]); 191 如果sstart[i].getSum() < 0, sstart[i]=sa(a[i]),这就说明如果某个i使得是sstart[i].getSum()小于0,就要以当前位置i的a[i]计算sstart[i] 192 而且算法3增加了空间复杂度,可以观察到俩个子数组sall[],sstart[]只有sall[i+1]和sstart[i+i]有用,所以可以变换算法为4和5: 193 */ 194 SubArray sall,sstart,sa,rsa; 195 sall.set(high,high,a[high]); 196 sstart.set(high,high,a[high]); 197 for(int i=high-1;i>=low;--i){ 198 sa.set(i,i,a[i]); 199 rsa.set(i,sstart.getright(),sstart.getSum()+a[i]); 200 //已知sstart,求得sstart 201 sstart = max(sa,rsa); 202 //已知sstart和旧的sall求得新的sall 203 sall = max(sall,sstart); 204 } 205 return sall; 206 207 } 208 209 210 SubArray FindMaximumSubArray5(int a[],int low,int high){ 211 212 213 SubArray sall,sstart,sa,rsa; 214 sall.set(high,high,a[high]); 215 sstart.set(high,high,a[high]); 216 for(int i=high-1;i>=low;--i){ 217 218 sa.set(i,i,a[i]); 219 if(sstart.getSum()<0) 220 221 sstart.equal(sa); 222 else 223 sstart.set(i,sstart.getright(),sstart.getSum()+a[i]); 224 225 if(sstart.getSum()>sall.getSum()) 226 sall.equal(sstart); 227 } 228 return sall; 229 230 } 231 232 int main(){ 233 int num; 234 SubArray sa; 235 while(1){ 236 cin>>num; 237 int *array=NULL; 238 array = (int *)malloc(sizeof(int)*(num)); 239 for(int i=0;i<num;i++) cin>>array[i]; 240 sa=FindMaximumSubArray5(array,0,num-1); 241 sa.write(); 242 // for(int i=0;i<num;i++) cout<<array[i]<<" "; 243 } 244 return 0; 245 }
参考:
1、算法导论
2、编程之美
3、博客:http://www.ahathinking.com/archives/120.html
4、博客:http://blog.youkuaiyun.com/v_JULY_v/article/details/6444021