算法优化:最大m个子段和,问题规模从1个子段和扩展到m个,动态规划

最大m个子段和,问题规模从1个子段和扩展到m个,动态规划

问题规模由2个决定,一是子段数m,二是元素个数n,准确的说是最后一个子段终止的标号

b(i,j)定义为:前j个元素中有i个子段,且第i个子段包含j,i个子段和为b(i,j)
那么原问题的最优解为max{b(m,j)},m<=j<=n
还是不是针对原问题动态规划,动态规划的问题是可能解,最后在可行解里面找最优解

根据b(i,j)定义,有两种情况,第一种情况arr[i]是第i个子段的一部分,那前面j-1的尾巴也是属于第i个子段,可以把前面j-1也看成有i个子段,即b(i,j-1) + arr[j];另外一种情况arr[i]就是第i个子段的全部,也就是前面j-1的尾巴不属于第i个子段,那我们必须在前j-1个元素里找到i-1个子段使之和最大(类似原始问题max{b(m,j)},m<=j<=n),所以我们这里是max{b(i-1,k)},i-1<=k<=j-1,即第二种情况为:max{b(i-1,k)} + arr[j] ,i-1<=k<=j-1

状态方程为:

在这里插入图片描述

代码实现如下:

#%%
def maxMSum(arr,m):
    n = len(arr)
    b = np.zeros((m+1,n+1),int)
    
    for i in range(1,m+1):
        # (i,n+1-m+i),本来是(i,n+1),可以发现有些没有必要计算,
        # 因为[i][j]总是取左边或者左上的数据     
        for j in range(i,n+1-m+i):
            # 最后刚好子段个数==元素个数时,就是相当于一个元素一个子段
            if j == i:
                b[i][j] = b[i-1][j-1] + arr[j-1]       
            # 其他的情况需要按照状态方程比较
            else:
                # 第一种情况
                b[i][j] = b[i][j-1] + arr[j-1]
                # 第二种情况
                for k in range(i-1,j):
                    if b[i-1][k] + arr[j-1] > b[i][j]:
                        b[i][j] = b[i-1][k] + arr[j-1]
    # 最后一行都是可行解,取最大为最优解
    return b,max(b[m][:])


arr = [-2,11,-4,-4,13,-5,-2,1,1,5,8,-1]
print(maxMSum(arr,3))   


(array([[ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0],
       [ 0, -2, 11,  7,  3, 16, 11,  9, 10, 11, 16,  0,  0],
       [ 0,  0,  9,  7,  7, 24, 19, 17, 18, 19, 24, 32,  0],
       [ 0,  0,  0,  5,  5, 22, 19, 22, 25, 26, 31, 39, 38]]), 39)

从代码或者从状态方程我们可以看出

在这里插入图片描述

只是使用了两行b[i-1][:j]和b[i][j-1]数据,可以利用滚动数组优化,如何优化了?从这里看至少需要2行才行。

b[i-1][:j]用于求max{b(i-1,k)} + arr[j],我们可以使用一个数组c专门记录max{b(i-1,k)} ,c[j-1]代表k从i-1到j-1里面最大值,b[i][j-1]直接使用当前的数组b[j-1]就行了,当前行b[j] = max{c[j-1],b[j-1]} + arr[j], 此时还需要做一件事,时刻记录b到目前位置j的最大值,把b当前最大值cur_max一直放在空中,一旦得到一个新的b大于空中的值的话更新空中的值cur_max,这个值代表的时0到j的最大值,把更新之前的cur_max放到c[j-1](记住这一层b[j+1]待会儿用的是c[j]),留给下一层也就是i+1层使用,i+1层的b[j] 用的是上一层留下的c[j-1]。

这样使用滚动数组节约了空间消费,使用临时cur_max一轮比较就把一行的最大值都更新了,避免后续每一次都用for循环算一遍。

代码实现如下:

def maxMSum_opt(arr,m):
    n = len(arr)
    b = [0]*(n+1)
    # 数组c专门记录max{b(i-1,k)} ,c[j-1]代表k从i-1到j-1里面最大值,需要初始化
    # 参考b的第0行可以得到c的初始化如下
    c= [0]*(n+1)
    
    for i in range(1,m+1):
        # 先把第一个元素处理一下,也就是j == i,我们只使用一个一维的滚动数组
        b[i] = b[i-1] + arr[i-1]
        # 我们需要一个变量记录当前一行的到当前位置的最大值
        cur_max = b[i]
        
        # (i,n+1-m+i),本来是(i,n+1),可以发现有些没有必要计算,
        # 因为[i][j]总是取左边或者左上的数据     
        # 第一个元素上面已经处理了
        for j in range(i+1,n+1-m+i):
            # 获取b[j]当前的值
            b[j] = max(b[j-1],c[j-1]) + arr[j-1]
            # c[j-1] 使用了之后,就要更新c[j-1]留给下一层使用,值为还未更新的cur_max
            c[j-1] = cur_max
            # 更新后的cur_max要放在c[j],但是旧的c[j]还没使用了,所以c[j]的更新是滞后一步的
            if cur_max <  b[j]:
                cur_max = b[j]
        # 我们会发现内存循环完了,最后的cur_max还没更新到c里面,因为c的更新是滞后一步
        # 得再次进入循环才能更新的,注意这里c的标号
        c[n-m+i] = cur_max
           

    # 最后一行都是可行解,取最大为最优解
    return b,max(b[:])


arr = [-2,11,-4,-4,13,-5,-2,1,1,5,8,-1]
print(maxMSum_opt(arr,3))         


([0, -2, 9, 5, 5, 22, 19, 22, 25, 26, 31, 39, 38], 39)

猜你喜欢

转载自blog.csdn.net/weixin_40759186/article/details/85038632
今日推荐