• 算法总结之递推与递归


    递推算法

    递归算法大致包括两方面的内容:1)递归起点 ; 2)递归关系

    递推起点

    递归起点一般由题目或者实际情况确定,不由递归关系推出。如果无法确定递归起点,那么递归算法就无法实现。可见,递归起点是递归算法中的重要一笔。

    递推关系

    递归关系是递归算法的核心。常见的递归关系有以下几项:

    • 1)一阶递推;
    • 2)多阶递推;
    • 3)间接递推;
    • 4)逆向递推;
    • 5)多维递推。

    下面通过栗子来详细介绍一下上述类别的递推关系。

    1. 一阶递推
    在计算f(i)时,只用到前面项中的一项,如等差数列。公差为3的等差数列,其递推关系为:

    f(i)=f(i-1)+3

    eg. 平面上10条直线最多能把平面分成几部分?
    分析:以直线数目为递推变量,假定i条直线把平面最多分成f(i)部分,则f(i-1)表示i-1条直线把平面分成的最多部分。在i-1条直线的平面上增加直线i,易得i与平面上已经存在了的i-1条直线最多各有一个交点,即直线i最多被分成i段,而这i段将会依次将平面一分为二,即直线i将最多使平面多增加i部分。所以,递推关系可表示为:f(i)=f(i-1)+i
    易得当0条直线时,平面为1部分。所以f(0)=1为递推起点。
    上述分析可用下面代码表示(c++):

    #define MAX 100
    int f[MAX] //存放f(i)
    int lines(int n){
    //输入n为直线数目
    //输出最多部分数
        int i;
        f(0)=1;
        for(i=1;i<=n;i++){
              f[i]=f[i-1]+3;
        }
        return f[i];
        }
    

    2. 多阶递推
    在计算f(i)时,要用到前面计算过的多项,如Fibonacci数列。
    eg.求Fibonacci的第10项。
    分析:总所周知,Fibonacci数列中的第n项等于第n-1项加上n-2项。所以递推关系为
    f(i)=f(i-1)+f(i-2);且f[0]=f[1]=1。
    C++代码如下:

    #define MAX 100
    int f[MAX];
    int fib(int n){
    //输入n为项数
    //输出第n个fib数
       int i;
       f[0]=0;
       f[1]=1;
       for(i=2;i<=n;i++){
             f[i]=f[i-1]+f[i-2];
             }
       return f[n]
       }
    

    3. 间接递推
    在计算f[i]时需要中间量,而计算中间量要用到之前计算过的项。
    eg.现有四个人做传球游戏,要求接球后马上传给别人。由甲先传,并作为第一次传球。求经过10次传球,球仍回到发球人甲手中的传球方式的种数。
    分析:定义两个状态,1)当前球在甲上,经过i次传球之后球仍在甲上,此状况记为F,其传球方式的种数为f(i);2)当前球不在甲手上,经过i次传球之后球在甲手上,此状态记为G,其传球方式的种数为g(i)。
    对于状态1):甲传出一个球之后,接球的人的状态便变成G(i-1)了,由于甲可以传给3个不同的人,所以f(i)=3g(i-1);
    对于状态2):持球者可以选择把球传给甲,此时是F(i-1)状态;也可以把球传给另外两个人,即2
    G(i-1)状态。所以g(i)=f(i-1)+2*g(i-1).
    计算递推起点,由于甲第一次不可能把球传给自己,所以f(1)=0;其他人要传一次球就把球传给甲,那只有一种方式(直接把球传给甲),即g(1)=1.
    上述递推关系便是间接递推。用c++实现如下:

    #define MAX 100
    int f[MAX];
    int g[MAX];
    int ball(int n){
    //输入n为传球次数
    //输出为传球方式的种数
    	int i;
    	f[1]=0;
    	g[1]=0;
    	for(i=2;i<=n;i++){
    		f[i]=3*g[i-1];
    		g[i]=f[i-1]+2*g[i-1];
    		}
    	return f[n];
    	}
    
    

    4. 逆向递推
    顾名思义,就是从后面开始往前推。
    eg.硬币下棋游戏。棋盘上标有第0站,第1站...第100站,一开始棋子在第0站,棋手每次投一次硬币,若硬币正面向上,则往前跳两站;否则,往前跳一站...直到棋子跳到第99站(胜利大本营),第100站(失败大本营)时,游戏结束。如果硬币出现正反面的概率均为0.5,分别求出棋子到达胜利大本营和失败大本营的概率。
    分析:假设记从第i站开始,最后到达100站的概率为f(i)。而从第i站,投掷一次硬币,有0.5的概率到达第i+1站,有0.5的概率到达i+2站。所以递推关系为:f(i)=0.5f(i+1)+0.5f(i+2).
    易得递推起点f(100)=1,f(99)=0.因为到达99站,游戏结束。
    上述就是逆递推的一个过程。c++实现如下:

    #define MAX 100
    double f[MAX];
    double prob(){
    //无输入
    //输出为到达100站的概率
    	int i;
    	f[100]=1.0;
    	f[99]=0;
    	for(i=98;i.=0;i--){
    		f[i]=0.5*f[i+1]+0.5*f[i+2];
    		}
    	return f[0];
    	}
    

    5. 多维递推
    元素处于一个多维矩阵中,递推需要使用矩阵中其他位置的元素。
    例子日后更新。

    递归函数

    在计算机科学中,如果一个函数的实现中,出现对函数自身的调用语句,则该函数称为递归函数。
    递推算法可以用递归函数来实现。一般来说循环递推算法比递归函数要快,但递归函数的可读性更棒。
    把上面的部分递推算法改写成递归函数。
    1)平面划分

    int lines(int i){
    	if(i<=0)
    		return 1;
    	else
    		return lines(i-1)+i;
    		}
    

    2)Fibonacci数

    int fib(int i){
       if(i==0)
       		return 0;
    	if(i==1)
    		return 1;
    	else
    		return fib(i-1)+fib(i-2);
    	}
    

    由上面的代码可以分析到,递推起点在递归函数中起到了递归截止作用。

    递归函数的执行过程

    递归函数每次调用自身都会生成一个激活帧(包含程序的参数、局部变量、返回值、以及该程序执行完毕后返回上一层的指令地址等),同时把计算控制交给下一次调用。这些激活帧存在在系统中先进后出的栈里。所以,程序的递归调用过大的话,会引发栈溢出。

    尾递归

    在计算机科学里,尾调用是指一个函数里的最后一个动作是一个函数调用的情形:即这个调用的返回值直接被当前函数返回的情形。

    上面说到递归函数需要在调用多次时需要保留很多激活帧,这会引发栈溢出。但如果采用尾递归的话,就可以避免这个情况。因为尾递归在程序的最后动作只是调用函数,不涉及其他计算问题,所以可以优化删去很多中间的激活帧。
    如上面递归函数fib(),其最后一步就涉及加法,所以不是尾递归,但可以把它改成尾递归。如下:

    int fib(int n,int f1,int f2)
    {
    //初始f1=0;f2=1
    	if(n==0)
    		return f1;
    	else
    		return fib(n-1,f2,f1+f2)
    	}
    

    由上面代码可看到,函数的最后调用就是一个函数,不涉及其他计算。

    小结

    递归函数一定要有递归起点作为递归结束标志。

  • 相关阅读:
    数据类型
    32个关键字
    标识符
    Xcode常用快捷键
    Linux中级之keepalived配置
    linux中级之keepalived概念
    Linux中级之lvs三个模式的图像补充(nat,dr,tun)
    linux中级之防火墙的数据传输过程
    Linux中级之netfilter/iptables应用及补充
    linux中级之lvs配置(命令)
  • 原文地址:https://www.cnblogs.com/surecheun/p/9737378.html
Copyright © 2020-2023  润新知