递归与分治策略
1.递归算法
1.1什么是递归:
程序直接或间接地调用自身的编程技巧称为递归算法。
一个过程或函数在其定义或说明中又直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需要少量的程序就可以描述出解题过程所需要的多次重复计算,大大减少了程序的代码量。
递归的能力在于用有限的语句来定义对象的无限的集合。
用递归思想写出的程序往往是十分简洁易懂。
一般来说,递归需要有边界条件,递归前进段和递归返回段。当边界条件不满足时,递归前进,当边界条件满足时,递归返回。注意:在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口,否则将无限进行下去(死锁)。
递归算法一般用于解决以下三类问题:
- 1 数据的定义是按照递归定义的,例如Fibonacci函数。
- 2 问题解决按递归算法实现,例如回溯算法。
- 3 数据的结构形式是按递归定义的,例如树的遍历,图的搜索。
1.2递归的缺点:
递归算法解题的运行效率较低。在递归调用过程中,系统为每一层的返回点,局部变量等开辟了堆栈来存储。递归次数过多容易造成堆栈溢出等。
递归算法是解决问题的一种最自然且合乎逻辑的方式,利用递归算法不需要花费太多的精力就能够解决问题,但是程序的执行效率可能会变差。在这种情况下,通常把递归算法转换为非递归算法,例如模拟或者递推。
递归的基本使用:Fibonacci函数,集合的全排列问题,整数划分问题。
2.分治策略
2.1什么是分治策略:
分治策略是对于一个规模为n的问题,若该问题可以容易的解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同。递归的解这些子问题,然后将各子问题的解合并得到原问题的解。
2.2 分治法的基本步骤:
分治法在每一层递归上都有以下三个步骤:
- 1 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题。
- 2 解决:若子问题规模较小而容易被解决则直接解,否则递归地解决各个子问题。
- 3 合并:将各个子问题的解合并为原问题的解。
人们从大量实践中发现,在用分治法设计算法时,最好使子问题的规模大致相同。
2.3分治法的适用条件
分治法所能解决的问题一般具有以下几个特征:
(1)该问题的规模缩小到一定的程度就可以容易的解决。
(2)该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质。
(3)利用该问题分解出的子问题的解可以合并为该问题的解。
(4)该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。
基本应用:二分搜索技术,循环赛日程表,棋盘覆盖问题,选择问题,输油管道问题,半数集问题,整数因子分解。
3.基本运用
3.1集合的全排列问题的递归算法
【算法讲解】
设R={r1,r2,…,rn}是要进行排列的n个元素,Ri=R-{ri}。
集合X中元素的全排列记为perm(X)。
(ri)perm(X)表示在全排列perm(X)的每一个排列前加上前缀得到的排列。
R的全排列可归纳定义如下:
当n=1时,perm(R)=(r),其中r是集合R中唯一的元素;
当n>1时,perm(R)由(r1)perm(R1),(r2)perm(R2),…,(rn)perm(Rn)构成。
实现思想:将整组数中的所有的数分别与第一个数交换,这样就总是在处理后n-1个数的全排列。
核心代码:
template<class Type>
//产生k-m的全排列,作为前k-1个元素的后缀
//>>是输入流符号 <<是输出流符号
void Perm(Type list[], int k, int m )
{ //产生[list[k:m]的所有排列
//构成一次全排列,输出结果
if(k==m)
{ //只剩下一个元素
for (int i=0;i<=m;i++)
cout<<list[i];
cout<<endl;
}
else //还有多个元素待排列,递归产生排列
//在数组list中,产生元素k-m的全排列
for (int i=k; i<=m; i++)
{
swap(list[k],list[i]);
Perm(list,k+1,m);
swap(list[k],list[i]);
}
}
全搜索图:
3.2 整数划分问题
画图分析: