《算法导论读书笔记》(第一部分 第四章 分治策略(4.1~4.2))

第一部分 基础知识(3)

第四章 分治策略(4.1~4.2)

前面的归并排序,利用了分治策略。

分治策略中我们采用递归:每层递归中应用如下三个步骤:

分解(Divide),解决(Conquer),合并(Combine)

根据要不要递归又分为递归情况(recursive case)和基本情况(base case)。

递归式

递归式(recurrence)就是一个等式或不等式, 它通过更小的输入上的函数值来描述一个函数。

递归式可以有很多形式。

子问题的规模不必是原问题规模的一个固定比例。

例子略

本章介绍三种求解递归式的方法,即得出算法的“Θ”或“O“渐进界的方法:

代入法:我们猜测一个界,然后用数学归纳法证明这个界是正确的。

递归树法:将递归式转换为一棵树,其结点表示不同层次的递归调用产生的代价。然后采用边界和技术来求解递归式。

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

KaTeX parse error: Undefined control sequence: \fr at position 116: …,每个子问题规模是原问题规模的\̲f̲r̲1c{1}{b},分解和合并步…

主方法最好用


偶尔遇到不等式的递归式,如 T ( n ) ≤ 2 T ( n 2 ) + Θ ( n ) T(n)\le 2T(\frac{n}{2})+ \Theta(n) T(n)2T(2n)+Θ(n)这样仅仅描述了T(n)的一个上界,因此可以用大O符号而不是Θ符号来描述其解。类似的,只描述下界则使用Ω符号。

递归式技术细节

如我们在MERGE-SORT中n是奇数时n/2不是整数被忽略。

边界条件是另一类我们常忽略的细节。如把常数因子项省略。

4.1 最大数组问题

解决股票买进卖出问题(开挂股票)

我们一般说一个最大子数组,因为可能不止一个;

使用分治策略的求解方法

思考怎么分治;

如数组A[low…high]分为A[low…mid]和A[mid+1…hight], 然后任何连续子数组A[i…j]所处的位置为三种情况之一;

在这里插入图片描述

对于这三种情况,除了跨越中点的子数组外的其余两数组是规模更小的原问题;

跨越中点的也只要在A[i…mid]和A[mid+1…j]线性查找即可

FIND-MAX-CROSSING-SUBARRAY(A, low, mid, high)

left_sum = -∞
sum ← 0
for i = mid downto low
    sum = sum + a[i]
    if sum > left_sum  // 不断累加检查
        left_sum = sum
        max_left = i
right_sum = -∞
sum = 0
for j = mid + 1 to high
    sum = sum + a[j]
    if sum > right_sum
        right_sum = sum
        max_right = j
return (max_left, max_right, left_sum + right_sum)

θ(n) , 总迭代次数为n


有了这个线性时间的方法,我们就可以设计求解最大子数组问题的分治算法的伪代码了:

FIND-MAXIMUM-SUBARRAY(A, low, high)

if hight == low
    return (low, high, A[low])      // 平凡情况(base case: only one element)
else mid = ⌊(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-MAXIMUM-CROSSING-SUBARRAY(A, low, mid, high)
if left_sum >= right_sum and left_sum >= cross_sum   // 返回最大值
    return (left_low, left_high, left_sum)
elseif right_sum >= left_sum and right_sum >= cross_sum
    return (right_low, right_high, right_sum)
else
    return (cross_low, cross_high, cross_sum)
分治算法的分析

建立一个递归式来描述递归过程 FIND-MAXIMUM-SUBARRAY的运行时间。

照例先简化,假设原问题的规模为2的幂,所有子问题规模就都是整数。

T(1) = Θ(1)

四五行两个子问题求解规模都是一半,

因此,增加了2T(n/2),

下一行已知是Θ(n)时间,

接下去的判断是常数时间Θ(1)。因此:

T ( n ) = Θ ( 1 ) + 2 T ( n 2 ) + Θ ( n ) + Θ ( 1 ) = 2 T ( n 2 ) + Θ ( n ) \color{#0000FF} T(n) = \Theta(1) + 2T(\frac{n}{2}) + \Theta(n)+ \Theta(1) = 2T(\frac{n}{2}) + \Theta(n) T(n)=Θ(1)+2T(2n)+Θ(n)+Θ(1)=2T(2n)+Θ(n)

结合上式有:
T ( n ) = { Θ ( 1 ) , 若 n = 1 2 T ( n 2 ) + Θ ( n ) , 若 n > 1 T(n)= \begin{cases} \Theta(1) \quad ,若n=1\\ 2T(\frac{n}{2}) + \Theta(n) \quad ,若n>1 \end{cases} T(n)={Θ(1),n=12T(2n)+Θ(n),n>1

和归并排序的一样

我们在4.5节将看到用主方法求解此递归式,其解为T(n) = Θ(nlgn)

暴力解法由于有 ( n 2 ) 种 组 合 , 因 为 ( n 2 ) = Θ ( n 2 ) , 运 行 时 间 为 Ω ( n 2 ) \binom{n}{2}种组合,因为\binom{n}{2}= \Theta(n^2), 运行时间为\Omega(n^2) (2n)(2n)=Θ(n2),Ω(n2)

练习

4.1-1 当A的所有元素均为负数时,FIND-MAXIMUM-SUBARRAY返回什么?

最大和是0, 也就是空数组的和,所以返回值是0

4.1-2 暴力求解,时间为Θ(n2)

Step 1 of 2:

The pseudo code for the brute-force method is:

Input: integer n >= 1 and array A[0…n-1] of integers.

Output: computes the maximum sum S that can be obtained by adding some number of consecutive elements.


S = 0
for i = 0 to n-1
    do p = 0
    for j = i to n-1
        do p = p + A[j]
        if p > S
            then S = p
        endif
    endfor
endfor

Step 2 of 2:

∑ i = 0 n − 1 ∑ j = i n − 1 1 = ∑ i = 0 n − 1 n − i ∈ Θ ( n 2 ) \sum_{i=0}^{n-1}\sum_{j=i}^{n-1}1 = \sum_{i=0}^{n-1}n-i \in \Theta(n^2) i=0n1j=in11=i=0n1niΘ(n2)

Note: In order to achieve Θ(n2) operations, we cannot compute the sum of each range from
scratch. It is necessary to construct a partical sum § and update it as we extend j (with a fixed i).

4.1-3 性能交叉点

TODO: <13-09-20, wgc> >

4.1-4 假定修改最大子数组问题的定义,允许结果为空子数组,其和为0。你应该如何修改现有算法,使它们能允许空子数组为最终结果

Suppose we change the definition of the maximum-subarray problem to allow the result to be an empty subarray,
where the sum of the values of an empty subarray is 0. How would you change any of the algorithms that do not
allow empty subarray to permit an empty subarray to be the result?


Step 1 of 1

We will modify it as:

The FIND-MAXIMUM-SUBARRAY(A, low, high)

// if array is empty
1. for i = low to high
2.    if A[i] == -1  // or error message
          // array is empty
3.        return -1
      // base case: only one element
4.    else if high == low
5.        return A[low]
6.    else
7.        mid = ⌊(high + low)/2⌋
8.        FIND-MAXIMUM-SUBARRAY(A, low, mid)
9.        FIND-MAXIMUM-SUBARRAY(A, mid+1, high)
10.       FIND-CROSSING-MAXIMUM-SUBARRAY(A, low, mid, high)
11.       Compare all three sub arrays to find the max
12.       return max

其实就是加一个为空时判断就好

4.1-5 使用如下思想设计一个非递归的、线性时间的算法。从数组的左边界到右处理,记录到目前为止已经处理过的最大子数组。已知A[1…j]的最大子数组,扩展到j+1, 可见要么是原数组的最大子数组,要么是某个子数组A[i…j+1]。在这种情况下可以线性时间找出A[i…j+1]的最大子数组

Step 1 of 2

Summary of maximum-subarray problem:

Suppose we want to find a nonempty, contigous subarray of an array A[low…high] whose values have the largest sum.

To solve this problem, we divide A into 2 subarrays of as possible. By using the mid-point of array mid = high/2,

then we have two subarry A[low…mid] and A[mid+1…high].

thus, 就同上的三选一,

Step 2 of 2

Linear-time algorithm for the maximum-subarray problem.

由上述思想:

Pseudocode

FIND-MAXIMUM-SUBARRAY(A)

1    max_sum ← A[1..j]
2    sum = 0
3    i = 0
4    for j = j + 1 down to 1
5        sum = sum + A[j]
6        if sum > max_sum
7            max_sum = sum
8            i = j

Analysis:

Lines 1-3 : T(1) = &Theta;(1).

Lines 4-8 : T(j) = &Theta;(j), j > 0.

Thus, it takes a liner-time running for the problem.

4.2 矩阵乘法的Strassen算法

若 A = ( a i j ) 和 B = ( b i j ) 是 n × n 的 方 阵 , 则 对 i , j = 1 , 2 , . . . , n , 定 义 乘 积 C = A ⋅ B 中 的 元 素 c i j 为 : 若A = (a_{ij})和B = (b_{ij})是n\times n的方阵,则对i, j = 1, 2, ..., n, 定义乘积C = A \cdot B中的元素c_{ij}为: A=(aij)B=(bij)n×ni,j=1,2,...,n,C=ABcij

c i j = ∑ k = 1 n a i k ⋅ b k j c_{ij} = \sum_{k=1}^{n}a_{ik}\cdot b_{kj} cij=k=1naikbkj

SQUARE-MATRIX-MULTIPLY(A, B)

n = A.rows
let C be a new n*n matrix
for i = 1 to n
    for j = 1 to n
        c_ij = 0
        for k = 1 to n
            c_ij = c_ij + a_ik * b_kj
return C

三重循环加一些常量时间,花费Θ(n3)时间

你可能觉得任何矩阵乘法都要Ω(n2)的时间,但这是错的:

我们有方法在o(n3)时间内完成。

本节我们将看到Stranssen的著名n×n矩阵相乘的递归算法。将在4.5节证明其运行时间为Θ(nlg7)。

lg7介于2.80和2.81之间,因此Strassen算法的运行时间为O(n2.81), 优于SQUARE-MATRIX-MULTIPLY过程

一个简单的分治算法

简单起见,假定要计算的三个矩阵均为n×n矩阵,其中n为2的幂。

假定将A、B和C均分解为4个n/2×n/2的子矩阵:

鉴于已知到矩阵乘法,实现递归分治算法:

( A B ) i j = ∑ k = 1 n a i k b k j (AB)_{ij} = \sum_{k= 1}^{n} a_{ik} b_{kj} (AB)ij=k=1naikbkj
<++>

SQUARE-MATRIX-MULTIPLY-RECURSIVE(A, B)

n = A.rows
let C be a new n×n matrix
if n==1
    c_11 = a_11 * b_11
else partition A, B, and C as in equations(4.9)
    C_11 = SQUARE-MATRIX-MULTIPLY-RECURSIVE(A_11, B_11) + SQUARE-MATRIX-MULTIPLY-RECURSIVE(A_12, B_21)
    C_12 = SQUARE-MATRIX-MULTIPLY-RECURSIVE(A_11, B_12) + SQUARE-MATRIX-MULTIPLY-RECURSIVE(A_21, B_21)
    C_21 = SQUARE-MATRIX-MULTIPLY-RECURSIVE(A_21, B_11) + SQUARE-MATRIX-MULTIPLY-RECURSIVE(A_22, B_21)
    C_22 = SQUARE-MATRIX-MULTIPLY-RECURSIVE(A_21, B_12) + SQUARE-MATRIX-MULTIPLY-RECURSIVE(A_22, B_22)
return C

这里省略了重要的实现细节。第五行的分解矩阵。

然而通过下标方式计算子矩阵,第五行就只需要Θ(1)的时间(虽然我们将看到是否通过复制元素来分解矩阵对总渐进运行时间并无影响)。

递归基是常数时间,每次递归完成两个n/2×n/2矩阵的乘法,花费T(n/2), 8次调用则8T(n/2)。还有6~9行的4次矩阵加法。每个矩阵包含n2/4个元素,是Θ(n2)

T ( n ) = Θ ( 1 ) + 8 T ( n 2 ) + Θ ( n 2 ) = 8 T ( n 2 ) + Θ ( n 2 ) T(n)= \Theta(1) + 8T(\frac{n}{2}) + \Theta(n^2) = 8T(\frac{n}{2}) + \Theta(n^2) T(n)=Θ(1)+8T(2n)+Θ(n2)=8T(2n)+Θ(n2)

完整的递归式:

$$
T(n)= \begin{cases}
\Theta(1) \quad \quad\quad\qquad, n = 1\
8T(\frac{n}{2}) + \Theta(n^2) \quad , n>1
\end{cases}

$$

好啦,接着我们将在4.5节看到用主方法求解上面的递归式解为T(n) = Θ(n3)。

因此简单的分治算法并不多好。

需要搞清楚为什么是8T(n/2)和Θ(n2)时间,(常数因子8即8次递归)

因此,切记,虽然渐进符号包含了常数因子,但递归符号(如T(n/2))并不包含


每次递归操作都要4次加法和8次矩阵相乘,这很不妙,减少乘法次数很关键。

Strassen方法

1969年, Volker Stranssen提出;


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l8vbUliv-1600073551228)(pic/Strassen.png)]

核心思想是:让递归树稍微不那么茂盛,即减少递归次数。

Strassen算法并不很直观,包含四个步骤:

  1. 同上将输入矩阵A、B和输出矩阵C分解为n/2 × n/2的子矩阵。采用下标计算方法,此步骤花费Θ(1)时间,与SQUARE-MATRIX-MULTIPLY-RECURSIVE相同。

  2. 创建10个n/2 × n/2的矩阵 S 1 , S 2 , . . . , S 10 S_1, S_2, ..., S_{10} S1,S2,...,S10, 每个矩阵保存步骤1中创建的两个子矩阵和或差。花费时间为Θ(n2)。

  3. 用步骤1中创建的子矩阵和步骤2中创建的10个矩阵,递归地计算7个矩阵积 P 1 , P 2 , . . . , P 7 P_1, P_2, ...,P_7 P1,P2,...,P7。每个矩阵 P i P_i Pi都是n/2 × n/2的。

  4. 通过 P i P_i Pi 矩阵的不同组合进行加减计算,计算出结果矩阵C的子矩阵$ C_{11}, C_{12}, C_{21}, C_{22}$。花费时间Θ(n2)。

2~4的细节稍好会看到,现在先建立Strassen算法的运行时间递归式。

假定一旦矩阵规模从n变为1, 就进行简单标量乘法计算。当n>1时,步骤1、2和4共花费Θ(n2)时间,步骤3要求进行7次n/2×n/2矩阵的乘法。因此得到如下描述Strassen算法运行时间T(n)的递归式:

T ( n ) = { Θ ( 1 ) , n = 1 7 T ( n 2 ) + Θ ( n 2 ) , n > 1 \color{#FF0000} T(n)= \begin{cases} \Theta(1) \qquad \qquad , n = 1\\ 7T(\frac{n}{2}) + \Theta(n^2) \qquad , n> 1 \end{cases} T(n)={Θ(1),n=17T(2n)+Θ(n2),n>1

有很多疑问,没关系,往下看


Strassen算法的细节。

步骤2中的10个矩阵:

S 1 = B 12 − B 22 S 2 = A 11 + A 12 S 3 = A 21 + A 22 S 4 = B 21 − B 11 S 5 = A 11 + A 22 . . . S_1 = B_{12} - B_{22}\\ S_2 = A_{11} + A_{12}\\ S_3 = A_{21} + A_{22}\\ S_4 = B_{21} - B_{11}\\ S_5 = A_{11} + A_{22}\\ ... S1=B12B22S2=A11+A12S3=A21+A22S4=B21B11S5=A11+A22...

十次n/2×n/2矩阵的加减法,因此花费Θ(n2)时间。

步骤3中,递归地计算7次n/2×n/2矩阵的乘法,如下所示:

P 1 = A 11 ⋅ S 1 = A 11 ⋅ B 12 − A 11 ⋅ B 22 P 2 = S 2 ⋅ B 22 = A 11 ⋅ B 22 + A 12 ⋅ B 22 P 3 = S 3 ⋅ B 11 = A 21 ⋅ B 11 + A 22 ⋅ B 11 P 4 = A 22 ⋅ S 4 = A 22 ⋅ B 21 − A 22 ⋅ B 11 P 5 = S 5 ⋅ S 6 = A 11 ⋅ B 11 + A 11 ⋅ B 22 + A 22 ⋅ B 11 + A 22 ⋅ B 22 P 6 = S 7 ⋅ S 8 = . . . P 7 = S 0 ⋅ S 10 = . . . P_1 = A_{11} \cdot S_1 = A_{11}\cdot B_{12} - A_{11}\cdot B_{22}\\ P_2 = S_2 \cdot B_{22} = A_{11}\cdot B_{22}+ A_{12}\cdot B_{22}\\ P_3 = S_3 \cdot B_{11} = A_{21} \cdot B_{11} + A_{22}\cdot B_{11}\\ P_4 = A_{22} \cdot S_4 = A_{22}\cdot B_{21} - A_{22}\cdot B_{11}\\ P_5 = S_5\cdot S_6 = A_{11}\cdot B_{11}+ A_{11}\cdot B_{22}+ A_{22}\cdot B_{11}+ A_{22}\cdot B_{22}\\ P_6 = S_7 \cdot S_8 = ...\\ P_7 = S_0 \cdot S_{10} = ... P1=A11S1=A11B12A11B22P2=S2B22=A11B22+A12B22P3=S3B11=A21B11+A22B11P4=A22S4=A22B21A22B11P5=S5S6=A11B11+A11B22+A22B11+A22B22P6=S7S8=...P7=S0S10=...

步骤4对步骤3创建的 P i P_i Pi矩阵进行加减法运算,计算出C的4个n/2×n/2的子矩阵,

首先,

C 11 = P 5 + P 4 − P 2 + P 6 C_{11}= P_5 + P_4 - P_2 + P_6 C11=P5+P4P2+P6

利用每个 P i P_i Pi的展开式展开,

→ C 11 = A 11 ⋅ B 11 + A 12 ⋅ B 21 \rightarrow \qquad C_{11}= A_{11}\cdot B_{11} + A_{12} \cdot B_{21} C11=A11B11+A12B21

同理得其他项:

C 11 = P 5 + P 4 − P 2 + P 6 C 12 = P 1 + P 2 C 21 = P 3 + P 4 C 22 = P 5 + P 1 − P 3 − P 7 C_{11} = P_5 + P_4 - P_2 + P_6\\ C_{12} = P_1 + P_2\\ C_{21} = P_3 + P_4\\ C_{22} = P_5 + P_1 - P_3 - P_7\\ C11=P5+P4P2+P6C12=P1+P2C21=P3+P4C22=P5+P1P3P7

在步骤四中共进行了8次n/2×n/2矩阵的加减法,因此花费Θ(n2)时间。

练习

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值