36-1 用递归和迭代解决问题
1、求n的阶乘
公式:
n!=1×2×3×...×(n-1)×n。用递归方式定义:0!=1,n!=(n-1)!×n。
代码1:
我们先回忆一下之前用循环怎么实现的吧
非递归,也可称迭代:
int main()
{
int n = 0;
scanf("%d", &n);
int i = 1;
int ret = 1;
for (i = 1; i <= n; i++)
{
ret = i * ret;
}
printf("%d\n", ret);
return 0;
}
运行结果:
代码2:
递归
int fac(num)
{
if (num > 1)
{
return num * fac(num - 1);
}
else
{
return 1;
}
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = fac(n);
printf("%d\n", ret);
return 0;
}
运行结果:
有些时候,并不适合用递归
2、求第n个斐波那契数
斐波那契数列:在数学上,斐波那契数列可以通过递推的方式定义,从第三项开始,每一项都等于前两项之和。具体的递推公式为F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2),其中n≥2且n属于自然数集。
代码1:递归方法
int F(n)
{
int ret = 0;
if (n >= 2)
{
return F(n - 1) + F(n - 2);
}
else if (0 == n)
{
return 0;
}
else
{
return 1;
}
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = F(n);
printf("%d\n", ret);
return 0;
}
运行结果:
但是,这里会有大量重复的计算!严重浪费时间
我们可以进行一个测试:
int count = 0; //计数器
int F(n)
{
int ret = 0;
if (n == 3)
{
count++; //计数,一共算了多少次第三个斐波那契数
}
if (n >= 2)
{
return F(n - 1) + F(n - 2);
}
else if (0 == n)
{
return 0;
}
else
{
return 1;
}
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = F(n);
printf("%d\n", ret);
printf("count=%d\n", count);
return 0;
}
运行结果:
竟然算了将近4万次!!!
效率太低了,用递归太不合适啦!我们试试迭代吧~~~
代码2:迭代
①for循环
int Fib(n)
{
if (n >= 2)
{
int sum = 0;
int i = 0;
int n1 = 1; //第一个数
int n2 = 1; //第二个数
for (i = 0; i < n-2; i++) //直接从第3个算起
{
sum = n1 + n2;
n1 = n2;
n2 = sum;
}
return sum;
}
else if(0==n)
{
return 0;
}
else
{
return 1;
}
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fib(n);
printf("%d\n", ret);
}
②while循环(该代码默认n>=1)
int Fib(n)
{
int n1 = 1;
int n2 = 1;
int n3 = 1;
while (n >= 3)
{
n3 = n1 + n2;
n1 = n2;
n2 = n3;
n--;
}
return n3;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = Fib(n);
printf("%d\n", ret);
}
再使用这两个代码进行测试,你会发现代码运行速度明显比递归的速度快
注意:如果用特别大的数测试,可能会得到负值,这是因为溢出了,关于溢出的问题这里不进行展开
36-2 权衡递归与迭代的使用
1、许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
2、但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
3、当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。
4、如果使用递归没有出现明显的问题,递归可以使用;但是如果有类似的明显问题,则采用非递归的方法解决。
5、递归的层次太深,会出现栈溢出的现象。
应对策略:
①将递归改写成非递归
②使用static对象替代nonstatic局部对象。在递归函数设计中,可以使用static对象替代nonstatic局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放nonstatic对象的开销,而且static对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。
当然,这只是一些可能的解决方案,并不一定真的能够解决栈溢出的问题