计算给定递归函数的精确运行时间(时间复杂度)

Posted

技术标签:

【中文标题】计算给定递归函数的精确运行时间(时间复杂度)【英文标题】:Calculate exact runtime (time complexity) given recursive functions 【发布时间】:2022-01-08 03:26:46 【问题描述】:

考虑这个计算double x的幂的代码:

public static double F1 (double x, int k)
    if (k==1)  return x;  // O(1)
    return x * F1(x, k-1);  // O(k)

我的结论是

if (k==1) return x; 中的操作数:2 个操作,if-statement 和 return-statement。因此,T(1) = 2

return x * F1(x, k-1); 中的操作数:4 次操作,return-statement = 1,*-operator = 1,F1(x, k-1); = 2。所以等式的第一部分 = 4

我们在x * F1(x, k-1), 中有一个递归调用,所以x = 1

我们在每个递归调用中将问题减少 1,因此 y = k-1。所以方程的第二部分 = T(k-1)

将所有这些放在一起,我们得到:

4 + T(k-1), T(1) = 2

但是我如何从这里开始找到确切的运行时?

我试图查看this 问题的解释,但它侧重于如何计算 Big-O 表示法,而不是精确时间复杂度。如何继续找到确切的时间复杂度?

这里的答案应该是:

Exact: 4k-2 
Tilde: 4k 
Big-O: O(k)

但我不知道他们做了什么。

【问题讨论】:

我不知道你所说的“波浪号”是什么意思。你能提供解释的引文吗? (请注意,不管它是什么意思,它与[tilde]逻辑标签的主题无关......所以我把它删除了。) 【参考方案1】:

对于您执行的第一个k-1 步骤:

    比较k==1 减法k-1 产品x * ... return 指令

在您执行的最后一步中:

    比较k==1 return 指令

所以你有4*(k-1)+2 = 4k-2 的总体说明。

编辑:正如@rzwitserloot 正确指出的那样,您搜索的数量不是很重要,但这取决于代码的编译和执行方式。上面我只是想弄清楚你的老师所说的“精确时间复杂度”是什么意思。

【讨论】:

在字节码级别,这些数学都没有结果。即使您尝试计算字节码步数,每条字节码指令的执行时间也会非常变化。整个分析完全没有意义。【参考方案2】:

但是我如何从这里开始找到确切的运行时?

您将迄今为止所做的所有事情都扔进垃圾箱,然后启动 JMH,稍后再查看更多信息。

完全不可能根据这样的学术分析来确定确切的运行时间。确切的运行时间取决于您的音乐播放器中正在播放的歌曲、您的操作系统是否忙于进行磁盘清理、向网络时间服务器发送 ping、哪些页面碰巧在片上缓存上、您的代码的 CPU 核心最终被运行,以及月相。

让我尽可能清楚地说:像4k - 2 这样的东西完全不相关且被误导 - 这不是计算机的工作方式。您不能说具有“精确运行时间”4k - 2 的算法会比6k + 2 算法更快。它同样可能更慢:它的预测能力为零。这是一个完全没有意义的“计算”。这不代表任何意思。 big-O 表示法存在是有原因的:这确实意味着无论硬件是否变幻莫测:给定 2 种算法,其中一种算法比另一种具有“更好”的 big-O 表示法,那么存在一些输入大小使得更好的算法将是更快,不管硬件问题。这可能是一个非常大的数字,而 big-O 不会告诉您发生这种情况的数字是多少。

big-O 表示法的意义在于,它以数学上的确定性规定了如果您更改算法输入的大小,最终会发生什么,非常宽泛。这就是为什么在显示大 O 表示法时要删除所有常量和除最大因子之外的所有内容。

画一张图;在 X 轴上,有“输入大小”,即O(k) 中的“k”。在 Y 轴上,有执行时间(或者,如果您愿意,最大内存负载)。然后,弥补一些输入大小并运行您的算法几次。平均结果,并在该图上放置一个点。例如,如果您在 k=5 的输入上运行算法,平均需要 27 毫秒,请在 x=5, y=27 上放置一个点。

继续。很多点。最终这些点形成了一个图形。该图将在 x=0 点附近遍布整个地方。就好像一个喜欢随机性的醉汉正在向棋盘扔飞镖一样。

但是,最终(当“最终”出现时无法确定,因为这又取决于很多操作系统的东西,不要费心去预测这些东西),它会开始看起来像一个可识别的形状。我们用简单的公式定义这些形状。例如,如果它最终(足够靠右)合并成看起来像绘制y=x^2 所得到的东西,那么我们称之为O(x^2)

现在,y=5x^2 看起来与 y=x^2 完全一样。就此而言,y=158*x^2 + 25000x + 2134931239,如果您在该曲线上向右看足够远,看起来与y=x^2 完全一样。因此,为什么O(158x^2+20x) 完全没有抓住重点,因此是不正确的。 O 的意义只是告诉你它“向右足够远”会是什么样子。

这给我们留下了正好 2 个有用的性能指标:

    O(k) 符号。您在此处正确确定:此算法具有 O(k) 运行时。

    时间报告。 没有意义试图通过查看代码来解决这个问题,您需要运行代码。反复地,周围有各种保护措施,以确保热点优化不会完全消除您的代码,重新运行很多次以获得良好的平均值,并确保我们通过了 JVM 的 JIT 步骤。您使用 JMH 来执行此操作,并注意 JMH 的结果自然取决于您运行它的硬件,这是因为程序的性能特征可能因硬件而异。

【讨论】:

以上是关于计算给定递归函数的精确运行时间(时间复杂度)的主要内容,如果未能解决你的问题,请参考以下文章

第六章 函数和宏定义实验

第六章 函数和宏定义实验

计算递归函数的时间复杂度

给定递归算法解决递归关系并给出最坏情况下的时间复杂度,这是正确的吗?

第六章函数与宏定义实验报告

实验报告(2019年4月30日)下半部分