算法--递推策略

本文地址:http://www.cnblogs.com/archimedes/p/4265019.html,转载请注明源地址。

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

  递推算法的首要问题是得到相邻的数据项间的关系(即递推关系)。递推算法避开了求通项公式的麻烦,把一个复杂的问题的求解,分解成了连续的若干步简单运算。一般说来,可以将递推算法看成是一种特殊的迭代算法。

引例:Fibonacci数列

Fibonacci数列的代表问题是由意大利著名数学家Fibonacci于1202年提出的“兔子繁殖问题”(又称“Fibonacci问题”)。

问题:

一个数列的第0项为0,第1项为1,以后每一项都是前两项的和,这个数列就是著名的裴波那契数列,求裴波那契数列的第N项。

算法:

f[0]:=0; f[1]:=1;

for i:=2 to n do f[i]:=f[i–1]+f[i–2];

总结:

从这个问题可以看出,在计算裴波那契数列的每一项目时,都可以由前两项推出。这样,相邻两项之间的变化有一定的规律性,我们可以将这种规律归纳成如下简捷的递推关系式:Fn=g(Fn-1),这就在数的序列中,建立起后项和前项之间的关系。然后从初始条件(或是最终结果)入手,按递推关系式递推,直至求出最终结果(或初始值)。很多问题就是这样逐步求解的。

对一个试题,我们要是能找到后一项与前一项的关系并清楚其起始条件(或最终结果),问题就可以递推了,接下来便是让计算机一步步了。让高速的计算机从事这种重复运算,真正起到“物尽其用”的效果。

递推概念

给定一个数的序列H0,H1,…,Hn,…若存在整数n0,使当n>n0时,可以用等号(或大于号、小于号)将Hn与其前面的某些项Hi(0<i<n)联系起来,这样的式子就叫做递推关系。

  • 如何建立递推关系
  • 递推关系有何性质
  • 如何求解递推关系

解决递推问题的一般步骤

  • 建立递推关系式
  • 确定边界条件
  • 递推求解

递推的两种形式

顺推法和倒推法

递推的应用分类

  • 一般递推问题
  • 组合计数类问题
  • 一类博弈问题的求解
  • 动态规划问题的递推关系

(一)递推的应用(一般递推问题)

例题2:输出杨辉三角的前N行(HDOJ2032

Problem Description

还记得中学时候学过的杨辉三角吗?具体的定义这里不再描述,你可以参考以下的图形:
1
1
1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1

Input

输入数据包含多个测试实例,每个测试实例的输入只包含一个正整数n(1<=n<=30),表示将要输出的杨辉三角的层数。

Output

对应于每一个输入,请输出相应层数的杨辉三角,每一层的整数之间用一个空格隔开,每一个杨辉三角后面加一个空行。

Sample Input

2 3

Sample Output

1

1 1

1

1 1

1 2 1

代码如下:

#include <iostream>
using namespace std;
int a[31][31];
int main( )
{
    int i,j,n;
    a[0][0]=a[1][0]=a[1][1]=1;
    for(i=2; i<31; i++) {
        for(j=0; j<=i; j++) {
            if(j==0 || i==j) {
                a[i][j]=1;
            } else {
                a[i][j]=a[i-1][j-1]+a[i-1][j];
            }
        }
    }
    while(cin>>n) {
        for(i=0; i<n; i++) {
            for(j=0; j<=i; j++) {
                if(j!=0) cout<<" ";
                cout<<a[i][j];
            }
            cout<<endl;
        }
        cout<<endl;
    }
    return 0;
}

例题3 : Hanoi塔问题 .

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

(1)一次只能移一个圆盘;

(2)圆盘只能在三个柱上存放;

(3)在移动过程中,不允许大盘压小盘。

问将这n个盘子从a柱移动到c柱上,总计需要移动多少个盘次?

分析

当n=1时:A->C

当n=2时:A->B,A->C,B->C

当n=3时:

设f(n)为n 个盘子从1柱移到3柱所需移动的最少盘次。

当n=1时,f(1)=1。

当n=2时,f(2)=3。

以此类推,当1柱上有n(n>2)个盘子时,我们可以利用下列步骤:

第一步:先借助3柱把1柱上面的n-1个盘子移动到2柱上,所需的移动次数为f(n-1)。

第二步:然后再把1柱最下面的一个盘子移动到3柱上,只需要1 次盘子。

第三步:再借助1柱把2柱上的n-1个盘子移动到3上,所需的移动次数为f(n-1)。

由以上3步得出总共移动盘子的次数为:f(n-1)+1+ f(n-1)。

所以:f(n)=2f(n-1)+1

hn=2hn-1+1 =2n-1    边界条件:h1=1

(二)递推的应用(组合计数)

例题4:数的计数

【问题描述】我们要求找出具有下列性质数的个数(包含输入的自然数n),先输入一个自然数n(n≤1000),然后对此自然数按照如下方法进行处理:
1.不作任何处理;
2.在它的左边加上一个自然数,但该自然数不能超过原数的一半;
3.加上数后,继续按此规则进行处理,直到不能再而 自然数为止;

方法1:用递推

用h[n]表示自然数n所能扩展的数据个数,则:h[1]=1,h[2]=2,h[3]=2,h[4]=4,h[5]=4,h[6]=6,h[7]=6,h[8]=10,h[9]=10。分析上数据,可得递推公式:h[i]=1+h[1]+h[2]+…+h[i/2]。时间复杂度O(n2)。

方法2:是对方法1的改进。我们定义数组s

s(x)=h(1)+h(2)+…+h(x),

h(x)=s(x)-s(x-1)

此算法的时间复杂度可降到O(n)。

方法3:还是用递推

只要做仔细分析,其实我们还可以得到以下的递推公式:

(1)当i为奇数时,h(i)=h(i-1);

(2) 当i为偶数时,h(i)=h(i-1)+h(i/2);

【例题6】传球游戏

【问题描述】上体育课的时候,小蛮的老师经常带着同学们一起做游戏。这次,老师带着同学们一起做传球游戏。
  游戏规则是这样的:n(3<=n<=30)个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹哨子时开始传球,每个同学可以把球传给自己左右的两个同学中的一个(左右任意),当老师再吹哨子时,传球停止,此时,拿着球没传出去的那个同学就是败者,要给大家表演一个节目。

  聪明的小蛮提出一个有趣的问题:有多少种不同的传球方法可以使得从小蛮手里开始传的球,传了m(3<=m<=30)次后,又回到小蛮手里。两种传球被视作不同的方法,当且仅当这两种方法中,接到球的同学按照顺序组成的序列是不同的。比如3个同学1号、2号、3号,并假设小蛮为1号,球传了3次回到小蛮手里的方式有1->2->3->1和1->3->2->1,共两种。

分析:

设f[i][k]表示经过k次传到编号为i的人手中的方案数,传到i号同学的球只能来自于i的左边一个同学和右边一个同学,这两个同学的编号分别是i-1和i+1,所以可以得到以下的递推公式:

f[i][k]=f[i-1][k-1]+f[i+1][k-1]

f[1][k]=f[n][k-1]+f[2][k-1],   当i=1时

f[n][k]=f[n-1][k-1]+f[1][k-1],   当i=1时

边界条件:f[1][0]=1;结果在f[1][m]中。

核心代码:

cin>>n>>m;
memset(f,0,sizeof(f));
f[1][0]=1;
for(k=1;k<=m;k++)
{
    f[1][k]=f[2][k-1]+f[n][k-1];
    for(i=2;i<=n-1;i++)f[i][k]=f[i-1][k-1]+f[i+1][k-1];
    f[n][k]=f[n-1][k-1]+f[1][k-1];
}
cout<<f[1][m]<<endl;

(三)递推的应用(组合计数)

Catalan数

定义:Cn=n+2条边的多边形,能被分割成三角形的方案数,例如5边型的分割方案有:

如图,有一个正n+2边形。任取一边,从这边的端点开始,依次顺时针给顶点编号为:0,1,2,3,….,n,n+1(所取的边端点编号为:0,n+1)。这样,除线段所在顶点外,还有n个顶点:1,2,3,…,n。我们以该线段为三角形的一条边,另一个顶点为i(1<=i<=n)。

我们设题意要求的三角形剖分方案数为H(n),即除线段顶点(编号0与n+1)外,还有n个顶点时的三角形剖分方案为H(n)。则以顶点0,i为指定线段(上面还有1,2,…,i-1,共i-1个顶点)的剖分数位H(i-1);以顶点n+1,i为指定线段的剖分数为H(n-i)。根据乘法原理,以0,i,n+1为一剖分三角形的剖分数应为:H(i-1)*H(n-i),i=1,2,…,n,所得的剖分各不相同,根据加法原理则有:

这与Catalan数C(n)的表达式是一致的。故本题答案为H(n)=C(n)。

具体实现时,若直接用上述公式计算,对数字的精度要求较高。可将其化为递推式:

再进行递推计算,并且注意类型的定义要用long long长整型。

(四)递推的应用(博弈问题)

例题:走直线棋问题。

有如下所示的一个编号为1到n的方格:

现由计算机和人进行人机对奕,从1到n,每次可以走k个方格,其中k为集s={a1,a2, a3,....am}中的元素(m<=4),规定谁最先走到第n格为胜,试设计一个人机对奕方案,摸拟整个游戏过程的情况并力求计算机尽量不败。

分析:

题设条件:若谁先走到第N格谁将获胜,例如,假设S={1,2},从第N格往前倒推,则走到第N-1格或第N-2格的一方必败,而走到第N-3格者必定获胜,因此在N,S确定后,棋格中每个方格的胜、负或和态(双方都不能到达第N格)都是可以事先确定的。将目标格置为必胜态,由后往前倒推每一格的胜负状态,规定在自己所处的当前格后,若对方无论走到哪儿都必定失败,则当前格为胜态,若走后有任一格为胜格,则当前格为输态,否则为和态。

设1表示必胜态,-1表示必败态,0表示和态或表示无法到达的棋格。

例如,设N=10,S={1,2},则可确定其每个棋格的状态如下所示:

而N=10,S={2,3}时,其每格的状态将会如下所示:

有了棋格的状态图后,程序应能判断让谁先走,计算机选择必胜策略或双方和(双方均不能到达目标格)的策略下棋,就能保证计算机尽可能不败。

(五)递推的应用(动态规划中的递推)

例题:方格取数

在一个n×m的方格中,m为奇数,放置有n×m个数 ,如图,方格中间的下方有一人,此人可按照五个方向前进但不能越出方格,见右下图。人每走过一个方格必须取此方格中的数。要求找到一条从底到顶的路径,使其数相加之和为最大。输出和的最大值。

分析:

我们用坐标(x,y)唯一确定一个点,其中(m,n)表示图的右上角,而人的出发点是,受人前进方向的限制,能直接到达点(x,y)的点只有(x+2,y-1),(x+1,y-1),(x,y-1),(x-1,y-1),(x-2,y-1)。到点(x,y)的路径中和最大的路径必然要从(m/2,0)到(x+2,y-1),(x+1,y-1),(x,y-1),(x-1,y-1),(x-2,y-1)的几条路径中产生,既然要求最优方案,当然要挑一条和最大的路径,关系式如下:

Fx,y= Max{Fx+2,y-1 ,Fx+1,y-1,Fx,y-1,Fx-1,y-1,Fx-2,y-1}+Numx,y

其中Numx,y 表示(x,y) 点上的数字。

边界条件为:

动态规划与递推的关系

上题实质上是采用动态规划来求解,那么与递推动态规划之间到底是什么关系呢?

我们不妨画个图(如下图)。而通常人们理解的递推关系就是一般递推关系,故认为动态规划与递推关系是两个各自独立的个体。

1、一般递推边界条件很明显,动态规划边界条件比较隐蔽,容易被忽视

2、一般递推数学性较强,动态规划数学性相对较弱

3、一般递推一般不划分阶段,动态规划一般有较为明显的阶段

PS:更多递推练习,可以看看《递推求解专题练习

时间: 2024-10-05 11:22:52

算法--递推策略的相关文章

c语言递推算法1

递推算法之一:倒推法 1.一般分析思路: if 求解初始条件F1 then begin { 倒推 } 由题意(或递推关系)确定最终结果Fn; 求出倒推关系式Fi-1 =G(Fi ); i=n; { 从最终结果Fn出发进行倒推 } while 当前结果Fi非初始值F1 do 由Fi-1=G(Fi)倒推前项; 输出倒推结果F1和倒推过程; end { of then } else begin { 顺推 } 由题意(或递推关系)确定初始值F1(边界条件); 求出顺推关系式Fi=G(Fi-1); i=1

滑动平均滤波算法(递推平均滤波法)

//滑动平均滤波算法(递推平均滤波法) //ADNum为获得的AD数 //GN为数组value_buf[]的元素个数.该函数主要被调用,利用参数的数组传值 const int GN = 12; int filterPtr = 0; bool isFirst = true; public float gSum = 0; float[] gbuf  = new float[GN]; float GlideFilterAD(float ADNum) { if (isFirst) { isFirst =

斐波那契 递推算法

/***Date : 2014.12.10***/ //递推算法:是理性思维模式的代表,根据已有的数据和关系,逐步推导而得出结果. //执行过程:1)根据已知的结果和关系,求解中间结果. ///////////////////// 2)判断是否满足要求,若未满足,则继续根据已知结果和关系求解中间结果:若满足要求,则表示寻找到一个正确答案. //13世纪,意大利数学家斐波那契的<算盘书>中记载:兔子产仔问题. //一对两个月大的兔子,每月都可产仔一对,小兔子两月后的每月也可产仔一对;即1月生,3

数的计数——递推算法

Problem Description 我们要求找出具有下列性质数的个数(包括输入的自然数n).先输入一个自然数n(n<=1000),然后对此自然数按照如下方法进行处理: 不作任何处理: 在它的左边加上一个自然数,但该自然数不能超过原数的一半: 加上数后,继续按此规则进行处理,直到不能再加自然数为止. Input 输入有多组数据,每组数据为自然数n. Output 对于每组数据输出满足条件的数的个数. Sample Input 6 Sample Output 6 Hint 满足条件的数为6,16

数据结构与算法之递推算法 C++与PHP实现

数据结构是算法实现的基础,算法总是要依赖于某种数据结构来实现的.往往是在发展一种算法的时候,构建了适合于这种算法的数据结构.一种数据结构如果脱离了算法,也就没有存在的价值了. 算法的作用----解决任何一个实际问题,都不可避免地涉及到算法的问题,通过一定的算法,得到一个最优(或较优)的方案. 递推算法:递推算法是一种简单的算法,即通过已知条件,利用特定关系得出中间推论,直至得到结果的算法. 顺推法:从已知条件出发,逐步推算出要解决的问题的方法. 逆推法:从已知问题的结果出发,用迭代表达式逐步推算

组合数递推算法

主要式子:C(n,k)=C(n-1,k-1)+C(n-1,k),C(n,k)表示从n个物品中挑选k个物品的所有组合数. 1 #include<stdio.h> 2 #include<string.h> 3 #define N 10 4 int c[N][N]; 5 void init() 6 { 7 memset(c,0,sizeof(c)); 8 for(int i=0;i<=N;i++) 9 { 10 c[i][0]=1; 11 c[i][i]=1; 12 for(int

一天一道算法题---6.6---排列递推(我不会)

感谢微信平台: 一天一道算法题-----每天多一点进步--- 好吧 这题 我看了它的分析 还是感觉很不清晰 自己的思路 闪过 逆序数 但也不行,,, 把题目 先放上来 problem:列出一个 1~n 的排列 可以通过一系列的交换得到(1,2,3--n)比如,{2,1,4,3}需要两次交换(1和2 3和4),(4,2,3,1)需要一次(4和1):给定n和k 统计有多少个排列至少需要K次交换能变成(1,2,3--n); 各位 大神 不要吝啬 留下你们的思路与想法 告知我 thanks... 今天

算法入门——递推

主要思想: 通过已知的条件(已知结果),利用特定的关系,逐步递推(顺推/逆推),直到有解或者无解. 主要分为两种:顺推,从已知条件出发,直至推出解. 逆推,从已知结果出发,直至推出解. 需要注意的:每一递推结果,都是下一步递推的条件. 顺推: 斐波那契数列  F0=0,F1=1,Fn=F(n-1)+F(n-2)(n>=2,n∈N*) 实例 兔子的总数有多少? 一对兔子,每月能生一对,而每对兔子3个月后可以生育.求12个月后共有多少兔子. #include<stdio.h> #define

滑动平均滤波算法(递推平均滤波法)(转帖)

//滑动平均滤波算法(递推平均滤波法)--C语言版 int FilterI=0; //ADNum为获得的AD数 //n为数组value_buf[]的元素个数.该函数主要被调用,利用参数的数组传值 int GlideFilterAD(int value_buf[],int n,int ADNum) { int sum=0; value_buf[FilterI++]=ADNum; if(FilterI==n) FilterI=0; //先进先出,再求平均值 for(int count=0;count