递归与递推算是ACM中比较重要的知识点,其难点主要在于对关系式的分析,当你建立起来关系式时,题目就变得轻而易举,难就难在对关系式的分析与寻找,对递推公式的建立上,在运用递推递归解决问题是,一定要仔细分析题目,其中另外重要的一点就是边界(终止)条件的寻找,此问题处理起来较为简单。在运用递推递归时,要灵活运用,有时不需要递归,只需一个for循环即可解决问题,还有就是还可以对数据进行预处理,以及运用前缀和等等,为递推递归解决问题提供了很大的方便。总之还是那句话,寻求大问题与小问题之间相互的联系。
一、递推
递推算法是一种用若干步可重复的简运算(规律)来描述复杂问题的一种方法!即一个问题的求解需一系列的计算,在已知条件和所求问题之间总存在着某种相互联系的关系;如果可以找到前后过程之间的数量关系,能使复杂运算化为若干步重复的简单运算,充分发挥出计算机擅长于重复处理的特点。递推算法的首要问题是得到相邻的数据项间的关系(即递推关系)。 把一个复杂的问题的求解,分解成了连续的若干步简单运算。l另外 递推可以分为顺推和逆推,具体的问题要求具体的分析,寻找问题的最优解!
总之一句话:就是将大的问题寻求子问题来解决!
典型例题:
数字三角形。如下所示为一个数字三角形。请编一个程序计算从顶到底的某处的一条路径,使该路径所经过的数字总和最大。只要求输出总和。
1、 一步可沿左斜线向下或右斜线向下走;
2、 三角形行数小于等于100;
3、 三角形中的数字为0,1,…,99;
测试数据通过键盘逐行输入,如上例数据应以如下所示格式输入:
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
算法分析(逆推):
此题解法有多种,从递推的思想出发,设想,当从顶层沿某条路径走到第i层向第i+1层前进时,我们的选择一定是沿其下两条可行路径中最大数字的方向前进,为此,我们可以采用倒推的手法,设a[i][j]存放从i,j 出发到达n层的最大值,则a[i][j]=max{a[i][j]+a[i+1][j],a[i][j]+a[i+1][j+1]},a[1][1] 即为所求的数字总和的最大值。
代码:
#include<iostream>
using namespace std;
int main()
{
int n,i,j,a[101][101];
cin>>n;
for (i=1;i<=n;i++)
for (j=1;j<=i;j++)
cin>>a[i][j]; //输入数字三角形的值
for (i=n-1;i>=1;i--)
for (j=1;j<=i;j++)
{
if (a[i+1][j]>=a[i+1][j+1]) a[i][j]+=a[i+1][j]; //路径选择
else a[i][j]+=a[i+1][j+1];
}
cout<<a[1][1]<<endl;
}
二、递归
程序直接或间接调用自身的编程技巧称为递归算法 (Recursion)。直接或间接调用自身的函数称为递归函数。它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。
1、递归算法的基本思想:(问题分解)
把一个不能或不好解决的大问题转化为一个或几个小问题,再把这些小问题进一步分解成更小的小问题,
最小问题可以直接解决。
2、递归的关键在于找出递归定义和递归终止条件:
递归定义:使问题向边界条件转化的规则。递归定义必须能使问题越来越简单。
递归终止条件:也就是所描述问题的最简单情况,它本身不再使用递归的定义。
3、递归算法解题通常有三个步骤:
1)分析问题、寻找递归:找出大规模问题与小规模问题的关系,这样通过递归使问题的规模逐渐变小。
2)设置边界、控制递归:找出停止条件,即算法可解的最小规模问题。
3)设计函数、确定参数:设计函数体中的操作及相关参数。
4、我认为写好一个递归算法主要需要掌握三个方面:
1 找到其中重复的逻辑。
2 需要控制编辑的条件!
3 就是要寻找恰当的结束条件,不至于使程序为死循环!
经典例题:
集合的全排列问题
关键代码:
void Perm(int list[], int k, int m)
{
//构成了一次全排列,输出结果
if(k==m)
{
for(int i=0;i<=m;i++)
cout<<list[i]<<" ";
cout<<endl;
}
else
//在数组list中,产生从元素k~m的全排列
for(int j=k;j<=m;j++)
{
swap(list[k],list[j]);
Perm(list,k+1,m);
swap(list[k],list[j]);
}
}
心得:
递推递归就是要找关系式,递推式,寻找大问题与小问题的关系!