数据结构论文——递归算法的讨论
所谓递归算法是把问题转化为规模缩小了的同类问题的子问题。然后递归调用函数(或过程)来表示问题的解。一个过程(或函数)直接或间接调用自己本身,这种过程(或函数)叫递归过程(或函数)。递归过程一般通过函数或子过程来实现。递归方法:在函数或子过程的内部,直接或者间接地调用自己的算法。递归算法是一种直接或者间接地调用自身算法的过程。在计算机编写程序中,递归算法对解决一大类问题是十分有效的,它往往使算法的描述简洁而且易于理解。递归算法解决问题的特点:
(1) 递归就是在过程或函数里调用自身。
(2) 在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口。
(3) 递归算法解题通常显得很简洁,但递归算法解题的运行效率较低。
(4) 在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等。所以一般不提倡用递归算法设计程序。下面就让我们结合例子详细讨论一下递归算法。
一、递归算法的原理
递归算法简单的说就是在函数中调用函数自身,不断调用,直到满足函数得出计算结果(某个条件)。因为其需要不断循环的调用自身,所以称为递归调用。递归的原理,其实就是一个栈(stack), 比如求5的阶乘,要知道5的阶乘,就要知道4的阶乘,4又要是到3的,以此类推,所以递归式就先把5的阶乘表示入栈, 在把4的入栈,直到最后一个,之后呢在从1开始出栈, 看起来很麻烦,确实很麻烦,他的好处就是写起代码来,十分的快,而且代码简洁,其他就没什么好处了,运行效率出奇的慢。还有一个十分形象的例子:从前有座山,山里有个庙,庙里有个老和尚正在讲故事:从前有座山,山里有个庙,庙里有个老和尚正在讲故事:从前有座山,山里有个庙,庙里有个老和尚正在讲故事……如此循环往复到最终的要求。
递归分为2种,直接递归和间接递归。直接递归,比如方法A内部调用方法A自身。间接递归,比如方法A内部调用方法B,方法B内部调用方法C,方法C内部调用方法A。递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,
大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。递归的三个条件:边界条件、递归前进段、递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。
二、递归算法的用处
了解了递归算法的原理,那么什么时候需要用到递归算法呢?①问题的定义是递归的。有许多数学公式、数列的定义是递归的,例如求n!和Fibonacci数列等。这些问题的求解的过程可以将其递归定义直接转化为对应的递归算法。
例如阶乘函数的定义
1 当n=0时
n!=
n*(n-1)*…*1 当n>0时
这时候递归的定义可以用如下的函数表示:
1 当n=0时
f(n)=
n*f(n-1) 当n>0时
也就是说,函数f(n)的定义用到了自己本身f(n-1)。
②数据结构是递归的。
有些数据结构是递归的。例如,第2章中介绍过的单链表就是一种递归数据结构,其结点类型定义如下:
typedef struct LNode
{
ElemType data;
struct LNode *next;
} LinkList;
该定义中,结构体LNode的定义中用到了它自身,即指针域next是一种指向自身类型的指针,所以它是一种递归数据结构。 对于递归数据结构,采用递归的方法编写算法既方便又有效。例如,求一个不带头结点的单链表head的所有data域(假设为int型)之和的递归算法如下:
int Sum(LinkList *head)
{
if (head==NULL)
return 0;
else
return(head->data+Sum(head->next));
}
③问题求解的过程是递归的。
一个典型的例子是在有序数组中查一个数据元素是否存在的折半查算法。有序数组元素为1;3;4;5;17;18;31;33;寻值为17的数据(如上图)。
折半查无非就是三种情况,其中两种情况的问题解法如果以算法来表示,都存在算法调用自身的情况。
递归算法的特点就是:将问题分解成为形式上更加简单的子问题来进行求解。递归算法不但是一种有效的分析问题方法,也是一种有效的算法设计方法,是解决很多复杂问题的重要方法。
递归算法的基本思想是:把规模大的、较难解决的问题变成规模较小的、易解决的同一问题。规模较小的问题又变成规模更小的问题,并且小到一定程度可以直接得出它的解,从而得到原来问题的解。
利用递归算法解题,首先要对问题的以下三个方面进行分析:
1、决定问题规模的参数。需要用递归算法解决的问题,其规模通常都是比较大的,在问题中决定规模大小(或问题复杂程度)的量有哪些?把它们出来。
2、问题的边界条件及边界值。在什么情况下可以直接得出问题的解?这就是问题的边界条件及边界值。
3、解决问题的通式。把规模大的、较难解决的问题变成规模较小、易解决的同一问题,需要通过哪些步骤或等式来实现?这是解决递归问题的难点。
把这些步骤或等式确定下来。 把以上三个方面分析好之后,就可以在子程序中定义递归调用。
利用递归算法解题,首先要对问题的以下三个方面进行分析:
1、决定问题规模的参数。需要用递归算法解决的问题,其规模通常都是比较大的,在问题中决定规模大小(或问题复杂程度)的量有哪些?把它们出来。
2、问题的边界条件及边界值。在什么情况下可以直接得出问题的解?这就是问题的边界条件及边界值。
3、解决问题的通式。把规模大的、较难解决的问题变成规模较小、易解决的同一问题,需要通过哪些步骤或等式来实现?这是解决递归问题的难点。
把这些步骤或等式确定下来。 把以上三个方面分析好之后,就可以在子程序中定义递归调用。
三、递归算法的一个坏例子
汉诺塔(Hanoi tower)问题:传说在古代印度的贝拿勒斯神庙,有一块黄铜板上插了3根宝石柱,在其中一根宝石柱自上而下由小到大地叠放着64个大小不等的金盘。一名僧人把这些金盘从一根宝石柱移到另外一根上。僧人在移动金盘时遵守下面3条规则:
第一,一次只能移动一个金盘。
第二,每个金盘只能由一根宝石柱移到另外一根宝石柱。
第三,任何时候都不能把大的金盘放在小的金盘上。
神话说,如果僧人把64个金盘完全地从一根宝石柱移到了另外一根上,传说中的末日就要到了——世界将在一声霹雳中消灭,梵塔、庙宇和众生都将同归于尽。
第一,一次只能移动一个金盘。
第二,每个金盘只能由一根宝石柱移到另外一根宝石柱。
第三,任何时候都不能把大的金盘放在小的金盘上。
神话说,如果僧人把64个金盘完全地从一根宝石柱移到了另外一根上,传说中的末日就要到了——世界将在一声霹雳中消灭,梵塔、庙宇和众生都将同归于尽。
移动金盘是个很繁琐的过程。通过计算,对于64个金盘至少需要移动2的64次方,等于1.8乘以10的19次方。如果僧侣移动金盘一次需要1秒钟,移动这么多次共需约5845亿年。把这个寓言和现代科学推测对比一下倒是有意思的。按照现代的宇宙进化论,恒星、太阳、行星(包括地球)是在三十亿年前由不定形物质形成的。我们还知道,给恒星特别是给太阳提供能量的“原子燃料”还能维持100~150亿年。因此,我们太阳系的整个寿命无疑要短于二百亿年。可见远不等僧侣们完成任务,地球早已毁灭了。
下面先试验一下汉诺塔游戏,体会一下它的原理,思考其中的递归思想。可以用递归方法求解n个盘子的汉诺塔问题。
基本思想:
1个盘子的汉诺塔问题可直接移动。
n个盘子的汉诺塔问题可递归表示为,首先把上边的n-1个盘子从A柱借助C移到B柱,然后把最下边的一个盘子从A柱移到C柱,最后把移到B柱的n-1个盘子再借助A移到C柱。(如下图)
下面进行汉诺塔递归程序设计。首先,盘子的个数n是必须的一个输入参数,对n个盘子,我们可从上至下依次编号为c语言用递归函数求n的阶乘1,2,…,n;其次,输入参数还需有3个柱子的代号,我们令3个柱子的参数名分别为fromPeg,auxPeg和toPeg;最后,汉诺塔问题的求解是一个处理过程,因此算法的输出是n个盘子从柱子fromPeg借助柱子auxPeg移动到柱子toPeg的移动步骤,我们设计每一步的移动为屏幕显示如下形式的信息:将第i 个盘子从X 移动到Y。移动方法示意图如下
这样,汉诺塔问题的递归算法可设计如下:
void Hanoi(int n,char a,char b,char c)
{
if(n==1)//递归出口
printf("\t将第%d个盘片从%c移动到%c\n",n,a,c);
else
{
Hanoi(n-1,a,c,b);//把n-1个圆盘从fromPeg借助toPeg移到auxPeg
printf("\t将第%d个盘片从%c移动到%c\n",n,a,c);
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论