zoukankan      html  css  js  c++  java
  • 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 '
    %r:' % fcn.__name__
            print 'num :%d'% num
            print 'Time:',end - start
            

    由于第一种算法能力太弱,没有测试,事实上加入它时,当n在1000左右时就要等待时间才能得到结果了。大家可以试试执行时间。

  • 相关阅读:
    mysql添加索引
    【最短路】道路重建 @upcexam5797
    【数论&想法题】小C的问题 @"科林明伦杯"哈尔滨理工大学第八届程序设计竞赛
    【最大公约数&链表】权值 @upcexam5921
    【组合数】微信群 @upcexam6016
    【二维树状数组】计数问题 @JSOI2009/upcexam5911
    【组合&取补集】数三角形 @CQOI2014/BZOJ3505/upcexam3843
    【LCA&倍增】货物运输 @upcexam5909
    【组合数】[NOIP2011]选择客栈[c++]
    【模拟】[NOIP2011]铺地毯[c++]
  • 原文地址:https://www.cnblogs.com/SRL-Southern/p/4875248.html
Copyright © 2011-2022 走看看