给定任意双递归函数,将如何计算其运行时间?
例如(用伪代码):
int a(int x){
if (x < = 0)
return 1010;
else
return b(x-1) + a(x-1);
}
int b(int y){
if (y <= -5)
return -2;
else
return b(a(y-1));
}
或类似的规定。
一个人可以或应该使用什么方法来确定类似的东西?
给定任意双递归函数,将如何计算其运行时间?
例如(用伪代码):
int a(int x){
if (x < = 0)
return 1010;
else
return b(x-1) + a(x-1);
}
int b(int y){
if (y <= -5)
return -2;
else
return b(a(y-1));
}
或类似的规定。
一个人可以或应该使用什么方法来确定类似的东西?
Answers:
您一直在更改功能。但是请继续选择那些无需转换即可永久运行的产品。
递归变得复杂,快速。分析提议的双递归函数的第一步是尝试通过一些样本值对其进行跟踪,以查看其功能。如果您的计算陷入无限循环,则该函数定义不正确。如果您的计算陷入不断增加的螺旋式上升(这很容易发生),则可能定义不明确。
如果通过跟踪找到答案,则可以尝试得出答案之间的某种模式或递归关系。一旦有了它,就可以尝试找出其运行时间。弄清楚它可能非常非常复杂,但是我们得到了诸如Master定理之类的结果,这些结果使我们在很多情况下都能找到答案。
请注意,即使是单次递归,也很容易提出我们不知道如何计算其运行时间的函数。例如,考虑以下内容:
def recursive (n):
if 0 == n%2:
return 1 + recursive(n/2)
elif 1 == n:
return 0
else:
return recursive(3*n + 1)
它是目前未知是否这个功能总是明确定义,更不用说它的运行时间是什么。
该对特定函数的运行时间是无限的,因为两个函数都不返回而没有调用另一个函数。的返回值a
是始终依赖于调用的返回值b
,其始终要求a
......那什么作为的无限递归。
a
仅b
在传入的数字> = 0 时才调用。但是,是的,存在无限循环。
一种明显的方法是运行该功能并测量所需的时间。但是,这仅告诉您进行特定输入需要多长时间。而且,如果您事先不知道该函数会终止,那就太难了:没有机械的方法可以确定该函数是否终止-这是一个停顿的问题,而且还无法确定。
根据赖斯定理,类似地,无法确定函数的运行时间。实际上,赖斯定理表明,即使确定一个函数是否O(f(n))
及时运行也是不确定的。
因此,一般而言,您能做的最好的事情就是利用您的人类智慧(据我们所知,不受图灵机的限制),并尝试识别一种模式或发明一种模式。分析函数运行时间的一种典型方法是将函数的递归定义转换为关于其运行时间的递归方程式(或一组相互递归函数的方程式):
T_a(x) = if x ≤ 0 then 1 else T_b(x-1) + T_a(x-1)
T_b(x) = if x ≤ -5 then 1 else T_b(T_a(x-1))
接下来是什么?您现在遇到了一个数学问题:您需要解决这些函数方程。一种常用的方法是将整数函数上的这些方程式转换为解析函数上的方程式,并使用演算来求解这些函数,解释函数T_a
并将其T_b
作为生成函数。
关于生成函数和其他离散数学主题,我推荐Ronald Graham,Donald Knuth和Oren Patashnik 撰写的《具体数学》一书。
正如其他人指出的那样,分析递归会非常困难。这是这种情况的另一个示例:http : //rosettacode.org/wiki/Mutual_recursion http://en.wikipedia.org/wiki/Hofstadter_sequence#Hofstadter_Female_and_Male_sequences 很难计算出这些答案和运行时间。这是由于这些相互递归的函数具有“困难形式”。
无论如何,让我们来看一个简单的例子:
http://pramode.net/clojure/2010/05/08/clojure-trampoline/
(declare funa funb)
(defn funa [n]
(if (= n 0)
0
(funb (dec n))))
(defn funb [n]
(if (= n 0)
0
(funa (dec n))))
让我们开始尝试计算funa(m), m > 0
:
funa(m) = funb(m - 1) = funa(m - 2) = ... funa(0) or funb(0) = 0 either way.
运行时间是:
R(funa(m)) = 1 + R(funb(m - 1)) = 2 + R(funa(m - 2)) = ... m + R(funa(0)) or m + R(funb(0)) = m + 1 steps either way
现在让我们选择另一个稍微复杂一点的示例:
受到http://planetmath.org/encyclopedia/MutualRecursion.html的启发,它本身很不错,让我们看一下:“”“斐波那契数可以通过相互递归来解释:F(0)= 1和G(0 )= 1,其中F(n + 1)= F(n)+ G(n),而G(n + 1)= F(n)。“”“
那么,F的运行时间是多少?我们将走另一条路。
好吧,R(F(0))= 1 = F(0); R(G(0))= 1 = G(0)
现在R(F(1))= R(F(0))+ R(G(0))= F(0)+ G(0)= F (1)
...
不难看出R(F(m))= F(m)-例如,计算索引i处的斐波那契数所需的函数调用次数等于斐波那契数的值在索引i。假设将两个数字相加比函数调用要快得多。如果不是这种情况,那就是对的:R(F(1))= R(F(0))+ 1 + R(G(0)),对此的分析将更加复杂,可能没有简单的封闭式解决方案。
斐波那契数列的封闭形式不一定很容易被重塑,更不用说一些更复杂的例子了。
首先要做的是显示您定义的函数终止以及确切的值。在示例中,您已定义
int a(int x){
if (x < = 0)
return 1010;
else
return b(x-1) + a(x-1);
}
int b(int y){
if (y <= -5)
return -2;
else
return b(a(y-1));
}
b
仅终止于此,y <= -5
因为如果您插入任何其他值,那么您将具有格式的条款b(a(y-1))
。如果再做一些扩展,您会看到形式b(a(y-1))
的术语最终会导致术语b(1010)
,导致术语b(a(1009))
再次导致术语b(1010)
。这意味着您不能将任何a
不满足的值插入其中,x <= -4
因为如果这样做,您将陷入无限循环,其中要计算的值取决于要计算的值。因此,基本上这个示例具有恒定的运行时间。
因此,简单的答案是,没有通用的方法来确定递归函数的运行时间,因为没有通用的过程来确定递归定义的函数是否终止。
运行时是否像Big-O一样?
这很容易:O(N) -假设存在终止条件。
递归只是循环,而无论您在该循环中执行了多少操作,一个简单的循环都是O(N)(调用另一个方法只是循环中的另一步)。
如果您在一个或多个递归方法中有一个循环,它将变得很有趣。在那种情况下,您将获得某种指数的性能(方法每次通过时乘以O(N))。
O(2^n)
和O(n*log(n))
。
a
调用b
和b
调用a
,所以你不能简单地认为这两种方法需要一定的时间O(1)
。