《算法概论》Note_第二章_分治算法

第2章 分治算法

分治法的三个步骤:
(1) 将原问题分解为一组子问题,每个子问题都与原问题类型相同,但是比原问题的规模小;(拆)
(2) 递归求解这些子问题;(解)
(3) 将子问题的求解结果恰当合并,得到原问题的解。(合)

1.乘法

高斯的注意:复数乘法 (a + b·i)(c + d·i) = ac - bd + (bc + ad)·i 中四次实数乘法可以写成三次实数乘法,因为 bc + ad = (a + b)(c + d) - ac -bd,这样可以减少一次乘法运算。
将此技巧应用到常规乘法上:
在这里插入图片描述
将 n 位数 x 和 y 分别分成两部分,即有:
在这里插入图片描述
其中乘以 2n/2 可以使用左移实现(线性时间),此时将一个 n 位乘法分解为 4 个 n/2 位乘法后算法的时间为:T(n) = 4T(n/2) + O(n);该递推式的运行时间仍为O(n2)。但当我们应用上高斯的技巧,即
在这里插入图片描述
这样我们每分解一次就变成了 3 次 k/2 位乘法和常数时间的求和,即其改进后的运行时间为:T(n) = 3T(n/2) + O(n);则算法复杂度的下界达到 O(n1.59)。
伪代码如下:
在这里插入图片描述
算法复杂度的图示:
在这里插入图片描述
实际上,对于二进制乘法,通常不需要将其递归分解使得子问题的规模达到1位,因为对于大多数处理器,16进制和32进制的乘法都是一次操作。

2.递推式

分治算法通常遵循一种通用模式,即:在解决规模为 n 的问题时,总是先递归地求解 a 个规模为 n/b 的子问题,然后在O(nd) 时间内将子问的解合并起来,其中 a 、b 、d > 0 是一些特定的整数,分治算法的运行时间从而可以通过公式 T(n) = aT( ceil[ n/b ] + O(nd ) ) 得出。有主定理:
若对于常数 a > 0、b > 1 以及 d ≥ 0,有 T(n) = a T( ceil[n/b] ) + O(nd)成立,则
在这里插入图片描述(证略.)

3.合并排序

可以使用分治策略来进行排序:将该列数分成两部分,递归地对每一部分进行排序,然后将两个有序子序列进行合并。伪代码如下:

function mergesort(a[1,...,n] )
————————————————————
Input:An array of numbers a[1,...,n]
Output:A sorted array of this array
if n>1:
	return merge(mergesort(a[1,...floor(n/2)] ),mergesort(a[florr(n/2)+1,...,n] ) )
else:
	return a

只要指定了正确的 merge 子例程,该算法的正确性不言自明。现在需要解决的是如何将两个有序数组合并为一个有序数组(即分治算法的第三步),可以采用如下思路:

function merge(x[1,...,k],y[1,...,l] )
————————————————
if k = 0: return y[1,...,l]
if l = 0: return x[1,...,k]
if x[1] ≤ y[1]: 
	return x[1]merge(x[2,...,k],y[1,...,l] )
else:
	return y[1]merge(x[1,...,k],y[2,...,l] )

该 merge 过程在每次递归调用时的工作量保持不变(merge过程要求的数组空间已提前分配好),其过程的总运行时间为 O(k+l),因此 merge 过程是线性时间的,mergesort的总运行时间满足 T(n) = 2T(n/2) + O(n) 或 O(n log n)。其实 mergesort 所有工作都在合并时完成。

4.寻找中项

有一类问题是寻找中位数或者一个序列中第几大/小的数据,即:
选择问题
输入:一列数的集合 S ;一个整数 k
输出:集合 S 中第 k 小的元素
选择问题的一个随机分治算法
可以先任意选择一个数 v ,则根据 v 可以将 S 分为三组,如:在这里插入图片描述
通过 k 和这些子集所含元素数目大小关系,然后我们可以递归地在某个子集上应用该种方法直到找到所需要的值。

  • 效率分析:
    (当寻找中项但只每次都找到最小或最大时)最坏情况: n + (n - 1) + (n - 2) + … + n/2 = O(n2)
    为区分 v 的质量好坏,我们规定:如果 v 落在它所在数组中的四分之一位置和四分之三位置之间,我们就称选中的 v 是好的。这时候我们选中质量好的 v 的概率有 50%,那么在得到一个好的 v 之前我们会得到几个不好的 v 呢?我们有引理:在掷硬币游戏中,在得到一次正面之前,平均需要掷一个匀质硬币2次。根据上述定理,我们可以得到,我们在平均 2 次划分操作之后,待搜索数组的大小将最多缩减至原始大小的四分之三。令 T(n) 表示以大小为 n 的数组为输入的算法的期望时间,我们可以得到:T(n) ≤ T(3n/4) + O(n)。
    不等式两端取期望,根据和的期望等于期望的和,即可以得到该递推式。由该递推式可以得到 T(n) = O(n),即我们平均可以在线性操作内返回正确的解。
4.矩阵乘法

一般的矩阵乘法有:
在这里插入图片描述
时间复杂度为 O(n2);
在使用分治思想后,可以使用矩阵分块:
在这里插入图片描述
但此时其时间复杂度为: T(n) = 8T(n/2) + O(n2),仍为O(n3)。
而 strassen 则发现了一种改进的算法:
在这里插入图片描述
改进后该算法的时间复杂度满足:T(n) = 7T(n/2) + O(n2)。通过主定理对该式进行优化,得到最终时间为O(nlog27) ≈ O(n2.81)。

5.快速Fourier变换

分治思想除了上述的整数相乘和矩阵相乘的领域,还可以应用到多项式相乘的领域。
1. 多项式的另一种表示法:讲解多项式的快速乘法之前,我们需要了解一个多项式的性质:一个 d 次多项式被其在任意 d + 1 个不同点处的取值所唯一确定。我们可以用下列方法表示一个 d 次多项式 A(x) = a0 + a1x + … + adxd
(1) 系数表示法:多项式的系数a0,a1,…,ad
(2) 值表示法:A(x0)、A(x1)、…、A(xd)。
其中第二种更方便于多项式乘法操作,由于 C(x) = A(x) * B(x) 的次数为 2d,所以它被任意 2d + 1 个不同点处的取值所唯一确定。同时 C(z) = A(z) * B(z),因此值表示法下的多项式乘法是线性时间的。
而一般给出的多项式都是系数表示法,且需要输出的多项式也是系数表示法(需要通过插值获得)。首先我们考虑根据系数表示法的 A(x) 和 B(x) 求得我们需要的 A(z) 和 B(z) 各点处的值,该过程需要获得 n 个 n 次多项式的点的值,一般的算法为 O(n2),但我们可不可以减少一些时间呢?
2. 计算步骤的分治实现:我们可以选取正负的数对,如 ±x0,±x1,…,±xn/2-1,则每个 A(xi) 和 A(-xi) 所需的计算过程中会有很大一部分重复,因为 xi 的偶次幂与 -xi 的偶次幂相同。例如,A(x) 为:3 + 4x + 6x2 + 2x3 + x4 + 10x5 = (3 + 6x2 + x4) + x(4 + 2x2 + 10x4),括号中的项都是关于 x2的多项式,则有:A(x) = Ae(x2) + x A0(x2),则计算 A(xi) 和 A(-xi) 时我们可以有: A(xi) = Ae(xi2) + xiA0(xi2); A(-xi) = Ae(xi2) - xiA0(xi2)。因此,在 n 个成对的点 ±x0,…, ±xn/2-1 处,A(x) 的计算过程可以化简为在 n/2 个点处的 Ae(x) 和 A0(x) 的计算过程(每个的次数都是 A(x) 次数的一半),即:
在这里插入图片描述
按照该方法,规模为 n 的原问题被转换成规模为 n/2 的两个子问题,另外还需要加上一些线性时间的算术操作。利用递归,我们有:T(n) = 2T(n/2) + O(n),上式经化简,可以得到算法的运行时间为 O(n log n)。
但仍存在的一个问题是,在第一层分解时可以计算正负数对,但在下一层,我们需要计算 n/2 个计算点 x02, x02,…, xn/2-12,要求它们本身可以表示成正负数对,此时我们需要引入复数。(后续略) 而 FFT 算法就是从此而来。在这里插入图片描述
3. 插值:现在我们已经根据 FFT 算法通过多项式的系数表示法获得了它的值表示法,然后我们可以在对应点处相乘 C(z) = A(z) · B(z),如此我们得到相乘后多项式 C(z) 的值表示法,那么接下来需要用插值获得多项式的系数表示。
在这里插入图片描述
我们已经有
在这里插入图片描述
实际上我们也有
在这里插入图片描述
即我们可以通过傅里叶逆变换求得系数表示法的多项式。
多项式操作的矩阵表示
重新审视上述由多项式的系数表示法求其值表示法的过程,我们可以将两种表示法的系数和值分别写成两个向量,则上述变换可以写成(其中在中间的矩阵为Vandermonde矩阵,记为M):
在这里插入图片描述
简而言之,我们可以认为:计算过程通过乘以 M 来实现,而插值过程通过乘以 M-1来实现。 Vandermonde矩阵还有一个性质就是其逆矩阵可以在 O(n2)时间内求取,而一般矩阵需要 O(n3)。
插值步骤的解决
在线性代数术语中,FFT 将一个任意 n 维向量——之前称为多项式的系数表示,与一个 n * n 的矩阵相乘:在这里插入图片描述
其中 w 是单位元的一个 n 次复根,而 n 是2的幂。乘以 Mn(w) 的操作将第 k 个坐标轴映射到 M 的第 k 列。 M 的每两列是正交的,所以可以认为是另一个坐标系的坐标轴,我们称之为 Fourier 基。FFT 即是从原坐标轴到 FFT 后空间的一次变换。我们有:
在这里插入图片描述
矩阵 M 的列向量彼此正交。
4. 快速 Fourier 变换的细节:
确定性 FFT 算法
FFT 以一个向量 a = (a0,…,an-1) 和一个复数 w 为输入,其中 w 的幂 1,w,w2,…,wn-1 是单位元的 n 次复根。将分治思想应用入该计算中:
在这里插入图片描述
接下来,我们利用 wn/2 = -1 和 wn = 1 对矩阵下半部分的元素进行了简化,矩阵左上角 n/2 * n/2 的子矩阵为 Mn/2(w2),左下角类似;矩阵右侧(由于比 2k 多了一个 1 )是相对于左侧分别乘了 wj 和 w-j 的结果。因此最终的乘积是:
在这里插入图片描述
由此,规模为 n 的相乘变成了规模为 n/2 的子问题。其运行时间为 T(n) = 2T(n/2) + O(n) = O(n log n)。
快速 Fourier 变换的内部机制
从以下伪代码可以理解 FFT 的分治思路:

function FFT(a,w)
——————————————
Input: An array a = (a0,a1,...,a(n-1) ),for n a power of 2
		A primitive nth root of unity, w
Output: Mn(w) · a

if(w = 1) return a
(s0,s1,...,s(n/2-1)  ) = FFT( (a0,a2,...,a(n-2) ), w^2 )
(s0`,s1`,...,s(n/2-1)` ) = FFT( (a1,a3,...,a(n-1) ), w^2 )
for j=0 to n/2-1:
	rj = sj + w^j · sj`;
	r(j+n/2) = sj - w^j · sj`
return (r0,r1,...r(n-1) )

也可以图示为:
在这里插入图片描述
我们用边代表电线,上面标记着从左到右的复数,权重 j 代表“对这条电线上的复数乘以wj”,当从左边发出的两条电线交汇的时候,意味着各自上面的复数进行相加(右边的漏斗形即蝶形运算)。
当 FFT 电路一共含有 n = 8 个输入时,我们将它完全拆开,得到如下图所示情况:
在这里插入图片描述
注意以下两点:
(1) 对于 n 个输入,共有 log2n 个层次,每个层次有 n 个节点,共有 n log n 次操作;
(2) 输入按照以下特定顺序排序:0,4,2,6,1,5,3,7。(证明略)
">本章主内容有:分治法一般步骤、在数字乘法中的应用、在矩阵乘法中的应用、在多项式乘法中的应用(快速 Fourier 变换) ,以及两个有关于排序的 合并排序和寻找中项。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值