我明天有一个计算机科学中期,我需要帮助确定这些递归函数的复杂性。我知道如何解决简单的案例,但我仍然在努力学习如何解决这些更难的案例。这些只是我无法弄清楚的一些示例问题。任何帮助将非常感谢,并将大大有助于我的学习,谢谢!
int recursiveFun1(int n)
{
if (n <= 0)
return 1;
else
return 1 + recursiveFun1(n-1);
}
int recursiveFun2(int n)
{
if (n <= 0)
return 1;
else
return 1 + recursiveFun2(n-5);
}
int recursiveFun3(int n)
{
if (n <= 0)
return 1;
else
return 1 + recursiveFun3(n/5);
}
void recursiveFun4(int n, int m, int o)
{
if (n <= 0)
{
printf("%d, %d\n",m, o);
}
else
{
recursiveFun4(n-1, m+1, o);
recursiveFun4(n-1, m, o+1);
}
}
int recursiveFun5(int n)
{
for (i = 0; i < n; i += 2) {
// do something
}
if (n <= 0)
return 1;
else
return 1 + recursiveFun5(n-5);
}
答案 0 :(得分:269)
每个函数的Big O表示法的时间复杂度按数字顺序排列:
O(n)
通常称为线性。O(n)
。
(实际上称为n / 5次的顺序。而且,O(n / 5)= O(n))。O(log(n))
(基数为5),通常称为对数,最常见的是Big O表示法和复杂性分析使用基数2。O(2^n)
或指数,因为每个函数调用都会调用两次,除非它已经递归 n 次。至于最后一个函数,for循环需要n / 2,因为我们正在增加
2,递归取n-5,因为for循环被调用
递归地因此,时间复杂度在(n-5)*(n / 2)=(2n-10)* n = 2n ^ 2~10n,由于渐近行为和最坏情况场景考虑或大O是上限为了争取,我们只关注最大的术语O(n^2)
。
祝你的中期好运;)
答案 1 :(得分:114)
对于n <= 0
,T(n) = O(1)
的情况。因此,时间复杂度取决于n >= 0
。
我们将在下面的部分中考虑案例n >= 0
。
1
T(n) = a + T(n - 1)
其中a是常数。
通过归纳:
T(n) = n * a + T(0) = n * a + b = O(n)
其中a,b是常数。
2
T(n) = a + T(n - 5)
其中a是常量
通过归纳:
T(n) = ceil(n / 5) * a + T(k) = ceil(n / 5) * a + b = O(n)
其中a,b是常数且k <= 0
3
T(n) = a + T(n / 5)
其中a是常量
通过归纳:
T(n) = a * log5(n) + T(0) = a * log5(n) + b = O(log n)
其中a,b是常量
4
T(n) = a + 2 * T(n - 1)
其中a是常量
通过归纳:
T(n) = a + 2a + 4a + ... + 2^n * a + T(0) * 2 ^ n
= a * 2^(n+1) - a + b * 2 ^ n
= (2 * a + b) * 2 ^ n - a
= O(2 ^ n)
其中a,b是常数。
5
T(n) = n / 2 + T(n - 5)
我们可以通过归纳证明T(5k) >= T(5k - d)
其中d = 0,1,2,3,4
写n = 5m - b
(m,b是整数; b = 0,1,2,3,4),然后是m = (n + b) / 5
:
T(n) = T(5m - b) <= T(5m)
(实际上,为了更加严谨,应该定义一个新功能T'(n)
,T'(5r - q) = T(5r)
q = 0, 1, 2, 3, 4
。我们知道T(n) <= T'(n)
如上所述。当我们知道T'(n)
位于O(f)
,这意味着存在常数a,b以便T'(n) <= a * f(n) + b
,我们可以推导出T(n) <= a * f(n) + b
,因此T(n)
位于{ {1}}。这个步骤并不是必需的,但是当你不必处理其余部分时,更容易思考。)
扩展O(f)
:
T(5m)
因此,T(5m) = 5m / 2 + T(5m - 5)
= (5m / 2 + 5 / 2) * m / 2 + T(0)
= O(m ^ 2) = O(n ^ 2)
为T(n)
。
答案 2 :(得分:18)
我发现用于近似递归算法复杂度的最佳方法之一是绘制递归树。一旦你有了递归树:
Complexity = length of tree from root node to leaf node * number of leaf nodes
n
,叶节点的数量为1
,因此复杂度为n*1 = n
第二个函数的长度为n/5
,叶节点的数量将再次为1
,因此复杂度为n/5 * 1 = n/5
。它应该近似为n
对于第三个函数,由于n
在每次递归调用时被除以5,因此递归树的长度将为log(n)(base 5)
,叶节点的数量将再次为1,因此复杂性将为log(n)(base 5) * 1 = log(n)(base 5)
对于第四个函数,因为每个节点都有两个子节点,叶子节点的数量将等于(2^n)
,递归树的长度将为n
,因此复杂性将是(2^n) * n
。但由于n
在(2^n)
之前无关紧要,因此可以忽略它,复杂性只能说是(2^n)
。
对于第五个函数,有两个元素引入复杂性。复杂性由每个函数中for
循环引入的函数和复杂性的递归性质引入。进行上述计算时,由于for循环~ n
,函数的递归性质引入的复杂性将为n
和复杂性。总复杂度为n*n
。
注意:这是计算复杂性的快速而肮脏的方法(没有官方!)。很想听听有关这方面的反馈。感谢。
答案 3 :(得分:11)
我们可以在数学上证明它,而上述答案正是我所缺少的。
它可以戏剧性地帮助您了解如何计算任何方法。 我建议从头到尾阅读它,以完全了解该怎么做:
T(n) = T(n-1) + 1
这意味着该方法完成所需的时间等于相同的方法,但是n-1为T(n-1)
,我们现在添加+ 1
是因为它是完成一般操作所需的时间(T(n-1)
除外)。
现在,我们将找到T(n-1)
,如下所示:T(n-1) = T(n-1-1) + 1
。看来我们现在可以形成一个可以给我们某种重复的函数,以便我们可以完全理解。我们将T(n-1) = ...
的右侧而不是T(n-1)
放在方法T(n) = ...
内,这将为我们提供:T(n) = T(n-1-1) + 1 + 1
即T(n) = T(n-2) + 2
或换句话说,我们需要查找丢失的k
:T(n) = T(n-k) + k
。下一步是采用n-k
并声明n-k = 1
,因为在递归结束时,当n<=0
时它将精确地为O(1)。从这个简单的方程式,我们现在知道k = n - 1
。让我们将k
放入我们的最终方法中:T(n) = T(n-k) + k
,它将为我们提供:T(n) = 1 + n - 1
恰好是n
或O(n)
。O(n)
。T(n) = T(n/5) + 1
和以前一样,此方法完成的时间等于相同方法但使用n/5
的时间,这就是它绑定到T(n/5)
的原因。让我们像在T(n/5)
中的T(n/5) = T(n/5/5) + 1
中那样找到T(n/5) = T(n/5^2) + 1
。
让我们将T(n/5)
放在T(n)
内进行最终计算:T(n) = T(n/5^k) + k
。再次像以前一样,n/5^k = 1
(即n = 5^k
)恰好是问5的幂次,将得到n,答案为log5n = k
(以5为底的对数)。让我们将调查结果放在T(n) = T(n/5^k) + k
中,如下:T(n) = 1 + logn
,即O(logn)
T(n) = 2T(n-1) + 1
这里基本上与以前相同,但是这次我们递归调用该方法2次,因此我们将其乘以2。让我们找到T(n-1) = 2T(n-1-1) + 1
,即T(n-1) = 2T(n-2) + 1
。我们之前的下一个地方,就是我们的发现:T(n) = 2(2T(n-2)) + 1 + 1
,这是T(n) = 2^2T(n-2) + 2
,为我们提供了T(n) = 2^kT(n-k) + k
。我们通过声明k
为n-k = 1
来找到k = n - 1
。让我们将k
放置如下:T(n) = 2^(n-1) + n - 1
,大致为O(2^n)
T(n) = T(n-5) + n + 1
与4几乎相同,但是现在我们添加n
,因为我们有一个for
循环。让我们找到T(n-5) = T(n-5-5) + n + 1
的{{1}}。让我们放置它:T(n-5) = T(n - 2*5) + n + 1
,它是T(n) = T(n-2*5) + n + n + 1 + 1)
;对于k:T(n) = T(n-2*5) + 2n + 2)
,再次:T(n) = T(n-k*5) + kn + k)
,它是n-5k = 1
,大约是n = 5k + 1
。这将为我们提供:n = k
,大致为T(n) = T(0) + n^2 + n
。我现在建议阅读其余的答案,这将使您有一个更好的视角。 赢得这些大O的祝你好运:)
答案 4 :(得分:4)
此处的关键是可视化调用树。完成后,复杂度为:
nodes of the call tree * complexity of other code in the function
可以使用与普通迭代函数相同的方式来计算后一项。
相反,完整树的总节点计算为
C^L - 1
------- , when C>1
/ C - 1
/
# of nodes =
\
\
L , when C=1
其中C是每个节点的子代数,L是树的级别数(包括根)。
很容易将树形象化。从第一个调用(根节点)开始,然后绘制与该函数中的递归调用数相同的子级数。将传递给子调用的参数写为“节点的值”也很有用。
因此,在上面的示例中:
n level 1 n-1 level 2 n-2 level 3 n-3 level 4 ... ~ n levels -> L = n
n n-5 n-10 n-15 ... ~ n/5 levels -> L = n/5
n n/5 n/5^2 n/5^3 ... ~ log5(n) levels -> L = log5(n)
n level 1 n-1 n-1 level 2 n-2 n-2 n-2 n-2 ... n-3 n-3 n-3 n-3 n-3 n-3 n-3 n-3 ... ... ~ n levels -> L = n
n n-5 n-10 n-15 ... ~ n/5 levels -> L = n/5
答案 5 :(得分:0)
我看到对于已接受的答案 (recursivefn5),有些人对解释有疑问。所以我会尽我所知尽量澄清。
for 循环运行 n/2 次,因为在每次迭代中,我们将 i(计数器)增加 2 倍。所以说 n = 10,for 循环将运行 10/2 = 5次,即当 i 分别为 0、2、4、6 和 8 时。
同样,递归调用每次被调用时都会减少 5 倍,即运行 n/5 次。再次假设 n = 10,递归调用运行 10/5 = 2 次,即当 n 为 10 和 5 时,它遇到基本情况并终止。
计算总运行时间,for 循环每次调用递归函数都会运行 n/2 次。由于递归 fxn 运行 n/5 次(在上面的 2 中),for 循环运行 (n/2) * (n/5) = (n^2)/10 次,这转化为整体 Big O 运行时O(n^2) - 忽略常数 (1/10)...