2015/10/13 算法习题:最大子列和问题
已经正式开始学习数据结构和算法,先学了网易云课堂上的浙江大学的数据结构课,是陈越和何钦铭上的,了解了什么是数据结构和算法后,学习了一些时间空间复杂度分析的技巧,结合之前马虎掌握的学习,先从简单的题目入手学习。
题目是这样的:
给定了一个n个整数组成的序列,求它各个子列中,子列和最大的值。
输入:输入n个整数组成的序列
要求输出最大子列和。
示例:
输入:
-2 11 -4 13 -5 -2输出:
20
做出这题的难度不是很大,至少很容易可以做到暴力求解,然而暴力求解的时间复杂度是很大的。
我用Python写了暴力求解的方法:
def MaxSubSeqSum1(list): #O(n^3) lenth = len(list) MaxSum = 0 for i in range(lenth): for j in range(lenth): ThisSum = 0 for count in range(i,j+1): ThisSum += lst[count] if ThisSum > MaxSum: MaxSum = ThisSum print MaxSum
暴力求解的逻辑很直接,就是按顺序将所有子列和求出来依次比较。
由于有三重嵌套循环,所以执行时间是输入序列长度规模n的三次方,时间复杂度是O(n^3)很显然,这是很差劲的一个算法,在输入量较大时就求不出结果了。
让我们分析一下这个算法执行的过程,可以很明显地看到一个改进的地方:
我们让 i 作为子列的首坐标, j 作为子列的尾坐标,依次递增。
然后 count 在 i 和 j 之间,求i j 之间所有子列的和,比较最大值。
当 i 为 0 ,j 为 0 时,求了lst[0]的值。
当 i 为 0 ,j 为 1 时,求了lst[0], lst[0]+lst[1]的值。
当 i 为 0 ,j 为 2 时,求了lst[0], lst[0]+lst[1], lst[0]+lst[1]+lst[2]的值。
...
我们很容易就发现了一个问题,我们重复计算了前面的值,也就是说,最后一个 count 的 for 循环是根本没有意义的,只是来增加了算法的时间而已。
当然,此处是故意添加的这个问题,正常情况下不会如此写这个问题。
于是,有了第二个算法,去除count的循环:
def MaxSubSeqSum2(list): #O(n^2) MaxSum = 0 lenth = len(list) for i in range(lenth): ThisSum = 0 for j in range(i,lenth): ThisSum += list[j] if ThisSum > MaxSum: MaxSum = ThisSum return MaxSum
这个算法时间复杂度是O(n^2),是正常想到的最常规的解法。
一般一个时间复杂度是O(n^2)的问题,都会想把它改成O(nlogn)的问题。
考虑分而治之的方法是否可行,如果采用分治法,需要讲问题规模减小。
求一个序列最大子列和,是前1/2序列的最大子列和,后1/2序列最大子列和,跨中间边界的最大子列和,三个数的最大值。这样子不断分隔,自然可以使用分治法。
举例如下:
一个序列是[-1, 2, 7, -3] 这个序列的最大子列和是下面这三个数中最大的:
[-1, 2]这个序列的最大子列和, [7, -3]这个序列的最大子列和, 经过2,7边界的序列的最大和。
同理,[-1, 2]这个序列的最大子列和,是[-1],[2],和经过-1,2边界的序列的最大和。
将一个问题分解成一个易于解决的问题(经过边界的序列的最大和)和两个规模较小的原问题。
而经过边界的序列的最大和是很简单的,等于从中间开始向左遍历的最大序列和,以及从中间开始向右遍历的最大序列和,然后将左右最大值相加。
以下是实现代码:
def MaxSubSeqSum3(list): #O(nlogn) lenth = len(list) def maxSum(list, left, right): if left == right: if list[left] > 0: return list[left] else: return 0 else: center = int((left+right)/2) maxLeftSum = maxSum(list, left, center) maxRightSum = maxSum(list, center+1, right) maxLeftBorderSum = 0 leftBorderSum = 0 for i in range(center, left-1, -1): leftBorderSum += list[i] if leftBorderSum > maxLeftBorderSum: maxLeftBorderSum = leftBorderSum maxRightBorderSum = 0 rightBorderSum = 0 for i in range(center+1, right+1): rightBorderSum += list[i] if rightBorderSum > maxRightBorderSum: maxRightBorderSum = rightBorderSum return max(maxLeftSum, maxRightSum,\ maxLeftBorderSum + maxRightBorderSum) return maxSum(list, 0, lenth-1)
这个算法的时间复杂度具体求解过程不在这里展开,是O(nlogn)
一般来说一个O(nlogn)的算法已经足够优秀,但是这个问题其实还有O(n)的算法,也是最快的算法了,因为必须遍历数据才能知道大小:
def MaxSubSeqSum4(list): #O(n) MaxSum = 0 lenth = len(list) ThisSum = 0 for i in range(lenth): ThisSum += list[i] if ThisSum > MaxSum: MaxSum = ThisSum elif ThisSum < 0: ThisSum = 0 return MaxSum
这个算法看到代码后推敲就容易理解这个思路了。不详述。
-------------------------------------------------
同时,为了测试这些算法,写了一个生成随机数表的函数和测试函数,测试各个函数的运行时间和结果:
import random import time def MakeIntSeq(n, low, high): list = [] for i in range(n): list.append(random.randint(low,high)) return list .... def test(n, low, high): lst = MakeIntSeq(n, low, high) for fcn in [MaxSubSeqSum4,MaxSubSeqSum3,MaxSubSeqSum2]: start = time.clock() num = fcn(lst) end = time. clock() print '\n%r:' % fcn.__name__ print 'num :%d'% num print 'Time:',end - start
由于第一种算法能力太弱,没有测试,事实上加入它时,当n在1000左右时就要等待时间才能得到结果了。大家可以试试执行时间。