Python进阶:递归算法

Python09

Python进阶:递归算法,第1张

  递归算法常用来解决结构相似的问题

  所谓结构相似,是指构成原问题的子问题与原问题在结构上相似,可以用类似的方法解决。具体地,整个问题的解决,可以分为两部分:第一部分是一些特殊情况,有直接的解法;第二部分与原问题相似,但比原问题的规模小,并且依赖第一部分的结果。

  本质上,递归是把一个不能或不好解决的大问题转化成一个或几个小问题,再把这些小问题进一步分解成更小的问题,直至每个小问题都可以直接解决。

  实际上,递归会将前面所有调用的函数暂时挂起,直到递归终止条件给出明确的结果后,才会将所有挂起的内容进行反向计算。其实,递归也可以看作是一种反向计算的过程,前面调用递归的过程只是将表达式罗列出来,待终止条件出现后,才依次从后向前倒序计算前面挂起的内容,最后将所有的结果一起返回。

递归,emmmmmmm,拥有一种魅力,接近人的立即思维,容易理解,又不容易理解。

递归算法的优点: 它使我们能够简洁地利用重复结构呈现诸多问题。通过使算法描述以递归的方式利用重复结构,我们经常可以避开复杂的案例分析和嵌套循环。这种算法会得出可读性更强的算法描述,而且十分有效。

但是 ,递归的使用要根据相应的成本来看,每次递归python解释器都会给一个空间来记录函数活动状态。但是有时候内存成本很高,有时候将递归算法转为非递归算法是一种好办法。

当然我们可以换解释器、使用堆栈数据结构等方法,来管理递归的自身嵌套,减小储存的活动信息,来减小内存消耗。

最近算法学到了递归这一块,写了三个课后习题:

给一个序列S,其中包含n个元素,用递归查找其最大值。

输出:

调和数:Hn = 1 + 1/2 + 1/3 + ··· + 1/n

输出:

例如:"12345"<class 'str'> 转换为12345<class 'int'>

输出:

递归分为线性递归、二路递归、多路递归。

函数的递归调用

递归问题是一个说简单也简单,说难也有点难理解的问题.我想非常有必要对其做一个总结.

首先理解一下递归的定义,递归就是直接或间接的调用自身.而至于什么时候要用到递归,递归和非递归又有那些区别?又是一个不太容易掌握的问题,更难的是对于递归调用的理解.下面我们就从程序+图形的角度对递归做一个全面的阐述.

我们从常见到的递归问题开始:

1 阶层函数

#include <iostream>

using namespace std

int factorial(int n)

{

if (n == 0)

{

return 1

}

else

{

int result = factorial(n-1)

return n * result

}

}

int main()

{

int x = factorial(3)

cout <<x <<endl

return 0

}

这是一个递归求阶层函数的实现。很多朋友只是知道该这么实现的,也清楚它是通过不断的递归调用求出的结果.但他们有些不清楚中间发生了些什么.下面我们用图对此做一个清楚的流程:

根据上面这个图,大家可以很清楚的看出来这个函数的执行流程。我们的阶层函数factorial被调用了4次.并且我们可以看出在调用后面的调用中,前面的调用并不退出。他们同时存在内存中。可见这是一件很浪费资源的事情。我们该次的参数是3.如果我们传递10000呢。那结果就可想而知了.肯定是溢出了.就用int型来接收结果别说10000,100就会产生溢出.即使不溢出我想那肯定也是见很浪费资源的事情.我们可以做一个粗略的估计:每次函数调用就单变量所需的内存为:两个int型变量.n和result.在32位机器上占8B.那么10000就需要10001次函数调用.共需10001*8/1024 = 78KB.这只是变量所需的内存空间.其它的函数调用时函数入口地址等仍也需要占用内存空间。可见递归调用产生了一个不小的开销.

2 斐波那契数列

int Fib(int n)

{

if (n <= 1)

{

return n

}

else

{

return Fib(n-1) + Fib(n-2)

}

}

这个函数递归与上面的那个有些不同.每次调用函数都会引起另外两次的调用.最后将结果逐级返回.

我们可以看出这个递归函数同样在调用后买的函数时,前面的不退出而是在等待后面的结果,最后求出总结果。这就是递归.

3

#include <iostream>

using namespace std

void recursiveFunction1(int num)

{

if (num <5)

{

cout <<num <<endl

recursiveFunction1(num+1)

}

}

void recursiveFunction2(int num)

{

if (num <5)

{

recursiveFunction2(num+1)

cout <<num <<endl

}

}

int main()

{

recursiveFunction1(0)

recursiveFunction2(0)

return 0

}

运行结果:

0

1

2

3

4

4

3

2

1

0

该程序中有两个递归函数。传递同样的参数,但他们的输出结果刚好相反。理解这两个函数的调用过程可以很好的帮助我们理解递归:

我想能够把上面三个函数的递归调用过程理解了,你已经把递归调用理解的差不多了.并且从上面的递归调用中我们可以总结出递归的一个规律:他是逐级的调用,而在函数结束的时候是从最后面往前反序的结束.这种方式是很占用资源,也很费时的。但是有的时候使用递归写出来的程序很容易理解,很易读.

为什么使用递归:

1 有时候使用递归写出来的程序很容易理解,很易读.

2 有些问题只有递归能够解决.非递归的方法无法实现.如:汉诺塔.

递归的条件:

并不是说所有的问题都可以使用递归解决,他必须的满足一定的条件。即有一个出口点.也就是说当满足一定条件时,程序可以结束,从而完成递归调用,否则就陷入了无限的递归调用之中了.并且这个条件还要是可达到的.

递归有哪些优点:

易读,容易理解,代码一般比较短.

递归有哪些缺点:

占用内存资源多,费时,效率低下.

因此在我们写程序的时候不要轻易的使用递归,虽然他有他的优点,但是我们要在易读性和空间,效率上多做权衡.一般情况下我们还是使用非递归的方法解决问题.若一个算法非递归解法非常难于理解。我们使用递归也未尝不可.如:二叉树的遍历算法.非递归的算法很难与理解.而相比递归算法就容易理解很多.

对于递归调用的问题,我们在前一段时间写图形学程序时,其中有一个四连同填充算法就是使用递归的方法。结果当要填充的图形稍微大一些时,程序就自动关闭了.这不是一个人的问题,所有人写出来的都是这个问题.当时我们给与的解释就是堆栈溢出。就多次递归调用占用太多的内存资源致使堆栈溢出,程序没有内存资源执行下去,从而被操作系统强制关闭了.这是一个真真切切的例子。所以我们在使用递归的时候需要权衡再三.