递归
若一个对象部分的包含自己,或用他自己给自己定义,被称为递归。
简而言之,就是自己调用自己。
问大家一个问题,你认为可以无限递归吗?
首先,递归调用会进行入栈和栈帧回退,消耗栈空间;其次,我们栈空间有限,Win下的VS2008编译器默认堆栈分配大小是1M,当栈空间不足时,会产生栈溢出错误。
最后,它不同于死循环,比如while(1),会一直占用CPU,直至操作系统时间片用完,会由执行状态转换到就绪态。
接下来先看一段代码
struct Node
{
int m;
Node node;
//Node* node1;
};
int main()
{
Node n;
return 0;
}
在你看来,这段代码可以运行吗?
答案是否定的,Node node;这行代码会造成程序无限递归下去,编译都无法通过。
编译器会提示“不允许使用不完整的类型”,从根本上杜绝了你的错误,因为我们在设计时,类中不能包含自己的实体。一般都会用注释那段代码,Node* node1。
斐波拉契数列在c语言中算是比较经典的,有递归和非递归实现方法,
斐波拉契数列,例1, 1, 2, 3, 5, 8, 13, 21…
//递归实现
int Fac(int n)
{
if(n == 1||n == 2)
return 1;
else
return Fac(n-1)+Fac(n-2);
}
递归下来,经测试,n=10时,Fac(10) = 55,需要函数调用109次;
n=20时,Fac(20) = 6765,需要函数调用13529次。
//非递归
int Fac1(int n)
{
int a = 1;
int b = 1;
int c = 1;
if(n <= 2)
return a;
for(int i = 3;i <= n;++i)
{
a = b + c;
b = c;
c = a;
}
return a;
}
非递归下来,相当于迭代,时间复杂度是O(n),效率高。
两种方法对比一看,递归实现效率太低,很多步重复计算,n越大,重复计算量呈指数增长,所以一般斐波拉契数列不建议使用这种递归方法。
如果我们这里把非递归实现中的循环改为递归,情况会怎么样呢?
//循环改为递归
int fac(int a,int b,int n)
{
if(n<=2)
return a;
else
return fac(a+b,a,n-1);
}
int fun(int n)
{
int a=1,b=1;
return fac(a,b,n);
}
测试数据后,效率和非递归实现相同,时间复杂度是O(n)。
还有一个问题是,我们一般输出数组元素,简单地循环输出就可以,如果改成递归输出,会是怎么样呢?
//n是数组br长度
void Print(int *br,int n)
{
if(n > 0)
{
cout<<br[n-1]<<" ";
Print_Array(br,n-1);
}
}//34,23,12
void Print_Array1(int *br,int n)
{
if(br == NULL || n < 1)//需进行参数检查
return;
Print(br,n);
}
int main()
{
int arr[]={12,23,34};
int n = sizeof(arr)/sizeof(arr[0]);
Print_Array(arr,n);
return 0;
}
这样输出后,数组元素是逆序的,递归调用是先输出数组元素,然后函数压入栈,递归条件结束,最后栈回退。我们这里可以将输出和函数调用顺序替换,这样就会顺序输出数组元素。
void Print1(int *br,int n)
{
if(n > 0)
{
Print1(br,n-1);
cout<<br[n-1]<<" ";
}
}
将上边这段代码稍加修改,n-1处改为–n,结果会是什么?
void Print2(int *br,int n)
{
if(n > 0)
{
Print2(br,--n);
cout<<br[n-1]<<" ";
}
}
看到--n,不由地就想到了前置操作及特性,递归进行到结束条件,输出br[n-1]会造成数组越界,输出br[-1],是个随机数,接着是数组前两个元素。
再加修改,把–n改成n–,又会怎么样呢?相信这会的已经被各种–整懵了,那就猜一下结果。
void Print3(int *br,int n)
{
if(n > 0)
{
Print3(br,n--);
cout<<br[n-1]<<" ";
}
}
编译器在处理后置时:是将值放入栈中,在输出时直接出栈就行;
在处理前置时:是等运算完成后,直接从n的地址中取值。
这段代码运行会奔溃,出现栈溢出错误。后置–会在函数调用完后执行–,n一直等于3,会无限递归下去,直至栈空间不足,出现栈溢出,程序奔溃。
引用是经常会使用到的,这里对代码再加修改,参数列表中进行引用n。
void Print4(int *br,int &n)
{
if(n > 0)
{
Print4(br,--n);
cout<<br[n]<<" ";
}
}
结果是12 12 12,先是会进行--操作,然后递归调用,递归条件结束时,n=0,这里是引用n,是n的地址,输出br[n]会连续输出br[0]。
如果这里是后置–,程序会编译无法通过。因为内置类型产生的临时量放在寄存器中,具有常性,所以是不能改变的,不能使用普通的引用。
(只是平时学习的一些总结,如果有什么错误或者问题,大佬们可以指点留言,互相交流学习嘛,谢谢支持!)
本文探讨了递归与非递归的概念及其在编程中的应用。通过对比斐波那契数列的不同实现方式,展示了递归与非递归方法的时间复杂度差异,并通过实例解释了递归输出数组元素时的注意事项。
1770

被折叠的 条评论
为什么被折叠?



