COMP9101 (Algorithm) Week 1-2 分治法(divide and conquer)

引题 —— 找假币

问题描述:现有27枚硬币,其中有一枚是假币,假币的质量小于其他真币,如何用一个天平在三次称量内找出假币?

解决方案:
【核心思想】:分治法
将硬币平均分成三份,任取2份进行称量,如果这两份硬币重量不相等,则假币在较轻的那一堆中,如果这两份硬币重量相等,则假币在未称量的那一堆中。用这种方法可以将搜索假币的范围缩小到硬币总数量的 13\dfrac{1}{3}. 那么我们的搜索范围就可以从27枚硬币缩小到9枚硬币,再用同样的方法缩小到3枚硬币,最终找出假币。

1 计数逆序

问题描述:我们如何就2个人电影品味的相似性进行定量描述?如果我们已知 AABB 两人心目中对n部电影的排序,一种常见的描述方法就是对两人心中同样两部电影排序不一致的情况进行计数。而这个问题可以转化为一个列表的逆序计数问题。
让我们以 BB 心中的电影排序为基准

BB

1 2 3 4 5 6 7 8 9 10 11 12

AA 心中的电影排序如下:

AA

1 11 9 12 7 10 3 4 6 8 2 5

a1=1a_1 = 1 表示B心中排名第 1 的电影在 AA 心中排名也是第 1
a2=11a_2 = 11 表示B心中排名第 11 位的电影在 AA 心中排名第 2
那么问题就可以转化为列表AA 中满足 i<ji < j 并且 ai>aja_i > a_j 的元组 (i,j)(i,j) 的数量。

解决方案 1 :(常规解法)
遍历所有的元组 (i,j)(i,j) 并判断 ai  aja_i \ 和\ a_j 的大小,显然,该算法的复杂度为 Θ(n2)\Theta(n^2)

解决方案 2 :(分治法)
将列表 AA 两等分,A1=A[1...n2];  A2=A[(n2+1)...n]A_1 = A[1...\dfrac{n}{2}];\ \ A_2 = A[(\dfrac{n}{2}+1)...n]

那么, AA 中逆序的数量 =A1  +A2 +A1 A2 =A_1 \ 中逆序对的数量 \ + A_2 中逆序对的数量\ + A_1\ 和 A_2\ 中逆序对的数量

A1 :  i<jA_1 \ 中逆序的数量:\ \ i < j 并且 ai>aj     i,j [1,n2]a_i > a_j\ \ \ \ \ i,j\ \in [1,\dfrac{n}{2}]

A2 :  i<jA_2 \ 中逆序的数量:\ \ i < j 并且 ai>aj     i,j [n2+1,n]a_i > a_j\ \ \ \ \ i,j\ \in [\dfrac{n}{2}+1,n]

A1A2 :   ai>aj     aiA1,aj A2A_1 和 A_2 \ 中逆序的数量:\ \ \ a_i > a_j\ \ \ \ \ a_i \in A_1,a_j\ \in A_2

该算法与归并排序算法本质上是相同的。故该算法的算法复杂度也是O(nlogn)O(nlogn)

2 大数乘法

引题:我们如何计算两个数的加法?
COMP9101 (Algorithm) Week 1-2 分治法(divide and conquer)
对于两个 n 位数相加,需要进行 N 次3个数字的加法运算。所以整个算法的时间复杂度为 O(n)O(n)。那如果是 2 个大数相乘呢?

问题描述:如何计算两个大数的乘法

解决方案 1 :(常规解法)
COMP9101 (Algorithm) Week 1-2 分治法(divide and conquer)
首先把第二个数的每一位分别乘以第一个数,得到n个"部分积",然后把这n个部分积相加,计算每个“部分积”需要用 O(n)O(n) 时间,所以计算 n 个 “部分积” 需要 O(n2)O(n^2) 时间。 把这 n 个“部分积”相加也需要O(n2)O(n^2) 时间。所以整个算法的时间复杂度为O(n2)O(n^2) 。那么有没有一种算法可以不用求这些“部分积”呢?

解决方案 2 :(分治法)
把两个数分解成部分和的形式。假设ABA,B是 n 位二进制的形式(实际上没有影响),则
A=A12n/2+A0A = A_12^{n/2} + A_0

B=B12n/2+B0B = B_12^{n/2} + B_0
那么
AB=A1B12n+(A1B0+B1A0)2n/2+A0B0AB = A_1B_1·2^n + (A_1B_0 + B_1A_0)2^{n/2} + A_0B_0
具体算法如下:

FunctionMultiA,B):Function Multi(A, B):
ifA=B=1:returnABelse:A0A1B0B1XMulti(A0B0)YMulti(A0B1)ZMulti(A1B0)WMulti(A1B1) if |A| = |B| = 1: return AB \\ else: 赋值A0,A1,B0,B1\\ X ← Multi(A0,B0)\\ Y ← Multi(A0,B1)\\ Z ← Multi(A1,B0)\\ W ← Multi(A1,B1)
return W2n+(Y+Z)2n/2+XW2^n + (Y+Z)2^{n/2} + X

也就是说,我们把 2 个 n 位数的乘法运算转化为4个 n2{\dfrac{n}{2}} 位数的乘法运算,再把四个结果进行移位、相加(所需时间为O(n)O(n) )得到最终我们需要的答案。
所以整个算法的时间复杂度为
T(n)=4T(n2)+O(n)T(n) = 4T(\dfrac{n}{2} )+O(n)

T(n)O(n2)T(n)\in O(n^2)
可见,该算法并没有提升时间复杂度。我们还需要进一步减少 n2{\dfrac{n}{2}} 位数的乘法运算次数。

解决方案 2 :(Karatsuba Trick)
我们仍以相同的形式拆分ABA,B
A=A12n/2+A0A = A_12^{n/2} + A_0

B=B12n/2+B0B = B_12^{n/2} + B_0
那么
AB=A1B12n+(A1B0+B1A0)2n/2+A0B0AB = A_1B_1·2^n + (A_1B_0 + B_1A_0)2^{n/2} + A_0B_0
为了减小递归调用的次数,我们需要减小大数相乘的次数,相应的,我们可能会增加大数加法 O(n)O(n) 的次数,但这是被乐于接受的。所以我们可以用
(A1+A0)(B1+B0)A1B1A0B0(A_1+A_0)(B_1+B_0) - A_1B_1 - A_0B_0 来代替
A1B0+B1A0A_1B_0 + B_1A_0
也就是说,我们可以用3个大数乘法和2个大数加法来实现这个过程
AB=A1B12n+((A1+A0)(B1+B0)A1B1A0B0)2n/2+A0B0AB = A_1B_1·2^n + ((A_1+A_0)(B_1+B_0) - A_1B_1 - A_0B_0)2^{n/2} + A_0B_0
具体算法如下:

FunctionMultiA,B):Function Multi(A, B):
ifA=B=1:returnABelse:A0A1B0B1UA0+A1VB0+B1XMulti(A0B0)YMulti(UV)WMulti(A1B1) if |A| = |B| = 1: return AB \\ else: 赋值A0,A1,B0,B1\\ U ← A0 + A1\\ V ← B0 + B1\\ X ← Multi(A0,B0)\\ Y ← Multi(U,V)\\ W ← Multi(A1,B1)
return W2n+(YXW)2n/2+XW2^n + (Y-X-W)2^{n/2} + X

所以整个算法的时间复杂度为
T(n)=3T(n2)+O(n)T(n) = 3T(\dfrac{n}{2} )+O(n)

T(n)O(nlog23)T(n)\in O(n^{\log _{2}3})

T(n)O(n1.58)<<n2T(n)\in O(n^{1.58}) << n^2

那么还有比 O(nlog23)O(n^{\log _{2}3}) 更快的大数相乘算法吗?
(见COMP9101 Week 2 知识点总结)

3 矩阵相乘快速算法

问题描述:对两个 nnn*n 的矩阵 PQP、Q进行乘法运算,得到矩阵 RnnR_{nn},如果使用暴力方法,需要进行 nn 次乘法才可以得到 RR 中的 11 个元素,也就是说整个算法的时间复杂度为 Θ(n3)\Theta(n^3),那么如何用分治法来提高算法的复杂度呢?

解决方案 1 :(分治法)
我们把 PQP、 Q 分成4个 n2n2\dfrac {n}{2}*\dfrac {n}{2} 的矩阵
P=(abcd);  Q=(efgh)        R=(rstu) P = \begin{pmatrix} a & b \\ c & d \end{pmatrix} ;\ \ Q = \begin{pmatrix} e & f \\ g & h \end{pmatrix} \ \ \ \ \ \ \ \ R = \begin{pmatrix} r & s \\ t & u \end{pmatrix}

(abcd)(efgh)=(rstu) \begin{pmatrix} a & b \\ c & d \end{pmatrix} · \begin{pmatrix} e & f \\ g & h \end{pmatrix} = \begin{pmatrix} r & s \\ t & u \end{pmatrix}
所以我们得到:
ae+bg=r       af+bh=sce+dg=t       cf+dh=uae + bg = r\ \ \ \ \ \ \ af+bh = s\\ce+dg = t\ \ \ \ \ \ \ cf+dh = u
于是我们把两个 nnn*n 的矩阵乘法转化为 8 个 n2n2\dfrac {n}{2}*\dfrac {n}{2} 的矩阵乘法和 4 个 n2n2\dfrac {n}{2}*\dfrac {n}{2} 的 矩阵相加的形式。该算法的复杂度为
T(n)=8T(n2)+O(n2)T(n) = 8T(\dfrac{n}{2} )+O(n^2)

T(n)Θ(n3)T(n)\in \Theta(n^{3})
该算法并没有改进时间复杂度,同样的,我们可以通过某种方式来减小矩阵乘法的次数

解决方案 2 :(Strassen’s algorithm)
采用相同的矩阵分割方式:
P=(abcd);  Q=(efgh)        R=(rstu) P = \begin{pmatrix} a & b \\ c & d \end{pmatrix} ;\ \ Q = \begin{pmatrix} e & f \\ g & h \end{pmatrix} \ \ \ \ \ \ \ \ R = \begin{pmatrix} r & s \\ t & u \end{pmatrix}

A=a(fh);         B=(a+b)h;         C=(c+d)e;     D=d(ge)E=(a+d)(e+h);        F=(bd)(g+h);     H=(ac)(e+f)    A = a(f-h);\ \ \ \ \ \ \ \ \ B = (a+b)h;\ \ \ \ \ \ \ \ \ C= (c+d)e;\ \ \ \ \ D = d(g-e)\\ E = (a+d)(e+h);\ \ \ \ \ \ \ \ F = (b-d)(g+h);\ \ \ \ \ H = (a-c)(e+f)\ \ \ \

那么
            r=E+DB+f            s=A+B            t=C+D            u=E+ACH\ \ \ \ \ \ \ \ \ \ \ \ r = E +D-B+f\\ \ \ \ \ \ \ \ \ \ \ \ \ s = A+B\\ \ \ \ \ \ \ \ \ \ \ \ \ t = C+D\\ \ \ \ \ \ \ \ \ \ \ \ \ u = E+A-C-H
于是我们把两个 nnn*n 的矩阵乘法转化为 7 个 n2n2\dfrac {n}{2}*\dfrac {n}{2} 的矩阵乘法和 18 个 n2n2\dfrac {n}{2}*\dfrac {n}{2} 的 矩阵相加的形式。该算法的复杂度为
T(n)=7T(n2)+O(n2)T(n) = 7T(\dfrac{n}{2} )+O(n^2)

T(n)Θ(nlog27)T(n)O(n2.808)T(n)\in \Theta ( n^{\log _{2}7})\\T(n)\in O(n^{2.808})

分治法个人理解

分治法的本质是运用递归,每次递归调用的时候不断的减小需要解决的问题的规模,递归的深度往往是对数级,而分治法的算法复杂度可以用主定理很好的求出。有关主定理的内容在week 2 中会有详细介绍。

思考题

用由三个小方框组成的图形来填满有一个空格缺失的 2n2n2^n * 2^n 的表格。

(为什么图片总是插不进去呢!!)
那就直接看这里吧!