递归函数论与程序设计的关系
(刘爱贵 高能物理研究所计算中心 北京 2003年)
摘要 : 递归函数论是元计算机科学理论基础,它与计算机科学的实践紧密相关。递归思想影响了程序设计语言的构造,甚至影响了计算机系统结构。本文根据递归函数类的构造过程来论证递归与程序设计语言基层控制机制的关系,以及递归思想对计算机科学其他一些方面的影响。
关键词 : 递归定义 复合 原始递归 极小化 结构化程序设计 递归过程 栈
可计算性理论是计算机科学的理论基础之一,递归函数论和图灵机是其两大理论支柱。图灵机是一种抽象计算机,它为可计算数的计算提供了强的有力的计算手段,是计算机的雏形和冯.诺依曼计算机的理论模型,影响了传统计算机的设计思想。递归函数理论作为元计算机科学理论基础,明确了计算机可计算的对象——递归函数类。图灵论题:一个函数是可计算的当且仅当图灵机是可计算的。而图灵机可计算的函数正是递归函数类,确切地说是一般递归函数类。图灵机和递归函数论有着天然的联系。递归函数论与计算机科学的实践有着密切的关系,递归的思想直接影响了程序设计语言的构造,进一步影响了计算机系统的结构。
递归是一种定义对象类的方法,这个方法称为递归定义。存在一定的原始对象,在被定义的类中,我们给出一些方法,运用这些方法要给定某些已知在类中的对象中,可以产生类中的新对象。被定义的类中的全体成员恰好而且仅仅是那些由原始对象开始,反复使用所给定的获取新对象的规则而引进的新对象。这种定义类的方法称为递归定义。递归函数类可以用递归来定义,它由原始对象经过复合运算、原始递归运算和极小化运算而得到。原始对象是三个最简单的原始函数:
1、继函数,S(x)=x+1。
2、零函数,N(x)=0。
在这三个原始函数基础上,我们可以定义出非常丰富的函数类。运算规则有:
1、复合运算。如果已知函数
是所生成类中的成员,那么函数 是由这些函数通过复合运算得到的(图1)。
2、原始递归运算。我们称将要定义的函数为r(x)。原始递归的运算包括把某种特定的值赋给r(0),和一个由已经得到的r(k)来确定r(k+1)的过程(图2)。
图2 原始递归的运算——r(x)的计算
3、 极小化运算。对于每一个给定的全函数 引进一个新函数 ,对于给定的 ,新函数的值为使得 的最小的y。记作 ,这样的y值可能没有定义(图3)。
图3 极小化运算——由f(y,x)计算h(x)
这三个运算能力是强有力的。原始函数通过复合生成基本递归函数类,继而根据原始递归运算生成递归函数类,再经过极小化运算即可得到部分递归函数类。由此可见复合运算、原始递归运算和极小化运算在数学体系中的关键作用,这也使得我们推想它们在程序设计中的重要性,它们与程序基层控制机制密切相关。
构造性是计算机系统的最根本特征,也是能行过程的重要特征,而递归是最具代表性的构造性数学方法。递归函数类就是递归构造得到的,而程序是构造性证明,图灵机理论已经蕴含了程序设计的思想。我们用函数方法构造一个可计算函数f(或者等价地构造性证明f是可计算的),就等于对f进行程序设计。Turing-Church论题告诉我们:递归函数类等同于能行可计算函数。而复合运算、原始递归运算和极小化运算发挥了很关键的作用,所以旨在提供通用计算功能的计算机必须实现这些运算。程序设计语言中的许多设施都是为了构造递归函数类而设置的。过程(Procedure)和宏(Macro)为运用复合提供了明显的便利。为了应付复合和原始递归运算的定义,一些程序设计语言,如ALGOL68,还提供了以函数作为输入参数和输出结果的特殊过程。这样复合运算可以解释成提供过程的功能。极小化运算实质上包含了对具有检测出口条件的循环进行程序设计的能力,它使用了结构程序设计中的Do While运算。原始递归运算不像极小化运算的功能那样强,它也包含循环,但在某些情况下,循环次数是预先确定的,这与使用任意检测在每一步确定终止条件是否满足的情形正好相反。使用Do While和FOR循环均可实现原始递归运算。
结构化程序设计最初由Dijkstra提出,1966年G.Jacopini和C.Bohn从理论上证明了:任何单入口、单出口程序仅使用序列、循环和条件三种结构就可以表示出来。这种程序设计方法采用了反复运用几个简单运算的思想,用这三种结构我们可以很方便完成递归函数理论的基本运算(图4、图5、图6),从而可以表达任意计算过程。
图4 序列运算表达的复合
图5 使用“Do While”的极小化 图6 使用“Do while”(定义一个单自变量函数r(x))的原始递归
在原始递归中如果我们开始就知道执行的次数,则完全可以用for循环来实现。由于全函数经过极小化运算后可能不是全函数,即不存在最小y的解,从而结构程序设计中Do While循环可能出现死循环,这在程序设计中要值得注意。利用结构程序设计语言提供的控制机制,我们可以很容易构造出原始函数和复合、原始递归和极小化运算。下面是用C语言函数实现的三个原始函数和三个运算,利用这六个函数可以计算任何可计算函数,完成递归理论的运算。可见程序设计的能力和递归函数理论表达能力是相同的,函数的构造和程序设计是等价的。
◆ 零函数 ◆原始递归运算
int zero(int x) int recursion(int y)
{ {
return(0); int k,h;
} h=f(x1,…,xn);
◆ 后继函数 while(y<>k)
int successor(int x) {
{ h=g(x1,…,xn,k,h);
return(x+1); k++;
} }
◆ 广义单位函数 return(h);
int projection(int i,int x1,int x2,…,int xn) }
{ ◆极小化运算
if(i==1)return(x1); int minmalisation()
if(i==2)return(x1); {
… int y,k;
if(i==n)return(xn); y=0;
} k=f(x1,…,xn,y);
◆ 复合运算 while(k<>0)
int composition() {
{ y++;
int y1,y2,…,yk; k=f(x1,…,xn,y);
y1=f1(x1,…,xn); }
y2=f1(x1,…,xn); return(y);
… }
yk=f1(x1,…,xn);
return(g(y1,…,yk));
}
程序设计是计算机实践的基本活动,而递归思想直接影响了程序设计的构造,它决定了程序设计语言的许多关键技术,从而递归过程、递归程序就十分自然了。简单地说,递归过程就是自身调用自身过程,包含递归过程的程序我们称为递归程序。在程序设计中使用递归算法往往可以简化求解问题的复杂性。如著名的梵天塔问题,这个问题只有用递归方法解决,而不能用其他方法有效求解。用递归过程描述如下:
void Hanoi(int n,char left,char middle,char right)
{
if(n==1)move(1,one,-,three);
else
{
hanoi(n-1,left,right,middle);
move(1,left,-,right);
hanoi(n-1,middle,left,right);
}
}
在编译技术中递归过程使用更为普遍,处理符号表达式递归过程尤为自然,因为这样的程序结构和数据结构相匹配。下面是Pascal语言一个子集TINY语言的文法(图7),从文法中我们可以看到许多递归结构,所以语法分析采用递归下降文法进行分析自然就很简单。我们为每一文法成分构造一个函数,这些函数之间相互调用,直接或间接就构成了递归。
图7 TINY语言的文法
递归程序中的变量与机器中的存储单元对应,当程序被它本身调用时,它将使用相同的存储单元改写它们原先的内容。所以有了栈这样的数据结构,用来存储必须保存的寄存器内容。这一存储在进入子程序前由调用程序完成或在子程序使用前完成。为了增强递归程序设计的效率,现在的计算机体系结构中设有硬件栈数据结构,它由一组寄存器组成,并且用专用的指令push和pop来处理栈操作。由此可见,递归思想在计算机系统中是非常普遍的,它甚至影响了计算机的体系结构。
递归函数理论作为计算机科学的元科学,对计算机实践具有非常重要的意义,而程序设计作为计算机科学基本的实践活动,递归思想直接影响了程序设计语言的构造,也影响了计算机的体系结构。递归是一种普遍的思维机制,这在计算机科学的理论和实践中得到了很广泛的应用,对计算机的发展起了至关重要的作用。
[参考文献]
1、(美)F.S.Beckman 程序设计的数学基础 1991.8 科学出版社
2、(英)J.M.布雷迪 计算机科学理论-程序设计途径 1988.4 科学出版社
3、董荣胜、古天龙 计算机科学与技术方法论 2002.9 人民邮电出版社