许多有用的算法在结构上是递归的:为了解决一个给定的问题,算法一次或多次递归地调用其自身以解决紧密相关的若干子问题。这些算法典型地遵守分治法(Divide and Conquer)的思想:将原问题分解成几个规模较小但类似于原问题的子问题,递归地求解这些子问题,然后再合并这些子问题的解来建立原问题的解。
分治模式在每层递归时都有三个步骤:
朴素解法需要 Θ(n) 的时间复杂度,即直接计算 n⏞x⋅x⋅⋅…⋅x。
1 | /** |
分治法的解法则可以达到 Θ(lgn) 的时间复杂度。
xn={xn/2⋅xn/2,如果n为偶数x(n+1)/2⋅x(n−1)/2,如果n为奇数
1 | /** |
斐波那契数列的定义:
Fn={0,如果n为01,如果n为1Fn−1+Fn−2,如果n≥2
朴素解法(直接递归求解)需要指数级[1]的时间(准确点讲是 Ω(ϕn),其中 ϕ 是黄金分割比 1+√52)
1 | /** |
朴素解法在计算斐波那契数列的时候做了很多不必要的重复计算。使用动态规划、自底向上递归求解的策略可以将Fibonacci数列的计算规模降低到线性级别,即 O(n) 的时间复杂度。主要的思想基于这点:计算新的值时,用到前两个数的结果。可以用借助一维数组来实现。
1 | /** |
(Fn+2Fn+1)=(1110)⋅(Fn+1Fn)
即
(Fn+1FnFnFn−1)=(1110)n
显然对斐波那契数列的求解变成了求矩阵 (1110) 的 n 次幂,用到上面求乘方的算法可以将时间复杂度将为 O(lgn) 。
1 | TODO |
定义:若 A=(aij), B=(bij) 是 n×n的方阵,则对i,j=1,2,…n,定义乘积 C=A⋅B中的元素 cij 为: Cij=n∑k=1aik⋅bkj
1 |
|
通常的做法是将矩阵进行分块相乘,如下图所示:
C⏞(C0C1C2C3)=A⏞(A0A1A2A3)×B⏞(B0B1B2B3)
即
Ai,j,Bi,j,Ci,j∈F2n−1×2n−1
于是
C1,1=A1,1B1,1+A1,2B2,1
C1,2=A1,1B1,2+A1,2B2,2
C2,1=A2,1B1,1+A2,2B2,1
C2,2=A2,1B1,2+A2,2B2,2
分块相乘总共用了8次乘法,因而需要 Θ(nlog28) 即 Θ(n3) 的时间复杂度。
引入新矩阵
M1:=(A1,1+A2,2)(B1,1+B2,2) M2:=(A2,1+A2,2)B1,1 M3:=A1,1(B1,2−B2,2) M4:=A2,2(B2,1−B1,1) M5:=(A1,1+A1,2)B2,2 M6:=(A2,1−A1,1)(B1,1+B1,2) M7:=(A1,2−A2,2)(B2,1+B2,2)
可得:
C1,1=M1+M4−M5+M7 C1,2=M3+M5 C2,1=M2+M4 C2,2=M1−M2+M3+M6
在求解M1,M2,M3,M4,M5,M6,M7时需要使用7次矩阵乘法,其他都是矩阵加法和减法。因此时间复杂度下降为 O(nlog27)=O(n2.807) 。有得必有失,Strassen演算法的数值稳定性较差。
“Exponential time is bad; polynomial time is good.”-- Erik Demaine ↩︎
Related Issues not found
Please contact @wzpan to initialize the comment