基本算法思想---递归

为什么要用递归 ?

很多不理解递归的人,总认为用循环就可以实现的功能为什么要用递归? 理解起来还挺麻烦,完全没必要用递归嘛
其实这是一种很肤浅的理解。因为好多时候循环没办法代替递归,就算可以代替,写出来的代码也很复杂,不方便理解.
比如树和图的遍历等等.

大家都知道递归分两步,递和归,递归对于空间性能来说,简直就是造孽,对于追求时空完美的人来说,简直无法接接受.
如果递归仅仅是循环,估计现在我们就看不到递归了。递归之所以现在还存在是因为递归可以产生无限循环体,也就是说有可能产生100层也可能10000层for循环。
例如对于一个字符串进行全排列,字符串长度不定,那么如果你用循环来实现,你会发现你根本写不出来,
这个时候就要调用递归,而且在递归模型里面还可以使用分支递归,例如for循环与递归嵌套,或者这节枚举几个递归步进表达式,每一个形成一个递归。

很对人对递归难以理解主要是因为 递归的时候函数自己调用自己不知道怎么处理的.
下边首先上一个例子,来说明递归.

问题: 求 3 的阶乘

众所周知, 3!=3*2*1=6

2!=2*1!

3!=3*2!

4!=4*3!
.
.
.

n!=n*(n-1)!


循环写法如下:

int get_factorial(int n)
{
    int i=1,ret=1;
    for(i=1;i<=n;i++)
    {
        ret=i*ret;
    }
    return ret;
}
================================================================================================
4!=4*3*2*1=24

如果仅仅只是求 3!
可以这么写:

int get_1factorial()
{
    return 1;
}

int get_2factorial()
{
    return 2*get_1factorial();
}
int get_3factorial()
{
    return 3*get_2factorial();
}
int get_4factorial()
{
    return 4*get_3factorial();
}

可以发现 :
get_4factorial()  中调用 get_3factorial(),继而 调用 get_2factorial(), get_1factorial().

在这里 我们发现 get_4factorial(), get_3factorial(), get_2factorial()里边 很相似,
完全 可以写成一个形式:
int get_nfactorial()
{
    return n*get_(n-1)factorial();
}

这么写就语法错误了, 改造一下:
int get_factorial(int n)
{
    return n*get_factorial(n-1);
}

写到这里,有的人就困惑了, 这不是函数自己调用自己吗?
这么一来n的值不就乱套了吗 ?
先说一下 函数A调用函数B的过程:
 
 
第一步:函数调用
    1、将函数A调用语句下一条语句的地址保存到在栈中,以便调用函数B完成后返回。(将函数放到栈空间中称为压栈)。
    2、对A中的变量,指针等值压栈。
    3、跳转到函数B处开始执行。
第二步:函数B执行
    4、执行函数体的功能体。
第三步:返回
    5、返回过程执行的是函数B中的return语句。
    当return语句不带有表达式时,按照保存的地址返回,当return语句带有表达式时,将计算出的return表达式的值保存起来,然后再返回。
    将A中的变量,指针等值出栈。
    跳转到函数A下一条语句开始执行。

这就好比是进入了一个宝塔, 每一层看起来一样(函数名字一样), 其实已经不一样了.

接着看,
int get_factorial(int n)
{
    return n*get_factorial(n-1);
}
很明显的一个问题, 这个函数是个死循环, 因为没有终止条件.
加上终止条件, 这就是阶乘的递归写法:

int get_factorial(int n)
{
    if( n==1)
        return 1;
    else
        return n*get_factorial(n-1);
}

用归纳法来理解递归

数学都不差的我们,第一反应就是递归在数学上的模型是什么。毕竟我们对于问题进行数学建模比起代码建模拿手多了。
(当然如果对于问题很清楚的人也可以直接简历递归模型了,运用数模做中介的是针对对于那些问题还不是很清楚的人)

自己观察递归,我们会发现,递归的数学模型其实就是归纳法,这个在高中的数列里面是最常用的了。回忆一下归纳法。

归纳法适用于想解决一个问题转化为解决他的子问题,而他的子问题又变成子问题的子问题,

而且我们发现这些问题其实都是一个模型,也就是说存在相同的逻辑归纳处理项。当然有一个是例外的,也就是递归结束的哪一个处理方法不适用于我们的归纳处理项,当然也不能适用,否则我们就无穷递归了。这里又引出了一个归纳终结点以及直接求解的表达式。如果运用列表来形容归纳法就是:

步进表达式:问题蜕变成子问题的表达式
结束条件:什么时候可以不再是用步进表达式
直接求解表达式:在结束条件下能够直接计算返回值的表达式
逻辑归纳项:适用于一切非适用于结束条件的子问题的处理,当然上面的步进表达式其实就是包含在这里面了。
这样其实就结束了,递归也就出来了。递归算法的一般形式:

void func( mode)
{
    if(endCondition)
    {
        constExpression         //基本项
    }
    else
    {
        accumrateExpreesion     //归纳项
        mode=expression         //步进表达式
            func(mode)          //调用本身,递归
    }
}


很多人对递归的理解不太深刻。一直就停留在“自己调用自己”的程度上。这其实这只是递归的表象(严格来说连表象都概括得不全面,

因为除了“自己调用自己”的递归外,还有交互调用的递归)。而递归的思想远不止这么简单。

递归,并不是简单的“自己调用自己”,也不是简单的“交互调用”。它是一种分析和解决问题的方法和思想。
简单来说,递归的思想就是:把问题分解成为规模更小的、具有与原问题有着相同解法的问题。
比如二分查找算法,就是不断地把问题的规模变小(变成原问题的一半),而新问题与原问题有着相同的解法。

有些问题使用传统的迭代算法是很难求解甚至无解的,而使用递归却可以很容易的解决。比如汉诺塔问题。

但递归的使用也是有它的劣势的,因为它要进行多层函数调用,所以会消耗很多堆栈空间和函数调用时间。

既然递归的思想是把问题分解成为规模更小且与原问题有着相同解法的问题,那么是不是这样的问题都能用递归来解决呢?

答案是否定的。并不是所有问题都能用递归来解决。那么什么样的问题可以用递归来解决呢?一般来讲,能用递归来解决的问题必须满足两个条件:

可以通过递归调用来缩小问题规模,且新问题与原问题有着相同的形式。
存在一种简单情境,可以使递归在简单情境下退出。
如果一个问题不满足以上两个条件,那么它就不能用递归来解决。

为了方便理解,还是拿斐波那契数列来说下:求斐波那契数列的第N项的值。

这是一个经典的问题,说到递归一定要提到这个问题。斐波那契数列这样定义:f(0) = 0, f(1) = 1, 对n > 1, f(n) = f(n-1) + f(n-2)

这是一个明显的可以用递归解决的问题。让我们来看看它是如何满足递归的两个条件的:

对于一个n>2, 求f(n)只需求出f(n-1)和f(n-2),也就是说规模为n的问题,转化成了规模更小的问题;
对于n=0和n=1,存在着简单情境:f(0) = 0, f(1) = 1。
因此,我们可以很容易的写出计算费波纳契数列的第n项的递归程序:

int fib(n){
    if(n == 0)
        return 0;
    else if(n == 1)
        return 1;
    else
        return f(n-1) + f(n-2);
}
在编写递归调用的函数的时候,一定要把对简单情境的判断写在最前面,
以保证函数调用在检查到简单情境的时候能够及时地中止递归,否则,你的函数可能会永不停息的在那里递归调用了。

递归在处理问题时要反复调用函数,这增大了它的空间和时间开销,所以在使用迭代可以很容易解决的问题中,使用递归虽然可以简化思维过程,

但效率上并不合算。效率和开销问题是递归最大的缺点。

虽然有这样的缺点,但是递归的力量仍然是巨大而不可忽视的,因为有些问题使用迭代算法是很难甚至无法解决的(比如汉诺塔问题)。这时递归的作用就显示出来了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值