《算法导论》一书中对最大字段和可谓讲的是栩栩如生,楚楚动人。如果简单的说最大字段和,没有意义。而《算法导论》上举了一个股票的例子。根据股票每天结束的价格来求出一段时间内何时买入何时卖出能是收益最大。把问题做一个转换,求出相邻天数的股票价格的差值(周二 - 周一 = 差值),然后求出连续天数差值和的最大值,即为最大收益,所以就是最大子段和的问题。
还有一点说明的是算法的实现是和语言没有关系的,下面是用OC来实现的,你也可以用Java, PHP, C++等你拿手的语言进行实现,算法注重的还是思想。
求此问题是通过分治法来做的,通过递归方式来进行分治。原问题可以分为三种情况,求原数组中左半的最大字段和,求原数组中右半部最大字段和,求跨越中间位置部分的最大字段和,然后在三个最大字段和中去最大的字段和,即为原问题的解。即为分解,计算,合并的过程。
一、求解跨越中点部分的最大字段和
1.编写相应的代码
分析:跨越中点的子数组有一个共同特点, 就是都可以被分为两部分Array[i] (low <= i <= mid) 和 Array[j](mid < j <= high)两部分,所以我们求出这两部分的最大字段和,然后相加,就是我们要求的跨越中点部分的最大字段和。具体代码如下:
思路分析:
1.先求左边的最大字段和并记录对应的起始位置的下标
2.在求右边的最大字段和,并记录对应的结束位置的下标
3.把两个和相加,即为求的解
1 //一次求解跨越中点的最大字段和(跨越中点的最大字段和可以分为Array[i>=low…………mid]和Array[mid+1……j<=high]两部分, 2 //所以求出两部分的字段和进行相加,就是跨越中点的最大字段和) 3 +(NSMutableDictionary *) findMaxCrossingSubarrayWithArray: (NSMutableArray *)array 4 WithLow: (NSInteger) low 5 WithMid: (NSInteger) mid 6 WithHigh: (NSInteger) high 7 { 8 //暂存左边的最大字段和 9 NSInteger leftMaxSum = 0; 10 NSInteger leftTempSum = 0; 11 12 //leftMaxStarIndex默认只是不再左边 13 NSInteger leftMaxStarIndex = mid; 14 15 //循环求解左边含有mid元素的最大字段和 16 for (NSInteger i = mid; i >= low; i --) 17 { 18 leftTempSum += [array[i] intValue]; 19 20 //暂存最大字段和 21 if (i == mid || leftTempSum > leftMaxSum) { 22 leftMaxSum = leftTempSum; 23 leftMaxStarIndex = i; 24 } 25 26 } 27 28 29 30 31 //右边的字段和的计算 32 //暂存左边的最大字段和 33 NSInteger rightMaxSum = 0; 34 NSInteger rightTempSum = 0; 35 36 NSInteger rightMaxEndIndex = mid; 37 38 //循环求解左边含有mid元素的最大字段和 39 for (NSInteger i = mid + 1; i <= high; i ++) 40 { 41 rightTempSum += [array[i] intValue]; 42 43 //暂存最大字段和 44 if (i == mid+1 || rightTempSum > rightMaxSum) { 45 rightMaxSum = rightTempSum; 46 rightMaxEndIndex = i; 47 } 48 } 49 50 51 52 NSLog(@"leftStarIndex = %ld, rightEndIndex = %ld, subMaxSum = %ld", leftMaxStarIndex, rightMaxEndIndex, leftMaxSum + rightMaxSum); 53 //获取最大子数组 54 NSRange subRange = NSMakeRange(leftMaxStarIndex, rightMaxEndIndex-leftMaxStarIndex+1); 55 56 NSArray *subArray = [array subarrayWithRange: subRange]; 57 NSLog(@"本轮递归所得最大子数组如下:"); 58 [Sort displayArrayWithArray:(NSMutableArray *)subArray]; 59 60 61 62 NSMutableDictionary *resultDic = [NSMutableDictionary dictionaryWithCapacity: 3]; 63 [resultDic setObject:@(leftMaxStarIndex) forKey:kLEFTSTARINDEX]; 64 [resultDic setObject:@(rightMaxEndIndex) forKey:kRIGHTENDINDEX]; 65 [resultDic setObject:@(leftMaxSum + rightMaxSum) forKey:kSUBMAXSUM]; 66 67 return resultDic; 68 69 }
2.对上面的方法进行测试,我们还是生成随机的数组,但是数组中要有正数和负数,生成随机数的代码如下:
1 //生成测试随机数组 2 NSMutableArray *array = [[NSMutableArray alloc] init]; 3 UInt count = 10; 4 for (int i = 0; i < count; i ++) { 5 NSInteger tempInteger = arc4random()%100; 6 7 if (tempInteger%2 == 1) { 8 tempInteger = -tempInteger; 9 } 10 NSNumber *temp = @(tempInteger); 11 [array addObject:temp]; 12 }
3.进行上面代码的测试
(1),如果数组中只有一个数,那个最大字段和就是本身,测试代码如下:
1 //一次寻找跨过中点的最大字段和 2 [Sort findMaxCrossingSubarrayWithArray:array WithLow:0 WithMid:(NSInteger)(array.count-1)/2 WithHigh:array.count-1];
运行结果如下:
如果数组中又两个数那么就是两个数的和,运行结果如下:
下面是10个数据运行的结果,最大子数组肯定是包括array[mid]这一项的,因为我们求得就是过中点的最大字段和。
二、递归分解问题
下面我们将递归把问题分解成更小的问题,对于被程序来说就是把原始数组递归分解成单个元素,这样单个元素的最大字段和就是本身了,然后我在进行子问题的合并,在求解的过程中我们要求出过中点的最大字段和,递归函数如下:
1 +(NSMutableDictionary *) findMaxSubArrayWithArray: (NSMutableArray *) array 2 WithLow: (NSInteger) low 3 WithHigh: (NSInteger) high 4 { 5 NSMutableDictionary *resultDic = [[NSMutableDictionary alloc] initWithCapacity:3]; 6 //递归结束条件:递归到只有一个元素时结束递归 7 if (low == high) { 8 resultDic[kLEFTSTARINDEX] = @(low); 9 resultDic[kRIGHTENDINDEX] = @(high); 10 resultDic[kSUBMAXSUM] = array[low]; 11 12 return resultDic; 13 } 14 15 16 NSInteger mid = (low + high) / 2; 17 //递归左半部分 18 NSMutableDictionary * leftResultDic = [self findMaxSubArrayWithArray:array WithLow:low WithHigh:mid]; 19 20 //递归右半部分 21 NSMutableDictionary * rightResultDic = [self findMaxSubArrayWithArray:array WithLow:mid + 1 WithHigh:high]; 22 23 //计算中间部分 24 NSMutableDictionary * midResultDic = [self findMaxCrossingSubarrayWithArray:array WithLow:low WithMid:mid WithHigh:high]; 25 26 //找出三部分中的最大值 27 if([leftResultDic[kSUBMAXSUM] intValue] >= [rightResultDic[kSUBMAXSUM] intValue] && [leftResultDic[kSUBMAXSUM] intValue] >= [midResultDic[kSUBMAXSUM] intValue]) 28 { 29 return leftResultDic; 30 } 31 32 if([rightResultDic[kSUBMAXSUM] intValue] >= [leftResultDic[kSUBMAXSUM] intValue] && [rightResultDic[kSUBMAXSUM] intValue] >= [midResultDic[kSUBMAXSUM] intValue]) 33 { 34 return rightResultDic; 35 } 36 37 38 return midResultDic; 39 }
先递归分解左半部分,求出左半部分的最大字段和,然后再求出右半部分的最大字段和,最后求出过中点的最大字段和,然后合并解,求出三者中最大的,下面的代码是进行测试调用的代码:
1 //求最大字段和 2 NSMutableDictionary *dic = [Sort findMaxSubArrayWithArray:array WithLow:0 WithHigh:array.count-1]; 3 4 NSLog(@"最终结果如下:================================================"); 5 6 NSLog(@"leftStarIndex = %@, rightEndIndex = %@, subMaxSum = %@", dic[kLEFTSTARINDEX], dic[kRIGHTENDINDEX], dic[kSUBMAXSUM]); 7 //获取最大子数组 8 NSRange subRange = NSMakeRange([dic[kLEFTSTARINDEX] intValue], [dic[kRIGHTENDINDEX] intValue]-[dic[kLEFTSTARINDEX] intValue]+1); 9 10 NSArray *subArray = [array subarrayWithRange: subRange]; 11 NSLog(@"最大子数组如下:"); 12 [Sort displayArrayWithArray:(NSMutableArray *)subArray];
下面是具体的运行结果:
三、最后给出暴力求解的代码如下,上面的时间复杂度是O(nlgn), 而暴力求解为O(n^2),代码如下:
1 //暴力求最大字段和 2 +(void)findMaxSubSumArrayWithArray: (NSMutableArray *) array{ 3 4 5 NSInteger tempSum = 0; 6 7 NSInteger starIndex = 0; 8 NSInteger endIndex = 0; 9 10 for (int i = 0; i < array.count; i ++) 11 { 12 NSInteger sum = 0; 13 for (int j = i; j < array.count; j ++) 14 { 15 sum += [array[j] integerValue]; 16 17 if (j == 0 || tempSum <= sum) 18 { 19 tempSum = sum; 20 starIndex = i; 21 endIndex = j; 22 } 23 24 } 25 26 } 27 NSLog(@"\n\n暴力求解的结果如下:"); 28 NSLog(@"leftStarIndex = %ld, rightEndIndex = %ld, subMaxSum = %ld", starIndex, endIndex, tempSum); 29 //获取最大子数组 30 NSRange subRange = NSMakeRange(starIndex, endIndex-starIndex+1); 31 32 NSArray *subArray = [array subarrayWithRange: subRange]; 33 NSLog(@"本轮递归所得最大子数组如下:"); 34 [Sort displayArrayWithArray:(NSMutableArray *)subArray]; 35 36 }
运行结果如下(上面是递归求解,下面是暴力破解):