首页 > 其他分享 >分治法

分治法

时间:2024-10-09 11:49:16浏览次数:9  
标签:lfloor frac log 递归 bn 分治 rfloor

算法导论

这个文档是学习“算法设计与分析”课程时做的笔记,文档中包含的内容包括课堂上的一些比较重要的知识、例题以及课后作业的题解。主要的参考资料是 Introduction to algorithms-3rd(Thomas H.)(对应的中文版《算法导论第三版》),除了这本书,还有的参考资料就是 Algorithms design techniques and analysis (M.H. Alsuwaiyel)。

分治法

分治法(Divide-and-Conquer)是一种十分有用的算法思想,比如归并排序所体现出来的算法思想就是分治。

在分治的算法思想下,递归性地解决问题,在每一次递归都执行下面的三个步骤:

  1. Divide: 将当前的问题划分为若干的子问题,这些子问题都是同一个问题的更小的实例,或者说是更小规模的相同问题
  2. Conquer: 递归性地解决子问题,也就是将子问题传到下一层递归中。如果子问题的规模已经足够小了,那么就直接解决这个子问题
  3. Combine: 将子问题的解决结果合并为当前问题的解决结果

这就是分治算法的基本思想,另外,在分治思想中还涉及到两个概念:

  • recursive case: 就是当问题规模太大而无法直接解决,只能用递归解决的情况
  • base case: 就是当问题规模足够小,能够直接解决的情况

比如归并排序就是一种比较典型的分治思想的算法,把对一个序列的排序工作划分成对两个子序列的排序工作:

\[T(n) = \begin{cases} \Theta(1) & if\space n=1\\ 2T(n/2) + \Theta(n) & if\space n>1 \end{cases} \]

即,如果序列的长度为1,那么就无需排序,需要的时间复杂度为\(\Theta(1)\),而如果序列的长度大于1,那么就这个序列划分为两个子序列,并且对子序列进行排序,然后将排好序的子序列进行合并,合并两个有序的子序列需要的时间复杂度为\(\Theta(n)\)。

而上面的式子也被称为递归式

递归式求解

下面介绍如何求解递归式,也就是通过递归式计算算法整体的时间复杂度的方法:

比如说有下面这样的一个递归式:

\[T(n) = \begin{cases}d & if\space n=1\\ aT(n/b) + f(n) & if\space n>1\end{cases} \]

在这个式子中,原问题被分解为a个子问题,每个子问题的规模为原来的 1/b 倍,而将这个a个子问题的解合并成原问题的解需要的时间为f(n),而解决一个base case所需的时间为常数d。

最容易想到的方法就是对递归式进行展开:

\[\begin{align} T(n) &=aT(\frac{n}{b}) + f(n)\\ T(\frac{n}{b})&=a(\frac{n}{b^2}) + f(\frac{n}{b})\\ \cdots\\ T(n)&=a^kT(\frac{n}{b^k})+\sum_{i=0}^{k}a^if(\frac{n}{b^i}) \end{align} \]

不过这样的方法比较抽象,可能需要分很多种情况进行讨论。

Introduction to algorithm 一书中,介绍了三种求解递归的方法,

  • substitution method:猜测一个界限,然后使用数学归纳法来证明这个猜测是正确的
  • recursive-tree method:使用递归树来,递归树中的节点表示在递归的各个级别上产生的成本,然后使用边界求和的方法来求解
  • master method:可以用于计算形如\(T(n) = aT(n/b) + f(n)\)形式的递归式的边界,在这个式子中,原问题被分解为a个子问题,每个子问题的规模为原来的 1/b 倍,而将这个a个子问题的解合并成原问题的解需要的时间为f(n)

substitution method通常用于证明渐进上界和渐进下界,当然也可以用于证明紧渐进界。

这里介绍一个使用substitution method方法求解递归式的例子,递归式如下:

\[f(n) = \begin{cases} d & if\space n = 1\\ f(\lfloor n/2\rfloor)+f(\lceil n/2 \rceil) + bn & if\space n\ge 2 \end{cases} \]

对于非负常数 bd,如果 n 为2的整数幂,那么这个递归式可以简化为:

\(f(n) = 2f(n/2) + bn\)

那么我们可以假设:

\(f(n) \le cbn\log n + dn\),其中 c 为大于0的常数,并且接下来将会确定 c 的值。

这部分的内容主要摘自 Algorithms design techniques and analysis,这里介绍了一些推论,用于假设递归式的边界,但是这里由于篇幅限制,所以没进行介绍。

假设上面猜测的结果在\(n\ge 2\)时,对于\(\lfloor n/2 \rfloor\)和\(\lceil n/2 \rceil\)是成立的,那么我们将其带入原式子右边的子式子可得:

\[\begin{align} f(n) = & f(\lfloor n/2 \rfloor) + f(\lceil n/2 \rceil) + bn\\ \le & cb\lfloor n/2 \rfloor\log (\lfloor n/2 \rfloor) + d\lfloor n/2\rfloor + cb\lceil n/2 \rceil\log (\lceil n/2 \rceil) +d\lceil n/2 \rceil + bn\\ \le&cb\lfloor n/2 \rfloor\log (\lceil n/2 \rceil) + cb\lceil n/2 \rceil\log (\lceil n/2 \rceil) + dn + bn\\ =&cbn\log(\lceil n/2 \rceil) + dn + bn\\ \le&cbn\log ((n+1)/2) + dn + bn\\ =&cbn\log (n+1) - cbn + dn + bn \end{align} \]

欲使前面的假设成立,则有:

\[\begin{align} cbn\log (n+1) - cbn + dn + bn \le& cbn\log n + dn\\ cbn\log(n+1) - cbn + bn \le& cbn\log n\\ c\log (n+1) -c + 1\le& c\log n\\ c\ge& \frac{1}{1 + \log n - \log(n+1)} = \frac{1}{1 + \log\frac{n}{n+1}} \end{align} \]

当 \(n\ge 2\)时,

\(\frac{1}{1+\log\frac{n}{n+1}} \le \frac{1}{1+\log\frac{2}{3}} \lt 2.41\)

于是取c = 2.41。并且当n = 1时,\(f(n) = d\le cb\log 1 + d = d\),假设依然成立。

所以能够求解出上面递归式的上界为:\(f(n)\le 2.41 bn\log n + dn\)

现在计算递归式的渐进下界,同样,假设递归式\(f(n)\)的下界为\(cbn\log n + dn\),其中 c 为大于0的常数,后面会确定其取值。

假设上面的猜测在\(n\ge 2\)时,对于\(\lfloor n/2 \rfloor\)和\(\lceil n/2 \rceil\) 是成立的,那么带入递归式右边可得:

\[\begin{align} f(n)=&f(\lfloor n/2 \rfloor) + f(\lceil n/2 \rceil) + bn\\ \ge&cb\lfloor n/2 \rfloor\log(\lfloor n/2 \rfloor) + d\lfloor n/2 \rfloor + cb\lceil n/2 \rceil\log(\lceil n/2 \rceil) + d\lceil n/2 \rceil + bn\\ \ge&cb\lfloor n/2 \rfloor\log(\lfloor n/2 \rfloor) + d\lfloor n/2 \rfloor + cb\lceil n/2 \rceil\log(\lfloor n/2 \rfloor) + d\lceil n/2 \rceil + bn\\ =&cbn\log (\lfloor n/2 \rfloor) + dn + bn\\ \ge&cbn\log(n/4) + dn +bn\\ =&cbn\log n - 2cbn + dn + bn \end{align} \]

欲使上面的假设成立,则有:

\[\begin{align} bn - 2cbn &\ge 0\\ c&\le \frac{1}{2} \end{align} \]

于是取 \(c =\frac{1}{2}\),则当\(n\ge 2\)时,\(f(n)\ge \frac{1}{2} bn\log n + dn\) 成立,并且当\(n= 1\)时,\(f(n) = d \ge \frac{1}{2}b\log 1 + d =d\),假设依然成立。

所以能够求出上面的递归式的下界为:\(f(n)\ge \frac{1}{2}bn\log n + dn\)

综上,上述递归式的紧渐进边界为\(f(n) =\Theta(n\log n)\)

这种方法的困难在于提出一个巧妙的猜测,将这个猜测作为递归式的严格边界。然而,在多数情况下,给定递归式类似于另一个递归式,而这个递归式的解是已经提前知道了的,所以可以借助这个递归式的解,来假设当前递归式的解,上面介绍的这个例子就是这样的。

在上面的这个三个方法中,最常用并且也是最有用的方法就是主方法(master method),这个方法通常用于计算递归式的渐进边界。

主定理

主定理主要用于求解下面这种形式的递归式:

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

其中a, b是大于等于1的常数,而 f(n) 是一个渐进的正函数。

将T(n)分下面三种情况进行讨论:

  • 若 \(f(n) = O(n^{\log_b a-\epsilon})\),其中 \(\epsilon \gt 0\),则 \(T(n)=\Theta(n^{\log_b a})\)
  • 若 \(f(n)=\Theta(n^{\log_b a})\),则 \(T(n)=\Theta(n^{\log_b a}\lg n)\)
  • 若 \(f(n)=\Omega(n^{\log_b a +\epsilon})\),其中 \(\epsilon \gt 0\),并且如果 \(af(n/b) \le cf(n)\),对于大于1的常数 c 以及足够大的 n 满足,则 \(T(n)=\Theta(f(n))\)

下面介绍一些使用主定理求解递归式的例子:

# 1. \(T(n) = 9T(n/3)+n\)

则: \(n^{\log_ba} = n^{\log_3 9}=n^2\)

\(f(n)=n=O(n^{\log_39 -\epsilon})\),其中\(\epsilon =1\)

根据主定理的第一个情况,\(T(n)=\Theta(n^2)\)

# 2. \(T(n)=T(2n/3)+1\)

则: \(n^{\log_ba}=n^{\log_{3/2}1} = 1\)

\(f(n) = 1 = \Theta(1)\)

根据主定理的第二个情况,\(T(n)=\Theta(n^{\log_{3/2}1}\lg n) = \Theta(\lg n)\)

# 3. \(T(n)=3T(n/4)+n\lg n\)

则: \(n^{\log_b a} = n^{\log_43} = O(n)\)

\(f(n) = n\lg n = \Omega(n^{\log_43+\epsilon})\),其中\(\epsilon = 1 - \log_43\approx 0.2\)

所以可以应用主定理的第三种情况。

而\(af(n/b) = 3(n/4)\lg(n/4) = (3/4)n\lg(n/4) \le (3/4)n\lg n = cf(n)\),其中 \(c = 3/4\)

根据主定理的第三种情况,\(T(n) = \Theta(n\lg n)\)

Selection Problem

选择问题(Selection Problem):找到一个大小为 n 数组中的第 k 小的元素。

那么最直接的方法是,先将这个数组进行排序,然后取第 k 个元素,那么这样的方法的时间复杂度为\(\Omega(n\log n)\),这也是所有基于比较的排序算法在最坏情况下的时间复杂度。

然而事实上,找到第 k 小的元素可以有线性时间复杂度的最优解。因为一个最简单的道理是,如果对原数组进行了排序,那么我们不仅能够找到第 k 小的元素,我们也能找到第 k+1 小的元素等,所以对数组进行排序的操作存在着一些冗余的工作量。

下面介绍这个问题最优解法的思路:

- 如果数组中的元素数量小于44个(44是一个预定义的阈值,后面会解释为什么会将这个阈值),那么就直接对数组进行排序,然后找第 k 个元素

- 否则,找到这个数组比较靠近"中间"的元素记为 mm,然后通过下面的方法将数组划分为三组:

- $A_1=\{a|a \lt mm\}$
- $A_2=\{a|a=mm\}$
- $A_3=\{a|a\gt mm\}$

那么,可以分下面几种情况讨论:

\#1. 若$|A_1| \ge k$,那么数组中第 *k* 小的元素必然在集合 $A_1$中,继续在 $A_1$ 中找第 *k* 小的元素

\#2. 若$|A_1| \lt k, |A_1| + |A_2|\ge k$,那么数组中第 *k* 小的元素就在 $A_2$ 中,所以第 *k* 小的元素就是 *mm*

\#3. 若$|A_1 + A_2| \gt k$,那么数组中第 *k* 小的元素必然在 $A_2$ 中,继续在$A_2$ 中找第 $k - |A_1| - |A_2|$ 小的元素

下面将介绍如何找到数组中靠近"中间"的元素:

将这个数组中每5个元素分为一组,如果数组长度不能被5整除,那么就丢弃剩下的元素。

找到每个小组的中位数,将这些中位数放在一个集合中,这个集合记为m,那么我们要找的元素 mm 就是集合 m 的中位数。

算法伪代码如下:

selection_problem

下面通过一个例子来演示这个算法的过程,并且为了方便演示而取消了阈值判断过程:

假设数组 A = { 8, 33, 17, 51, 57, 49, 35, 11, 25, 37, 14, 3, 2, 13, 52, 12, 6, 29, 32, 54, 5, 16, 22, 23, 7 }

要找到数组A中的第13小的元素,也就是数组A的中位数。

先将A五五分组:

(8, 33, 17, 51, 57), (49, 35, 11, 25, 37), (14, 3, 2, 13, 52), (12, 6, 29, 32, 54), (5, 16, 22, 23, 7)

然后找到每个小组的中位数(注意到这里每个小组只有5个元素,所以找中位数的时间开销可以视为一个固定常数),并且放入集合中:M={ 33, 35, 13, 29, 16 }

M 排序,M={ 13, 16, 29, 33, 35 },则 mm = 29

现在对原数组 A 进行划分:

\(A_1=\{ 8, 17, 11, 25, 14, 3, 2, 13, 12, 6, 5, 16, 22, 23, 7 \}\)

\(A_2=\{ 29 \}\)

\(A_3=\{ 33, 51, 57, 49, 35, 37, 52, 32, 54 \}\)

因为\(|A_1| = 15 > 13\),所以丢弃\(A_2,A_3\),在\(A_1\) 中找第13小的元素。

令 \(A = A_1\) 并五五分组:

(8, 17, 11, 25, 14), (3, 2, 13, 12, 6), (5, 16, 22, 23, 7)

找到每个小组的中位数,并且放入集合中:M = { 14, 6, 16 }

mm =14

现在对数组 A 进行划分:

\(A_1=\{ 8, 11, 3, 2, 13, 12, 6, 5, 7 \}\)

\(A_2=\{14\}\)

\(A_3=\{ 17, 25, 16, 22, 23 \}\)

因为\(|A_1| + |A_2| = 10 < 13\),所以丢弃\(A_1, A_2\),在\(A_3\) 中找第13-10=3小的元素

显然\(A_3[3]=22\)

所以原数组第13小的元素为22。

Algorithm analysis

显然,不难验证上面算法的正确性。

现在来分析上面这个算法的时间复杂度。

想要分析出分治算法的时间复杂度,那么就需要先确定其子问题的大小,如下图:

selection_problem_illustration

上图中,所有元素每列是按照从下往上递增排序的,然后将每列按照该列中位数的大小从左到右递增排序,那么对于整个集合的中间数 mm,图中左下角(W)的元素都是小于或等于 mm 的元素,图中右上角(X)的元素都是大于或等于 mm 的元素。

而这个算法中的子问题规模是所有严格小于中间数 mm 的元素集合( A1 )大小,或者严格大于中间数 mm 的元素集合( A3 )大小。现在考虑所有小于或等于中间数 mm 的集合( A1' ),显然,这个集合至少是和集合 W 一样大的。即:

\(|A1'| \ge 3\lceil \lfloor n/5 \rfloor / 2 \rceil \ge \frac{3}{2}\lfloor n/5 \rfloor\)

那么所有严格大于中间数 mm 的元素集合就是所有小于或等于中间数 mm 的元素集合的补集,即:

\(|A3| \le n - \frac{3}{2}\lfloor n/5 \rfloor \le n - \frac{3}{2}(\frac{n-4}{5}) = n - 0.3n + 1.2 = 0.7 n + 1.2\)

同理可得:

\(|A1| \le n - \frac{3}{2}\lfloor n/5 \rfloor \le n - \frac{3}{2}(\frac{n-4}{5}) = n - 0.3n + 1.2 = 0.7 n + 1.2\)

现在可以开始计算整个算法的时间复杂度了:

首先整个集合的规模( n )是知道的;将整个集合五五分组的时间复杂度是\(\Theta(n)\);将每组的 5 个元素进行排序的时间复杂度是 \(\Theta(n)\),因为对 5 个元素排序最多只需要 7 步,可以视为一个常数开销;找到所有中位数的中位数的开销是 \(T(\lfloor n/5 \rfloor)\),使用这里的选择算法找中位数;在确定了中间数 mm 后,就可以进入子问题了,根据前面的分析,子问题的时间复杂度应该是 \(T(0.7n + 1.2)\)。即:

\(T(n) = T(\lfloor n/5 \rfloor) + T(0.7 n + 1.2) + cn\)

其中 c 为足够大的常数。

为了将 0.7n+1.2 转换成n的常数倍形式,这里假设:

\(0.7n + 1.2 \le \lfloor 0.75n \rfloor\)

当 \(n = 44\)时,\(0.7n + 1.2 \le 0.75n - 1\) 成立。

因此,算法的时间复杂度为:

\[T(n) = \begin{cases} c& n \lt 44\\ T(\lfloor n/5 \rfloor) + T(\lfloor 3n/4 \rfloor) + cn &n\ge 44 \end{cases} \]

因为 \(\frac{1}{5} + \frac{3}{4} < 1\),所以算法的时间复杂度为 \(T(n) = \Theta(n)\)。

标签:lfloor,frac,log,递归,bn,分治,rfloor
From: https://www.cnblogs.com/TheFutureIsNow/p/18453921

相关文章

  • 【模板】"动态DP"&动态树分治
    目录题目描述朴素算法矩阵刻画实现code以洛谷模板题为例介绍动态dp的一般方法。P4719【模板】"动态DP"&动态树分治-洛谷|计算机科学教育新生态(luogu.com.cn)P4751【模板】"动态DP"&动态树分治(加强版)-洛谷|计算机科学教育新生态(luogu.com.cn)题目描述给定一......
  • CDQ分治
    前置知识 归并排序(一维偏序) 推荐写法:https://www.cnblogs.com/didiao233/p/17986130第1题   归并排序 查看测评数据信息给定你一个长度为n的整数数列。请你使用归并排序对这个数列按照从小到大进行排序。并将排好序的数列按顺序输出。输入格式 输入共......
  • 洛谷题单指南-分治与倍增-P6648 [CCC2019] Triangle: The Data Structure
    原题链接:https://www.luogu.com.cn/problem/P6648题意解读:在一个n行的数字三角形中,求所有边长为k的正三角形最大值之和。解题思路:1、枚举法枚举每一个边长为k的三角形,在其中求max,然后累加,n最多3000,时间复杂度是n^4,显然超时。2、倍增和ST思想此题非常类似于RMQ问题,也就是求区......
  • [2023四校联考3]sakuya 题解(根号分治)
    题目链接。题目分析第一个操作类似哈希冲突那一道题,可以运用类似的思路开一个二维表,很容易想到两种做法:开一个二维表,表上的第\(i\)行,第\(j\)列表示序列下标在模\(i\)意义下等于\(j\)的加法标记。对于修改操作,直接暴力修改对应的那一行的值即可,查询时用线段树查询那个......
  • 99th 2024/9/4 CDQ分治小结
    概括轻新小思路用于处理点对关系题在能将点对分段处理(如下)的题目中有奇效试想,现在要处理部分点对,其范围为\(l\in[L,R],r\in[L,R]\)其位置不确定,但是我们可以考虑将其分为三个板块分别作出的总贡献即分为\(l\in[L,mid],r\in[mid+1,R]\)\(l\in[L,mid],r\in[L,mid]\)\(l......
  • CDQ分治学习笔记
    CDQ分治学习笔记k维偏序问题求满足条件的二元组个数题意描述每个元素有\(k\)个值,要求满足(以\(k=2\)为例)\(a_j\lea_i,b_j\leb_i\)的点对个数。分析这实际上就是我们熟悉的逆序对问题,回忆一下我们是怎么处理的,首先来说,当\(a,b\)其中一个的含义是下标的时候可以直......
  • 树分治
    点分治点分治适合处理大规模的树上路径信息问题。本质思想是选择一点作为分治中心,将原问题划分为几个相同的子树上的问题,进行递归解决。常见的用于统计树上有关路径的信息。假设当前选定根节点为\(rt\),则符合条件的路径必然是以下两种之一:经过\(rt\)或一段在\(rt\)上;在......
  • 揭秘合并排序:分治排序初学者指南
    归并排序由约翰·冯·诺依曼于1945年提出,主要是为了提高大型数据集的排序效率。冯·诺依曼的算法旨在使用分而治之的方法提供一致且可预测的排序过程。这种策略允许归并排序有效地处理小型和大型数据集,保证在所有情况下都能实现稳定的排序,时间复杂度为o(nlogn)。合并排序采用......
  • 洛谷题单指南-分治与倍增-P4155 [SCOI2015] 国旗计划
    原题链接:https://www.luogu.com.cn/problem/P4155题意解读:在m个点的环上,有n个区间,且各个区间之间没有包含关系,计算从每个区间开始,最少要多少个区间能覆盖环上所有m个点。解题思路:本质上是一个区间覆盖问题!1、破环成链由于题目中是一个环,对于环的问题,在区间DP中介绍过,经典处理......
  • 深入分治法:如何用简单步骤解决复杂问题?
    分治法(DivideandConquer)概述分治法是一种经典的算法设计思想,它将一个复杂问题分成多个规模较小的子问题,分别解决这些子问题,然后将子问题的解组合成原问题的解。分治法的思想非常通用,适用于许多经典算法和问题,如归并排序、快速排序、二分搜索、矩阵乘法等。分治法的基本步......