算法导论详解(3) 第四章最大子数组+矩阵乘法+Python实现

本章讲解更多关于分治策略的算法。第一个算法是求解最大子数组的问题,然后是求解 n×n 矩阵乘法问题的分治算法,最后介绍了主方法。

分治策略简介

分治策略在每层递归时都有三个步骤:
- 分解原问题为若干子问题;子问题的形式与原问题一样,只是规模更小。
- 解决这些子问题,递归地求解各子问题。如果子问题的规模足够小,则停止递归,直接求解。
- 合并这些子问题的解成原问题的解。

递归情况(recursive case)
基本情况(base case):子问题足够小的时候,递归已经“触底”时。

递归式:我们用递归式描述了MERGE-SORT过程的最坏情况运行时间 T(n)

T(n)={Θ(1)2T(n/2)+f(n)n=1n>1

求解递归式的方法:代入法(猜测);递归树法;主方法。本书使用主方法。

主方法可求解形如下面公式的递归式的界:

T(n)=aT(n/b)+f(n)

其中, a1,b>1,f(n) 是一个给定的函数。

递归式的技术细节
- 忽略递归式声明和求解的一些细节,如MERGE-SORT的最坏情况运行时间准确的递归式为:

T(n)={Θ(1)T(n/2)+T(n/2)+Θ(n)n=1n>1

  • 边界条件是我们通常忽略的细节。
  • 当声明、求解递归式时,我们常常忽略向下取整、向上取整及边界条件。

本章讲解更多关于分治策略的算法。第一个算法是求解最大子数组的问题,然后是求解 n×n 矩阵乘法问题的分治算法。

最大子数组问题(4.1,P38)

问题

买股票(低价买入,高价卖出)。给定一段时间,选取最大收益。

问题变换

不关注每天的价格,而是关注每日价格变化。
那么问题就转化为寻求价格变化数组A的最大非空连续子数组。
称这样的连续子数组为最大子数组

使用分治策略的求解方法

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# MAX = 1 << 31
import math


def find_max_crossing_subarray(A, low, mid, high):
    max_left = mid
    max_right = mid
    left_sum = 0  # original version init with -max, now is zero,because minimum is zero
    all_sum = 0
    for i in range(mid - 1, low - 1, -1):  # 左边不经过mid
        all_sum += A[i]
        if all_sum > left_sum:
            left_sum = all_sum
            max_left = i
    right_sum = A[mid]  # original version init with -max, now is A[mid],because A[mid] must be included.
    all_sum = 0
    for i in range(mid, high + 1):
        all_sum += A[i]
        if all_sum > right_sum:
            right_sum = all_sum
            max_right = i
    # print([low, mid, high], [max_left, max_right, left_sum + right_sum])
    return [max_left, max_right, left_sum + right_sum]


def find_maximum_subarray(A, low, high):
    if high == low:
        return [low, high, A[low]]
    else:
        mid = math.floor((low + high) / 2)
        left_low, left_high, left_sum = find_maximum_subarray(A, low, mid)
        right_low, right_high, right_sum = find_maximum_subarray(A, mid + 1, high)
        cross_low, cross_high, cross_sum = find_max_crossing_subarray(A, low, mid, high)
        if left_sum >= right_sum and left_sum >= cross_sum:
            return [left_low, left_high, left_sum]
        elif right_sum >= left_sum and right_sum >= cross_sum:
            return [right_low, right_high, right_sum]
        else:
            return [cross_low, cross_high, cross_sum]


if __name__ == "__main__":
    A = [1, -3, 7, -1, 4, -1, -5, 3, -1, 3, -5, 9]
    # A = [1, -3, 7, -5, -4, -1, -9, -3, 1, -3, -5, -9]
    # print(find_max_crossing_subarray(A, 0, 6, len(A) - 1))
    print(find_maximum_subarray(A, 0, len(A) - 1))

分治算法的分析

假设问题规模为2的幂,这样所有问题的规模都是整数。

find_maximum_subarray函数中,需要求解两个子问题——左数组和右数组(分别为5/6行),每个子问题的运行时间为 T(n/2) ,两个子问题加起来就是 2T(n/2)
第7行,find_max_crossing_subarray函数求解跨越中点的子数组,花费线性的时间,为 Θ(n)

总的运行时间递归式为:

T(n)={Θ(1)2T(n/2)+Θ(n)if  n=1if  n>1

与鬼归并排序的递归式相同。在4.5节用主方法求解该递归式,其解为 T(n)=Θ(n lgn)

线性复杂度的解法–习题4.1-5(P42)

主要思想:从左到右处理,记录目前为止已经处理的最大子数组。非递归、线性复杂度。

从左到右累加,如果当前子数组的累加和小于零,则意味着最大子数组(maximun subarray)肯定不包括该子数组,所以果断舍弃,重新开始累加。

该解法的python实现:

 def find_maximum_subarray(A):
    j = 0
    max_sum = 0
    left = -1
    cur_left = 0
    right = -1
    sum = 0
    for j in range(0, len(A)):
        sum = sum + A[j]
        if sum > max_sum:
            max_sum = sum
            left = cur_left
            right = j
        elif sum < 0:
            sum = 0
            cur_left = j + 1
    if max_sum > 0:
        return left, right, max_sum
    return None

矩阵乘法的Strassen算法(4.2,P43)

A=(aij),B=(bij) nxn 的方阵,则对 i,j=1,2,,n ,定义矩阵乘积 C=AB 中的 cij 为:

cij=k=1naikbkj

写成程序,是一个三重循环,因此,复杂度为 Θ(n3)

def SQUARE_MATRIX_MULTIPLY(A, B):  
    assert(len(A) == len(B))  
    n = len(A)  
    C = [[0 for col in range(n)] for row in range(n)]  
    for i in range(0, n):  
        for j in range(0, n):  
            for k in range(0, n):  
                C[i][j]= C[i][j] + A[i][k]*B[k][j]  
    return C

一个简单的分治算法(4.2,P43)

假定三个矩阵均为 n×n 矩阵,其中n为2的幂。在每个分解步骤中, n×n 矩阵都被划分为4个 n/2×n/2 的子矩阵,如下:

A=[A11A21A12A22],B=[B11B21B12B22],C=[C11C21C12C22]

因此,公式 C=AB 改写成:
[C11C21C12C22]=[A11A21A12A22][B11B21B12B22]

等价于:
C11=A11B11+A12B21C12=A11B12+A12B22C21=A21B11+A22B21C22=A21B12+A22B22

该简单分治算法的总运行时间递归式为:

T(n)={Θ(1)8T(n/2)+Θ(n2)if  n=1if  n>1

Strassen 方法(4.2,P45)

为减小时间复杂度,采用Strassen 法,其原理仍将讲矩阵A,B,C划分成n/2 x n/2 ,然后按如下计算:

即:先创建10个矩阵 S1,,S10 ,由于进行了10次 n/2×n/2 矩阵的加减法,所以该步骤花费 Θ(n2) 时间。

接着,递归地计算七次 n/2×n/2 矩阵的乘法,即计算 P1,,P7 矩阵。

最后计算结果矩阵C的子矩阵 C11,C12,C21,C22

其时间复杂度为:

T(n)={Θ(1)7T(n/2)+Θ(n2)if  n=1if  n>1

利用4.5节的主方法,可以求出上述的解为:

T(n)=Θ(nlg7)

用主方法求解递归式(4.5,P53)

主方法依赖于主定理。

主定理

a1 b>1 是常数, f(n) 是一个函数, T(n) 是定义在非负整数上的递归式:

T(n)=aT(n/b)+f(n)

其中,我们将 n/b 解释为 n/b n/b 。那么 T(n) 有如下的渐近界:

  1. 若对某个常数 ϵ>0 f(n)=O(nlogbaϵ) ,则 T(n)=Θ(nlogba)

  2. f(n)=Θ(nlogba) ,则 T(n)=Θ(nlogbalgn)

  3. 若对某个常数 ϵ>0 f(n)=Ω(nlogba+ϵ) ,且对某个常数 c<1 和所有足够大的n有 aT(n/b)cf(n) ,则 T(n)=Θ(f(n))

以上就是主定理的完整叙述。

解释:我们将函数 f(n) nlogba 进行比较。直觉上,两个函数较大者决定了递归式的解。
情况1表示:函数 nlogba 更大,则解为 T(n)=Θ(nlogba)
情况3表示:函数 f(n) 更大,则解为 T(n)=Θ(f(n))
情况2表示:当两个函数大小相当,则乘上一个对数因子,解为 T(n)=Θ(nlogbalgn)

上述的大于/小于都是多项式意义上的,也就是渐近小于(大于)。每种情况之间都有一定的间隙。若 f(n) 落在间隙中,就不能使用主方法。

使用主方法

使用主方法,只需要确定主定理的哪种情况成立,即可以得到解。

下面举几个例子。

T(n)=9T(n/3)+n

上式中, a=9,b=3,f(n)=n ,因此, nlogba=nlog39=Θ(n2) 。由于 f(n)=O(nlog39ϵ) ,其中 ϵ=1 ,所以应用主定理的情况1,从而得到 T(n)=Θ(n2)

T(n)=T(2n/3)+1

上式中, a=1,b=3/2,f(n)=1 ,因此, nlogba=nlog3/21=n0=1 ,由于 f(n)=Θ(nlogba)=Θ(1) ,所以,适用于情况二,从而得到最终解为 T(n)=Θ(lgn)

归并排序和最大子数组方法的运行时间的递归式:

T(n)=2T(n/2)+Θ(n)

同理, nlogba=nlog22=n , 由于 f(n)=Θ(n) ,所以应用情况2,得到解 T(n)=Θ(nlgn)

矩阵乘法的第一个分治算法的运行时间:

T(n)=8T(n/2)+Θ(n2)

上式,有: nlogba=nlog28=n3 n3 多项式意义上大于 f(n) ,因此应用情况1,解为 T(n)=Θ(n3)

矩阵乘法的Strassen算法运行时间:

T(n)=7T(n/2)+Θ(n2)

上式中,有 nlogba=nlog27=nlg7 ,由于 2.80<lg7<2.81 ,对 ϵ=0.8 ,有 f(n)=O(nlg7ϵ) ,故应用情况1,得到: T(n)=Θ(nlg7)

参考资料

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值