递归转换为迭代方式实现对计算效率的影响
什么是递归
一句话解释递归:自己调用自己
eg.
define function(x)
{
if x ... {
return ...
} else {
return function(x...)...
}
}
举例:阶乘、斐波拉契数列
define Factorial(x)
{
if x < 1 {
return error
} else if x == 1 {
return 1
} else {
return x * Factorial(x-1)
}
}
define Fibonacci(x)
{
if x < 1 {
return error
} else if x == 1 || x == 2 {
return 1
} else {
return Fibonacci(x-1) + Fibonacci(x-2)
}
}
什么是迭代
一句话解释迭代:每次结果都是由上次结果进行相同的运算得到
eg.
define function(res, count, x)
{
if count, x ... {
return res
} else {
return function(res ..., count ..., x)
}
}
其调用需要给出初始态,由初始态向后计算达到需要的状态。由于初始态固定,其调用时res及count为固定值,所以大多时候都会选择做进一步封装。
举例:阶乘、斐波拉契数列
define factorial(res, count, x)
{
if x < 1 {
return error
} else if count < x {
return factorial(count * res, count + 1, x)
} else {
return res
}
}
define fibonacci(res, res_1, count, x)
{
if x < 1 {
return error
} else if x == 1 || x == 2 {
return 1
} else if count < x {
return fibonacci(res + res_1, res, count + 1, x)
} else {
return res
}
}
其调用方式为factorial(1, 1, x)和fibonacci(1, 1, 2, x),进一步封装为
define Factorial(x)
{
return factorial(1, 1, x)
}
define Fibonacci(x)
{
return fibonacci(1, 1, 2, x)
}
递归方式与迭代方式的计算效率对比
对比计算效率的差异,最简单的方式当然是实际跑代码了。
废话少说,show you my code。这里使用python,以斐波拉契数列为例,比较两种方式计算耗时。
递归方式及迭代方式的实现如下:
def Recursion(x):
if x < 1:
return None
elif x == 1 or x == 2:
return 1
else:
return Recursion(x-1) + Recursion(x-2)
def iteration(result, result_1, count, x):
if x < 1:
return None
elif x == 1 or x == 2:
return 2
elif count < x:
return iteration(result + result_1, result, count+1, x)
else:
return result
def Iteration(x):
return iteration(1, 1, 2, x)
测试函数:
def test(msg, func, x, times):
start = time.clock()
for index in range(times):
func(x)
end = time.clock()
print('Computing the %dth number of Fibolacci, %s running [%7d] times, time cost: %s Seconds'%(x, msg, times, end-start))
测试结果:
>>>test("Recursion",Recursion,30,1)
>Computing the 30th number of Fibolacci, Recursion running [ 1] times, time cost: 0.1959006259738926 Seconds
>>>test("Iteration",Iteration,30,1000)
>Computing the 30th number of Fibolacci, Iteration running [ 1000] times, time cost: 0.0047303810543576075 Seconds
>>>test("Recursion",Recursion,40,1)
>Computing the 40th number of Fibolacci, Recursion running [ 1] times, time cost: 23.777185128182722 Seconds
>>>test("Iteration",Iteration,40,1000)
>Computing the 40th number of Fibolacci, Iteration running [ 1000] times, time cost: 0.0064214635387642716 Seconds
由于两种实现方式在计算时间上的巨大差异,测试函数里面写了计算n次,迭代方式计算1000次计算总时间,不然数量级相差太大不好比较。python毕竟是使用解释器的脚本语言,用编译语言可能会更快一点。
计算效率差距为何如此之大
刚才实际跑代码看了一下他们的耗时差距,在运算第30位时其实耗时还比较少,没什么明显的感觉。但是运算第40位的时候递归方式使用了二十多秒才计算出来,等待这么久才有输出,都不需要认真做对比。那到底是什么导致了它们的差距会这么大呢?
我们可以从代码实现上来分析一下原因。
递归方式
递归方式的实现为函数为Recursion,我们看一下Recursion的时间复杂度: 函数内部的运算时间复杂度为O(1),加上函数跳转消耗的时间远大于这几个运算消耗的时间,我们其实只需要关注发生了几次递归调用就可以了。在count < x的条件判断分支,可以看到发生了两次递归调用,每多计算一轮需要多发生两次递归调用,所以这个函数的时间复杂度应该是O(2^n)。时间复杂度呈指数增长,如果用它来计算斐波拉契数列的第50位,我们很有可能会被耗死在电脑前。
迭代方式
迭代方式的实现为函数为Iteration,是对iteration的二次封装,那我们也一样,分析一下iteration的时间复杂度: 同上,函数内部的运算时间复杂度为O(1),我们一样只需要关注发生了几次递归调用。在count < x的条件判断分支,可以看到发生了一次递归调用,每多计算一轮需要多发生一次递归调用,所以这个函数的时间复杂度是O(n)。时间复杂度呈线性增长,很明显优于递归方式的指数增长。
那分析完复杂度,其实就很明白了,为什么我要用斐波拉契数列举例,而不用阶乘。因为阶乘的递归实现也只发生了一次递归调用,和迭代实现一样,时间复杂度也为O(n)。所以如果用阶乘举例是看不到什么优化效果的。如果有人有兴趣,也可以自己写代码对比一下阶乘的两种实现方式是不是真的没有太大差异。
同时,分析完复杂度,也能很清晰的明白,当需要我们使用递归的时候,什么情况下需要对递归进行迭代方式的优化,什么时候不需要。
当发生高次递归调用的时候,可以将实现方式切换为迭代,使方法的时间复杂度由指数级优化到线性级;而在一次递归的情况下,就可以不做额外转换,毕竟递归方式的代码可读性实在是比迭代方式友好太多了。