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左右时就要等待时间才能得到结果了。大家可以试试执行时间。

 

posted @ 2015-10-14 23:46  #SRL  阅读(731)  评论(0编辑  收藏  举报