我搜索过高和低,似乎找不到很多与运行时复杂性,递归和java相关的资料。
我目前正在学习Algorithms类中的运行时复杂性和Big-O表示法,而且我在分析递归算法时遇到了麻烦。
private String toStringRec(DNode d)
{
if (d == trailer)
return "";
else
return d.getElement() + toStringRec(d.getNext());
}
这是一个递归方法,它将简单地迭代一个双向链表并打印出元素。
我唯一能想到的是它的运行时复杂度为O(n),因为递归方法调用的数量将取决于DList中的节点数,但我仍然没有对这个答案感到满意。
我不确定是否应该考虑添加d
和d.getNext()
。
或者我完全偏离轨道并且运行时复杂性是不变的,因为它所做的只是从DNodes
中的DList
检索元素?
答案 0 :(得分:3)
乍一看,这看起来像tail recursion modulo cons的经典案例,是尾调的概括。它等效于具有迭代次数的循环。
然而,事情并非如此简单:这里的棘手问题是将d.getElement()
添加到不断增长的字符串中:这本身就是线性操作,并且重复N
1}}次。因此,函数的复杂性为O(N^2)
。
答案 1 :(得分:2)
如果T(n)是基本操作的数量(在这种情况下 - 当我们进入函数体时,内部的任何行最多执行一次,除第二次返回之外的所有行都不是O(1) )通过在n个元素的列表上调用toStringRec来执行,然后
T(0) = 1 - as the only things that happen is the branch instruction and a
return
T(n) = n + T(n-1) for n > 0 - as the stuff which is being done in the
function besides calling toStringRec is some constant time stuff and
concatenating strings that takes O(n) time; and we also run
toStringRec(d.getNet()) which takes T(n-1) time
此时我们已经描述了算法的复杂性。我们现在可以计算T的闭合形式,T(n)= O(n ** 2)。
答案 2 :(得分:2)
这是一个非常简单的例子,但诀窍是定义一个递归关系,它是较小输入大小的给定输入大小的运行时的函数。对于这个例子,假设在每个步骤完成的工作需要恒定的时间C并假设基本情况不起作用,那就是:
T(0) = 0
T(n) = C + T(n-1)
然后,您可以使用替换来解决运行时间以查找系列:
T(n) = C + T(n-1) = 2C + T(n-2) = 3C + T(n-3) = ... = nC + T(n-n) = nC + 0 = nC
根据O的定义,该等式为O(n)。这个例子并不是特别有趣,但如果你看一下类似mergesort的运行时或其他分而治之的算法,你可以更好地了解递归关系。
答案 3 :(得分:0)
该算法的运行时复杂度为O(n),如您所示。您的列表中包含n个项目,算法将为每个项目执行近乎固定的工作量(该工作是元素和下一个访问,以及新的toStringRec调用)。从DNode中检索元素需要恒定的时间,并且以big-O表示法丢弃常量时间。
关于递归方法(在大多数情况下)的有趣之处在于它们在空间复杂度方面也是O(n)。为每次调用toStringRec创建一个新的堆栈条目(用于存储传递给该方法的参数),该调用被称为n次。
答案 4 :(得分:0)
对于这样的递归算法,通常可以编写递归方程来计算顺序。习惯上显示用T(n)执行的指令数量。在这个例子中,我们有:
T(n)= T(n-1)+ O(1)
(假设函数getElement
在恒定时间内运行。)这个等式的解决方案通常是T(n)= O(n)。
这是一般情况。但是,有时您可以在不编写此类公式的情况下分析算法。在这个例子中,您可以轻易地争辩说每个元素最多只能被访问一次,并且每次完成一些恒定时间的工作;所以,整体工作需要O(n)。