Posted on 2006-08-28 18:41
樱木 阅读(1300)
评论(1) 编辑 收藏 引用 所属分类:
算法与数据结构
本文从递归函数参数的角度来分析,递归函数调用时递归工作栈中工作记录减肥的可能性。
用最简单的求阶层函数来举例:
n! = n*(n-1)*(n-2)***2*1
常见的递归实现如下:
int fun(int n)
{
if( n<=1 ) return 1;
return n*fun(n-1);
}
不用多说,这个实现下每次递归调用都会在当前层的工作记录中分配一个4字节(IBM兼容机)的空间来存放n的值。若栈的深度为DEPTH,那么一共需要DEPTH×4个字节存放参数。
那么能不能避免这些存储空间呢,我们来看看引用的情况:
int fun(int &n)
{
if( n<=1 ) return 1;
return n*fun(n-1);
}
这段代码编译不能通过,原因是n-1只是一个右值。这里简单介绍一下c++中的无名对象(变量)。c++中的无名对象与const常量有些相似,他们都只能做右值而不能做左值,区别是const常量是有地址的可以取地址,而无名对象一般是不可以的。这里n-1有点无名对象的味道,因为fun要求一个变量引用。由于n-1是右值,所以把参数类型改成int fun(const int & n)就可以了。
int fun(const int &n)
{
if( n<=1 ) return 1;
return n*fun(n-1);
}
现在运行通过了,但是栈空间需求仍然没有减少,因为引用的是无名对象,无名对象只是寄存器中的一个值,是没有RAM地址的。所以每递归调用一次,就会在工作记录中分配空间来存放n-1,实际效果相当于const int n = n-1;
那么换个思路。以上程序之所以如此是因为传给递归函数的不是n本身,现设计如下:
int fun(const int & n)
{
cout<<(long)&n<<endl;
if( n<=1 ) return 1;
--n;
return (n+1)*fun(n);
}
这个程序运行正确而且所有的n确实是最外面的那个n的引用(打印的地址均一致)。有些人会怀疑这个程序不能正确的求出n!。想想也对,当递归不断深入到最里层的时候n=1,递归回退的时候n仍然还是1,那结果不就成了1×2×2×2××××2了么??您可以试试看,结果可能出乎您的预料:计算出的确实是 n! 原因如下:
每调用一层时先计算n+1,n+1的结果保存在register中,进入下一层递归前编译器会把适当的register的值压栈以便日后恢复,这里某个寄存器就存放着运算的中间结果n+1。日后返回该层时虽然此时n确实是1,但是n+1的结果已经计算过,所以只是从栈顶取出n+1而非重新计算。作为思考,您可以试试看将(n+1)*fun(n)改成fun(n)*(n+1),它将得出错误的结果。
看来,递归调用函数使用引用参数和普通使用引用参数的非递归调用函数是有区别的:并不是使用了引用参数后就一定能够节省栈的空间,这取决于具体的递归函数(复杂。。。^_^)
当然,在递归函数中使用引用参数还有一种目的,当然是为了改变实参的数值罗。对于非内部类型的对象而言,使用引用往往可以既节省空间又同时可以修改实参的值,这里就不再举例了。