由斐波那契数列(Fibonacci)谈递归(Recursion)内部实现

2 篇文章 0 订阅
2 篇文章 0 订阅

一般说到递归,很多人第一时间可能都会想到斐波那契数列(Fibonacci),(注:斐波纳契,又称黄金分割数列,指的是这样一个数列:1、1、2、3、5、8、13、21、……数学上,斐波纳契数列以如下被以递归的方法定义:F0=0,F1=1,Fn=F(n-1)+F(n-2)(n>=2,n∈N*))。在C中用递归实现就很简单了,如下代码:

int Fbi_Rec(int n)
{
	if (n < 2)
	    return n == 0 ? 0:1;
	return Fbi_Rec(n-1)+Fbi_Rec(n-2);
}

接下来通过反编译来看看C内部具体是如何实现递归的:(贴出主要汇编代码,//后为个人加上的注释)
Fbi:
.LFB0:
	.cfi_startproc
	pushl	%ebp                //将ebp(堆栈数据指针)寄存器的值压入栈,esp=esp-ox10后面要用
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp          //将ebp=esp
	.cfi_def_cfa_register 5
	pushl	%ebx                //将ebx入栈,esp=esp-ox10,ebx保存函数返回值
	subl	$20, %esp           //esp=esp-ox20(ox20十六进制,因为前面pushl了两次
	cmpl	$1, 8(%ebp)         //ss:[ebp+8]对应我们的变量n(int型8bit),这里即比较n与1,对应C中的n<2
	jg	.L2                 //jg:如果n>1就跳转到.L2
	.cfi_offset 3, -12
	cmpl	$0, 8(%ebp)         //比较n与0
	setne	%al                 //接下来容易理解,就是n==0?0:1,这里编译器已经做了优化
	movzbl	%al, %eax           //eax保存返回值
	jmp	.L3                 //跳转.L3,主要是返回
.L2:                                //函数递归主要看这里
	movl	8(%ebp), %eax       //eax=n
	subl	$1, %eax            //n-1
	movl	%eax, (%esp)        //ss:[esp]=eax
	call	Fbi                 //调用Fbi段,最后在这里会跳出,整个函数返回,因为最后一步是F(3)=F(2)+F(1)
	movl	%eax, %ebx          //ebx=eax,保存Fbi(n-1)的返回值到ebx
	movl	8(%ebp), %eax       //eax保存Fbi(n-1)的返回值
	subl	$2, %eax            //n-2
	movl	%eax, (%esp)        //n=n-2
	call	Fbi                 //调用Fbi
	addl	%ebx, %eax          //Fbi(n-1)+Fbi(n-2)
.L3:
	addl	$20, %esp   
	popl	%ebx
	.cfi_restore 3
	popl	%ebp
	.cfi_def_cfa 4, 4
	.cfi_restore 5
	ret
	.cfi_endproc

通过上面的汇编代码我们可以更加深入的理解:
  c实现递归是通过将每一层递归用到的函数局部变量,参数值以及返回地址压入栈中,退回时再送出。
  因为递归用到了堆栈,就要考虑堆栈溢出的问题,X86 32位机堆栈最大能保存2^32bit.
递归的效率
接下来我将递归改成循环实现Fibonacci,代码如下:

int Fbi_Loop(int n)
{
	int i, pre_one, pre_two, cur;
	pre_two = 0;
	pre_one = 1;
	if (n < 2)
		return n == 0?pre_two:pre_one;
	i = 2;
	while(i<=n)
	{
		cur = pre_two + pre_one;
		pre_two = pre_one;
		pre_one = cur;
		i++;
	}
	return cur;
}

在我的机子上比较了下两种方式的效率:(取n=40,gprof统计结果)
 %   cumulative   self              self     total           
 time   seconds   seconds    calls   s/call   s/call  name    
100.00      2.58     2.58        1     2.58     2.58  Fbi_Rec
  0.00      2.58     0.00        1     0.00     0.00  Fbi_Loop
可见递归的效率很低,递归写法的两个缺点就是
(1) 递归算法解题通常显得很简洁,但递归算法解题的运行效率较低。所以一般不提倡用递归算法设计程序。
(2) 在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等。所以一般不提倡用递归算法设计程序。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值