递归:你打开面前这扇门,看到屋里面还有一扇门。你走过去,发现手中的钥匙还可以打开它,你推开门,发现里面还有一扇门,你继续打开它。若干次之后,你打开面前的门后,发现只有一间屋子,没有门了。然后,你开始原路返回,每走回一间屋子,你数一次,走到入口的时候,你可以回答出你到底用这你把钥匙打开了几扇门。
循环:你打开面前这扇门,看到屋里面还有一扇门。你走过去,发现手中的钥匙还可以打开它,你推开门,发现里面还有一扇门(若前面两扇门都一样,那么这扇门和前两扇门也一样;如果第二扇门比第一扇门小,那么这扇门也比第二扇门小,你继续打开这扇门,一直这样继续下去直到打开所有的门。但是,入口处的人始终等不到你回去告诉他答案。
分治法的设计思想是:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。
分治策略是:对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。这种算法设计策略叫做分治法。
如果原问题可分割成k个子问题,1 分治法所能解决的问题一般具有以下几个特征: 第一条特征是绝大多数问题都可以满足的,因为问题的计算复杂性一般是随着问题规模的增加而增加; 第二条特征是应用分治法的前提它也是大多数问题可以满足的,此特征反映了递归思想的应用;、 第三条特征是关键,能否利用分治法完全取决于问题是否具有第三条特征,如果具备了第一条和第二条特征,而不具备第三条特征,则可以考虑用贪心法或动态规划法。 第四条特征涉及到分治法的效率,如果各子问题是不独立的则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然可用分治法,但一般用动态规划法较好。 分治法在每一层递归上都有三个步骤: 它的一般的算法设计模式如下: 1、迭代法 不断用递推方程的右部替换左部,下面以汉诺塔为例进行求解。 有时候直接迭代可能不太方便,可以使用换元迭代。下面以二分归并排序迭代方程为例进行求解。 2、差消法 差消法一般应用在递归方程右边不仅仅只依赖于当前项的前一项,而是前很多项,这种递归方程直接用迭代法很麻烦。属于高阶递归方程,因此要先把高阶递归方程进行差消,再进行迭代。以快速排序的递归方程为例。 3、递归树 建立递归树,每次迭代将函数项作为儿子,非函数项作为根的值。以二分归并排序递归方程为例。 4、主定理 下面举一个例子: 一个分治法将规模为n的问题分成k个规模为n/m的子问题去解。设分解阀值n0=1,且adhoc解规模为1的问题耗费1个单位时间。再设将原问题分解为k个子问题以及用merge将k个子问题的解合并为原问题的解需用f(n)个单位时间。用T(n)表示该分治法解规模为|P|=n的问题所需的计算时间,则有: T(n)= k T(n/m)+f(n) 1.概念 2.基本思想 输入: 一系列的无序元素(eg.数字)组成的输入数组A。 经过: 分两步。第一步、先将整个问题递归分解成一个个只包含一个元素的子问题/子数组(eg,刚开始问题是{2,1,5,6,4,10},直接分成2,1,5,6,4,10即可) 第二步、一步步将分解出来的子数组合并成一个排好序的数列,向两两对比每个自数组(只有1个元素),按照大小顺序合并成一个子数组(有2个元素);再 两两对比现在的子数组,按照大小顺序合并成一个新的子数组…依此类推,知道最后两个子数组进行对比,里面元素按照大小顺序合并成最终的有序序列。 输出: 输出数组B,里面包含的元素都是A中的但是已经按照要求排好了顺序。 当数组元素个数为奇数时: 2.反复合并子列表以产生新的排序子列表,直到只剩一个子列表。 3.2.划分的方式 自顶向下的实现是使用递归的原理。它从书的顶端开始,然后向下操作,每次操作都问同样的问题(我需要做什么来排序这个数组?),并且回答它(分成两个子数组,进行递归调用,合并结果),知道我们到达树的底部。 自底向上 自底向上的实现则不需要递归。它直接从树的底部开始,然后通过遍历这些片段再将它们合并起来。 其实,任何的合并排序都可以被可视化为树上的操作。树中的叶子代表数组中的各个元素。树的每个内部节点对应于将两个较小的数组合并成一个更大数组。 非递归版(之后有空再补) 算法种类:分治算法 数据结构:数组 最优时间复杂度:O(nlog(n)) 最坏时间复杂度:O(nlog(n)) 平均时间复杂度:O(nlog(n)) 最坏空间复杂度:共O(n),辅助O(n);当使用linked list,辅助空间为O(1) 稳定性:稳定 复杂性:较复杂2.适用情况
1) 该问题的规模缩小到一定的程度就可以容易地解决
2) 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。
3) 利用该问题分解出的子问题的解可以合并为该问题的解;
4) 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。
2.基本步骤
step1 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;
step2 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题
step3 合并:将各个子问题的解合并为原问题的解。
Divide-and-Conquer(P)
1. if |P|≤n0
2. then return(ADHOC(P))
3. 将P分解为较小的子问题 P1 ,P2 ,...,Pk
4. for i←1 to k
5. do yi ← Divide-and-Conquer(Pi) △ 递归解决Pi
6. T ← MERGE(y1,y2,...,yk) △ 合并子问题
7. return(T)
其中|P|表示问题P的规模;n0为一阈值,表示当问题P的规模不超过n0时,问题已容易直接解出,不必再继续分解。ADHOC(P)是该分治法中的基本子算法,用于直接解小规模的问题P。因此,当P的规模不超过n0时直接用算法ADHOC(P)求解。算法MERGE(y1,y2,...,yk)是该分治法中的合并子算法,用于将P的子问题P1 ,P2 ,...,Pk的相应的解y1,y2,...,yk合并为P的解。
3.递推方程的求解方法
在这里插入图片描述4.算法复杂度分析
三、排序问题
1.合并排序
归并排序(Merge Sort)是一种高效的、通用的、基于比较的分治排序算法。大多数实现都产生了稳定的排序,意味着实现保留了排序输出中想等元素的输入顺序。归并排序是一种分治算法,由冯.诺伊曼于1945年发明。
将待排序元素分成大小大致相同的两个相同子集合,分别对两个子集合进行排序,最终将排好序的子集合合并成要求的排好序的集合。
当数组元素个数为偶数时:
3.工作原理
3.1.工作流程
1.将未排序的列表划分成为n个子列表,每个子列表包含一个元素(一个元素的列表被认为是排序的)。
自顶向下
4.代码实现
递归版#include
5.算法分析
分类:排序算法2.快速排序
四、经典递归分治问题
1.平面最接近点对问题
2.棋盘覆盖问题
3.选择问题
4.循环赛日程表
总结