递推算法-五种典型的递推关系

递推算法
递推法是一种重要的数学方法,在数学的各个领域中都有广泛的运用,也是计算机用于数值计算的一个重要算法。这种算法特点是:一个问题的求解需一系列的计算,在已知条件和所求问题之间总存在着某种相互联系的关系,在计算时,如果可以找到前后过程之间的数量关系(即递推式),那么,从问题出发逐步推到已知条件,此种方法叫逆推。无论顺推还是逆推,其关键是要找到递推式。这种处理问题的方法能使复杂运算化为若干步重复的简单运算,充分发挥出计算机擅长于重复处理的特点。
  递推算法的首要问题是得到相邻的数据项间的关系(即递推关系)。递推算法避开了求通项公式的麻烦,把一个复杂的问题的求解,分解成了连续的若干步简单运算。一般说来,可以将递推算法看成是一种特殊的迭代算法。

【例1】数字三角形
如下所示为一个数字三角形。请编一个程序计算从顶到底的某处的一条路径,使该路径所经过的数字总和最大。只要求输出总和。
 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];	
	return 0;
}
#include<iostream>
using namespace std;
int a[101][101]={0};
int main()
{
	int n,i,j;
	cin>>n;
	for(i=1;i<=n;i++)
		for(j=1;j<=i;j++) cin>>a[i][j];
	for(i=2;i<=n;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];
		}
	int max=0;
	for(i=1;i<=n;i++)
		if(a[n][i]>max) max=a[n][i];
	cout<<max;
	return 0;
}

【例2】满足F1=F2=1,Fn=F(n-1)+F(n-2)的数列称为斐波那契数列(Fibonacci),它的前若干项是1,1,2,3,5,8,13,21,34……求此数 列第n项(n>=3)。
即:
   f1=1                                    (n=1)
   f2=1                                    (n=2)
   fn=f(n-1) + f(n-2 )      (n>=3)

#include<iostream>
#include<stdio.h>
using namespace std;
int main()
{
	int n,i,f1=1,f2=1,f;
	scanf("%d",&n);
	if(n<=2) cout<<1;
	else {
		for(i=3;i<=n;i++){
			f=f1+f2;
			f1=f2;
			f2=f;
		}
	}
	printf("%d",f);
	return 0;
}

有关Fibonacci数列
楼梯台阶问题
问题:楼梯有n个台阶,上楼可以一步上一阶,也可以一步上两阶。一共有多少种上楼的方法?
这是一道计数问题。在没有思路时,不妨试着找规律。n=5时,一共有8种方法:
5=1+1+1+1+1
5=2+1+1+1
5=1+2+1+1
5=1+1+2+1
5=1+1+1+2
5=2+2+1
5=2+1+2
5=1+2+2
其中有5种方法第1步走了1阶(背景灰色),3种方法第1步走了2阶,没有其他可能。
假设f(n)为n个台阶的走法总数,把n个台阶的走法分成两类。
    第1类:第1步走1阶,剩下还有n-1阶要走,有f(n-1)种方法。
    第2类:第1步走2阶,剩下还有n-2阶要走,有f(n-2)种方法。
    这样,就得到了递推式:f(n)=f(n-1)+f(n-2),不要忘记边界情况:f(1)=1,f(2)=2。
    把f(n)的前几项列出:1,2,3,5,8,……。

兔子产子问题
问题:把雌雄各一的一对新兔子放入养殖场中。每只雌兔在出生两个月以后,每月产雌雄各一的一对新兔子。试问第n个月后养殖场中共有多少对兔子。
找规律:
        第1个月:一对新兔子r1。用小写字母表示新兔子。
        第2个月:还是一对新兔子,不过已经长大,具备生育能力了,用大写字母R1表示。
        第3个月:R1生了一对新兔子r2,一共2对。
        第4个月:R1又生一对新兔子r3,一共3对。另外,r2长大了,变成R2。
        第5个月:R1和R2各生一对,记为r4和r5,共5对。此外,r3长成R3。
       第6个月:R1、R2和R3各生一对,记为r6~r8,共8对。此外,r4和r5长大。
        ……
        把这些数排列起来:1,1,2,3,5,8,……,事实上,可以直接推导出来递推关系f(n)=f(n-1)+f(n-2):第n个月的兔子由两部分组成,一部分是上个月就有的老兔子f(n-1),一部分是这个月出生的新兔子f(n-2)(第n个月时具有生育能力的兔子数就等于第n-2个月兔子总数)。根据加法原理,f(n)=f(n-1)+f(n-2)。

【例3】骨牌的铺法
有 2χn的一个长方形方格,用一个1*2的骨牌铺满方格。编写一个程序,试对给出的任意一个n(n>0), 输出铺法总数。
【算法分析】
(1)面对上述问题,如果思考方法不恰当,要想获得问题的解答是相当困难的。可以用递推方法归纳出问题解的一般规律。
(2)当n=1时,只能是一种铺法,铺法总数有示为x1=1。
(3)当n=2时:骨牌可以两个并列竖排,也可以并列横排,再无其他方法,如下左图所示,因此,铺法总数表示为x2=2;

(4)当n=3时:骨牌可以全部竖排,也可以认为在方格中已经有一个竖排骨牌,则需要在方格中排列两个横排骨牌(无重复方法),若已经在方格中排列两个横排骨牌,则必须在方格中排列一个竖排骨牌。如上右图,再无其他排列方法,因此铺法总数表示为x3=3。
由此可以看出,当n=3时的排列骨牌的方法数是n=1和n=2排列方法数的和。
(5)推出一般规律:对一般的n,要求xn可以这样来考虑,若第一个骨牌是竖排列放置,剩下有n-1个骨牌需要排列,这时排列方法数为xn-1;若第一个骨牌是横排列,整个方格至少有2个骨牌是横排列(1*2骨牌),因此剩下n-2个骨牌需要排列,这是骨牌排列方法数为xn-2。从第一骨牌排列方法考虑,只有这两种可能,所以有:
 xn=x(n-1)+x(n-2)   (n>2)
 x1=1
 x2=2
 xn=xn-1+xn-2就是问题求解的递推公式。任给n都可以从中获得解答。例如n=5,
 x3=x2+x1=3
 x4=x3+x2=5
 x5=x4+x3=8

#include<iostream>
#include<stdio.h>
using namespace std;
int main()
{
	int n,i,f1=1,f2=2,f;
	scanf("%d",&n);
	for(i=1;i<=n;i++){
		if(i==1)	cout<<1<<endl;
		if(i==2)	cout<<2<<endl;
		if(i>2){
			f=f1+f2;
			cout<<f<<endl;
			f1=f2;
			f2=f;
		}		
	}
	return 0;
}

五种典型的递推关系
Ⅰ.Fibonacci数列
在所有的递推关系中,Fibonacci数列应该是最为大家所熟悉的。在最基础的程序设计语言Logo语言中,就有很多这类的题目。而在较为复杂的Basic、Pascal、C语言中,Fibonacci数列类的题目因为解法相对容易一些,逐渐退出了竞赛的舞台。可是这不等于说Fibonacci数列没有研究价值,恰恰相反,一些此类的题目还是能给我们一定的启发的。
Fibonacci数列的代表问题是由意大利著名数学家Fibonacci于1202年提出的“兔子繁殖问题”(又称“Fibonacci问题”)。
问题的提出:有雌雄一对兔子,假定过两个月便可繁殖雌雄各一的一对小兔子。问过n个月后共有多少对兔子?
解:设满x个月共有兔子Fx对,其中当月新生的兔子数目为Nx对。第x-1个月留下的兔子数目设为Fx-1对。则:
      Fx=Nx+ Fx-1
      Nx=F(x-2 )        (即第x-2个月的所有兔子到第x个月都有繁殖能力了)
 ∴   Fx=F(x-1)+F(x-2 )    边界条件:F0=0,F1=1
由上面的递推关系可依次得到
F2=F1+F0=1,F3=F2+F1=2,F4=F3+F2=3,F5=F4+F3=5,……。
Fabonacci数列常出现在比较简单的组合计数问题中,例如以前的竞赛中出现的“骨牌覆盖”问题。在优选法中,Fibonacci数列的用处也得到了较好的体现。

Ⅱ.Hanoi塔问题
问题的提出:Hanoi塔由n个大小不同的圆盘和三根木柱a,b,c组成。开始时,这n个圆盘由大到小依次套在a柱上,如图3-11所示。      要求把a柱上n个圆盘按下述规则移到c柱上:


(1)一次只能移一个圆盘;
(2)圆盘只能在三个柱上存放;
(3)在移动过程中,不允许大盘压小盘。
问将这n个盘子从a柱移动到c柱上,总计需要移动多少个盘次?
解:设hn为n个盘子从a柱移到c柱所需移动的盘次。显然,当n=1时,只需把a 柱上的盘子直接移动到c柱就可以了,故h1=1。当n=2时,先将a柱上面的小盘子移动到b柱上去;然后将大盘子从a柱移到c 柱;最后,将b柱上的小盘子移到c柱上,共记3个盘次,故h2=3。以此类推,当a柱上有n(n>2)个盘子时,总是先借助c柱把上面的n-1个盘子移动到b柱上,然后把a柱最下面的盘子移动到c柱上;再借助a柱把b柱上的n-1个盘子移动到c柱上;总共移动h(n-1)+1+h(n-1)个盘次。
 ∴hn=2h(n-1)+1    边界条件:h1=1

Ⅲ.平面分割问题
问题的提出:设有n条封闭曲线画在平面上,而任何两条封闭曲线恰好相交于两点,且任何三条封闭曲线不相交于同一点,问这些封闭曲线把平面分割成的区域个数。
解:设an为n条封闭曲线把平面分割成的区域个数。 由图3-13可以看出:a2-a1=2;a3-a2=4;a4-a3=6。


从这些式子中可以看出an-a(n-1)=2(n-1)。当然,上面的式子只是我们通过观察4幅图后得出的结论,它的正确性尚不能保证。下面不妨让我们来试着证明一下。当平面上已有n-1条曲线将平面分割成a(n-1)个区域后,第n-1条曲线每与曲线相交一次,就会增加一个区域,因为平面上已有了n-1条封闭曲线,且第n条曲线与已有的每一条闭曲线恰好相交于两点,且不会与任两条曲线交于同一点,故平面上一共增加2(n-1)个区域,加上已有的a(n-1)个区域,一共有a(n-1)+2(n-1)个区域。所以本题的递推关系是an=a(n-1)+2(n-1),边界条件是a0=1。

Ⅳ.Catalan数(卡塔兰数)
Catalan数首先是由Euler在精确计算对凸n边形的不同的对角三角形剖分的个数问题时得到的,它经常出现在组合计数问题中。
问题的提出:在一个凸n边形中,通过不相交于n边形内部的对角线,把n边形拆分成若干三角形,不同的拆分数目用hn表示,hn即为Catalan数。例如五边形有如下五种拆分方案(图3-14),故h5=5。求对于一个任意的凸n边形相应的hn。


Catalan数是比较复杂的递推关系,尤其在竞赛的时候,选手很难在较短的时间里建立起正确的递推关系。当然,Catalan数类的问题也可以用搜索的方法来完成,但是,搜索的方法与利用递推关系的方法比较起来,不仅效率低,编程复杂度也陡然提高。

Ⅴ.第二类Stirling数(斯特林数)
在五类典型的递推关系中,第二类Stirling是最不为大家所熟悉的。也正因为如此,我们有必要先解释一下什么是第二类Strling数。

【定义2】n个有区别的球放到m个相同的盒子中,要求无一空盒,其不同的方案数用S(n,m)表示,称为第二类Stirling数。
下面就让我们根据定义来推导带两个参数的递推关系——第二类Stirling数。
 解:设有n个不同的球,分别用b1,b2,……bn表示。从中取出一个球bn,bn的放法有以下两种:
①bn独自占一个盒子;那么剩下的球只能放在m-1个盒子中,方案数为S2(n-1,m-1);
②bn与别的球共占一个盒子;那么可以事先将b1,b2,……bn-1这n-1个球放入m个盒子中,然后再将球bn可以放入其中一个盒子中,方案数为mS2(n-1,m)。
       综合以上两种情况,可以得出第二类Stirling数定理:
【定理】S2(n,m)=mS2(n-1,m)+S2(n-1,m-1)   (n>1,m1)      边界条件可以由定义2推导出:
S2(n,0)=0;S2(n,1)=1;S2(n,n)=1;S2(n,k)=0(k>n)。
第二类Stirling数在竞赛中较少出现,但在竞赛中也有一些题目与其类似,甚至更为复杂。

小结:通过上面对五种典型的递推关系建立过程的探讨,可知对待递推类的题目,要具体情况具体分析,通过找到某状态与其前面状态的联系,建立相应的递推关系。
【例7】(1998合肥市竞赛复试第二题)同一平面内的n(n500)条直线,已知有p(p>=2)条直线相交于同一点,则这n条直线最多能将平面分割成多少个不同的区域?
解:这道题目与第一部分中的平面分割问题十分相似,不同之处就在于线条的曲直以及是否存在共点线条。由于共点直线的特殊性,我们决定先考虑p条相交于一点的直线,然后再考虑剩下的n-p条直线。首先可以直接求出p条相交于一点的直线将平面划分成的区域数为2p个,然后在平面上已经有k(k>=p)条直线的基础上,加上一条直线,最多可以与k条直线相交,而每次相交都会增加一个区域,与最后一条直线相交后,由于直线可以无限延伸,还会再增加一个区域。所以fm=fm-1+m (m>p),边界条件在前面已经计算过了,是fp=2p。虽然这题看上去有两个参数,但是在实际做题中会发现,本题还是属于带一个参数的递推关系。

发布了47 篇原创文章 · 获赞 0 · 访问量 758

猜你喜欢

转载自blog.csdn.net/lybc2019/article/details/104080425