【发布时间】:2019-01-19 01:55:06
【问题描述】:
int recursiveFunc(int n) {
if (n == 1) return 0;
for (int i = 2; i < n; i++)
if (n % i == 0) return i + recursiveFunc(n / i);
return n;
}
我知道复杂度 = 从根节点到叶节点的树的长度 * 叶节点的数量,但很难得出一个等式。
【问题讨论】:
int recursiveFunc(int n) {
if (n == 1) return 0;
for (int i = 2; i < n; i++)
if (n % i == 0) return i + recursiveFunc(n / i);
return n;
}
我知道复杂度 = 从根节点到叶节点的树的长度 * 叶节点的数量,但很难得出一个等式。
【问题讨论】:
这很棘手,因为运行时高度依赖于您提供的数字作为输入,而大多数递归函数都没有。
对于初学者,请注意这个递归的工作方式,它接受一个数字,然后是一个
这意味着在一种情况下,在数字 n 上调用的函数将执行 Θ(n) 工作并且不进行任何调用(如果数字是素数会发生这种情况),而在另一种情况下将执行 Θ(d ) 工作,然后对数字 n / d 进行递归调用,如果 n 是复合数并且是 n 的最大除数,则会发生这种情况。
我们将用来分析这个函数的一个有用的事实是,给定一个合数 n,n 的最小因子 d 永远不会大于 √n。如果是,那么对于其他因子 f,我们将有 n = df,并且由于 d 是最小的适当除数,我们将有 f ≥ d,所以 df > √n √ n = n,这是不可能的.
考虑到这一点,我们可以争辩说这个函数的最坏情况运行时间是 O(n),事实上,当 n 是素数时会发生这种情况。这是如何看待这个的。想象一下这个函数最终进行递归调用时可能花费的最坏情况。在这种情况下,该函数最多会做 Θ(√n) 的工作(假设我们的最小除数尽可能大),然后递归调用一个大小最多为 n / 2 的数字(这是绝对作为递归调用的一部分,我们可以得到的最大数。在这种情况下,我们会在我们做尽可能多的工作的悲观假设下得到这种递归关系
T(n) = T(n / 2) + √n
根据主定理,这解决了 Θ(√n),这比我们将素数作为输入时所做的工作更少。
但是,如果相反,我们在一定次数的迭代中尽可能多地工作,然后以质数结束并停止,会发生什么?在这种情况下,使用迭代方法,我们会看到所做的工作将是
n1/2 + n1/4 + ... + n / 2k,
如果我们在 k 次迭代后停止,就会发生这种情况。在这种情况下,请注意,当我们选择尽可能小的 k 时,这个表达式被最大化——这对应于尽快停止,如果我们为 n 选择一个素数,就会发生这种情况。
所以从这个意义上说,这个函数的最坏情况运行时间是 Θ(n),这发生在 n 是素数时,合数的终止速度比这快得多。
那么这个函数能有多快呢?好吧,例如,假设我们有一个 pk 形式的数,其中 p 是某个素数。在这种情况下,这个函数将做 Θ(p) 的工作来发现 p 作为一个素因数,然后在数字 pk-1 上递归地调用它自己。如果你想想这会是什么样子,这个函数最终会做 Θ(p) 工作 Θ(k) 次,总运行时间为 Θ(pk)。因为 n = pk,我们有 k = logp n,所以运行时间是 Θ(p logp n) .这在 p = 2 或 p = 3 时最小化,在这种情况下,无论哪种情况,我们的运行时间都是 Θ(log n)。
我强烈怀疑这是最好的情况,尽管我不完全确定。但这确实意味着
【讨论】: