【问题标题】:BIG O In absence of enough informationBIG O 缺乏足够的信息
【发布时间】:2015-05-02 16:43:03
【问题描述】:

所以,假设您有一个函数,X(N),它是一个完全黑匣子。不知道函数的增长率,查不到,也看不到源(目前)。

接下来让我们在另一个函数的上下文中检查它

for(int i = 0; i < N; ++i)
    X(N);

您编写的代码是线性的,但显然函数X 会影响您的 函数的增长率。 例如,如果X(N) 扩展为for(int i = 0; i &lt; N; ++i),则您的函数是二次函数。

我的问题是这样的:如果有人问你的函数的大 O 是什么,描述你的函数增长率的最佳方式是什么?

我说我会称之为线性,我的答案的辩护如下。

如果您知道X 的实际增长率,您可以准确估计您的代码,但是虽然您最终可以(以某种方式)获得代码,但大多数函数不附带性能统计信息.

因此,如果您确实可以访问X 的代码,您可以将其包含在您的估计中,但您在哪里划定界限? X 可能还会调用其他函数,然后再调用其他函数。我觉得在处理完全划分代码的制造场景之外,如果你还不知道被调用的黑盒函数的增长率,你必须决定估计你可以估计的代码。

【问题讨论】:

  • O(N*o(X)) 的上限测试有什么问题!

标签: performance time-complexity big-o


【解决方案1】:

如果您要谈论画线,我只想提供如下内容:-

代码的复杂性:- O(N*o(X))

只要判断函数 X(N) 的复杂度,就可以简单地代入公式。

到那时,它将是一个简写但有用的符号,同时满足循环的复杂性。

【讨论】:

  • X 是一个函数时,我不认为o(X) 是一个常见的符号。
  • 这可能不是正确的表示法,但我认为它在这种情况下的意图很清楚。
  • 好吧,@cfh-感谢您的评论!但是,o(X) 会给出更精确的复杂度评估,在这种情况下是完全可以接受的。当你需要替换时,它可以很容易地被替换——只是谈论更紧密的常数因子。
  • 我想我同意@Phylogenesis。它使意图明确。谢谢苏曼。
  • @Taekahn-不客气。感谢并继续发帖,以便我们和您一起受益:)
【解决方案2】:

你根本无法分辨。只是没有足够的信息。除非X(N) 是常数时间,否则说你的函数是线性的是错误的。

但是,您可以测量 X(N) 完成不同输入大小所需的时间。这通常会让您大致估计它的渐近行为方式。

【讨论】:

    【解决方案3】:

    首先,这是一个非常好的问题。人们通常会忘记一个关键步骤:确定基本操作

    时间效率的理论分析

    通过将基本操作的重复次数确定为输入大小的函数来分析时间效率。

    基本操作:对算法运行时间贡献最大的操作。

    我们尝试选择可以在 O(1) 时间或接近该时间执行的 基本操作 的东西,实际上我们选择的通常是次线性操作。例如:

    • 为变量赋值
    • 在数组中查找特定元素的值
    • 比较两个值
    • 增加一个值
    • 基本算术运算,例如加法和乘法

    例如,基本算术运算显然不是 O(1)。请参阅计算机科学的这个问题:https://cs.stackexchange.com/questions/1643/how-can-we-assume-that-basic-operations-on-numbers-take-constant-time

    因此,在决定基本操作时,我们近似 O(1)。

    C(n)为基本操作需要执行的次数。并设execTime为特定计算机上基本操作的执行时间。

    那么我们可以通过以下方式估计算法的运行时间T(n)

    T(n) ≈ execTime * C(n)
    

    如您所见,效率等级(例如 Big-O)仅取决于 C(n),因为无论如何 execTime 将是恒定的(而我们不这样做) t 关心渐近分析中的常数)。所以,任务是找到C(n)

    代码分析

    首先我们决定输入参数,显然是N

    然后我们决定基本操作,这就是与众不同的部分。

    如果我们决定将X(N);作为基本运算,那么:

    T(N) ≈ execTime * C(N)
    C(N) = N
    O(execTime * N) = O(n)
    

    如果我们知道 X(N) 内部发生了什么,我们可以决定一个 基本操作,它在每个 N 次中发生 N em>X(N) 运算例如,则:

    T(N) ≈ execTime * C(N)
    C(N) = N * N
    O(execTime * N * N) = O(N * N) = O(n^2)
    

    我们也可以选择整个循环作为基本操作

    T(N) ≈ execTime * C(N)
    C(N) = 1
    O(execTime * 1) = O(1)
    

    那么哪一个是正确的?他们都是。哪一个有意义?这完全取决于您要进行什么比较。

    例如比较排序算法的性能时,基本操作就是key比较,因为排序算法中最重要的工作就是比较key,而所有排序算法做key比较,这样我们就可以互相比较了。

    总结一下,你可以选择X(N)作为基本运算,说算法复杂度是O(N)X(N) 不一定是严格的 O(1),正如我上面解释的,我们已经忽略了比 O(1) 更复杂的基本操作。这里真正重要的是您要进行什么比较。

    假设您想比较这两种算法。

    for(int i = 0; i < N; ++i)
        X(N);
    

    for(int j = 0; j < N; j++)
        for(int i = 0; i < N; ++i)
            X(N);
    

    这里,如果你选择X(N)作为两者的基本运算,你会比较苹果和苹果,你会评估O(N) vs O(N^2)完美很好。

    如果你觉得这是错误的,因为 X(N) 不是 O(1),请考虑这个:

    for(int i = 0; i < N; ++i)
        System.out.print(N);
    

    来自 Java 的简单打印方法。我们不假思索地选择它作为基本操作,因为它只是一个打印语句,并假设它是O(1)。但事实并非如此。它的复杂性是次线性的,但绝对不是恒定的。打印所需的时间随着打印项目的大小(在本例中为 N)的增长而增长。

    那么,X(N) 还是 System.out.println(N),有关系吗?

    附:我没有发明上面的近似技术。我确实使用this 书准备了答案,主要是第二章。我建议你看看。

    【讨论】:

    • 这不是真的;我相信您混淆了 时间效率时间复杂度,后者是我们使用渐近分析工具描述的。在您的第一个示例中,您描述了 “将 X(N) 确定为 basic operation 可用于推断算法渐近行为的 Big-O 下限为 O(N)(我假设n 是这里的错字)。但是,如果我们正在分析我们的算法在 N 方面的行为,则此语句类似于说 “如果我们决定 X(N)=N”。在这种情况下,这实际上没有意义,因为 X(N) 是未知的。 ...
    • ... 总而言之,只要X(N) 是未知的,我们就不能真正说出我们算法的渐近行为,至少不会比在未知X(N)。回想一下,我们的输入参数是N;因此,“基本运算符” 应该反映算法随着N 的增加而增长。在不知道X(N) 的值的情况下,我们永远无法推导出绑定O(N)。然而,除此之外,我相信你提出了一些好的观点。
    • 我用Introduction to Design and Analysis of Algorithms第三版第2章准备了答案,推荐你看看。我很确定这个信息是正确的。
    • 好的,感谢建设性的 cmets。您的上一条评论让我对此感到怀疑,也许您是对的,X(m) 上很清楚,但 X(n) 上却很可疑。我将向教授提出这个问题并在这里分享答案。
    • 绝对不!我们只是在这里讨论,我不是专家(正如我所写,在你编辑后我对这个主题感到不安),所以我认为你可以让你的文字保持原样。 cmets 中的讨论可能对某人有价值(或者对更多知识的人来说)。我想我会总结为:可能(我不确定)在你上面的答案中使用这种方法在理论上是合理的,但我不建议它实践,因为它很容易被误解(即所描述的算法在O(N^2) 中运行。现在,快乐的算法分析!
    猜你喜欢
    • 2017-06-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多