【问题标题】:What is the n in big-O notation?big-O 表示法中的 n 是什么?
【发布时间】:2014-06-01 06:49:29
【问题描述】:

这个问题很简单,但我就是找不到足够好的答案。在the most upvoted SO question regarding the big-O notation 上,它说:

例如,排序算法通常基于比较操作(比较两个节点以确定它们的相对顺序)进行比较。

现在让我们考虑一下简单的冒泡排序算法:

for (int i = arr.length - 1; i > 0; i--) {
    for (int j = 0; j < i; j++) {
        if (arr[j] > arr[j+1]) {
            switchPlaces(...)
        }
    }
}

我知道最坏的情况是O(n²),最好的情况是O(n),但究竟什么是n?如果我们尝试对已经排序的算法进行排序(最好的情况),我们最终会什么都不做,那么为什么它仍然是O(n)?我们仍在循环 2 个 for 循环,所以如果有的话,它应该是 O(n²)n 不能是比较操作的次数,因为我们还是比较所有的元素吧?

【问题讨论】:

  • n 是要排序的数组的长度。即使数组已排序,您也需要进行 n - 1 次比较来验证这一点。
  • “WC”和“BC”应该是什么?请避免使用不常见的缩写,你不能确定人们会理解他们扩展的内容。
  • 假设 WC 和 BC 表示最坏情况和最佳情况,您展示的算法没有最佳情况 O(n),因为它在找到数组时不会提前退出已排序。
  • @NiklasB。 n 是数组的长度?这个怎么可能?对于 O(n^2) 的最坏情况,数组的长度不会改变?
  • @eitank 不正确。在处理排序算法时,N 表示要排序的元素总数。你访问内存的次数将是一个函数f(n),即N的一个函数。

标签: algorithm big-o complexity-theory time-complexity


【解决方案1】:

在分析排序算法的 Big-O 性能时,n 通常表示您要排序的元素数量。

因此,例如,如果您使用冒泡排序对 n 项目进行排序,则最坏情况下的运行时性能将是 O(n2) 次操作。这就是为什么冒泡排序被认为是一种极差的排序算法,因为它不能随着要排序的元素数量的增加而很好地扩展。随着要排序的元素数量线性增加,最坏情况的运行时间呈二次方增加。

这是一个示例图,展示了随着问题大小 N 的增加,各种算法如何根据最坏情况运行时间进行扩展。深蓝色的线代表线性缩放的算法,而洋红色/紫色的线代表二次算法。

请注意,对于足够大的 N,二次算法最终需要比线性算法更长的时间来解决问题。

图表取自http://science.slc.edu/~jmarshall/courses/2002/spring/cs50/BigO/

另见

【讨论】:

  • 那只是为了排序算法?那么这只是一个常见的约定吗?我的意思是,假设你有两个 for 循环,一个嵌套在另一个中,都从 0 到 n,并且在内部 for 循环之后进行了一些添加。 O 将是 (n^2)。那么 n 是什么?只是 + 操作的数量?
  • 一般来说,当您进行 Big-O 分析时,N 代表问题的大小。在您的双循环示例中,n 应该代表什么数字?
  • 只是一些任意数字。有点像这样:stackoverflow.com/questions/526728/…
【解决方案2】:

我认为这里有两件事混淆了,n 和受 Big-O 分析限制的 n 的功能。

按照惯例,对于任何算法复杂性分析,n 是输入的大小,如果没有指定不同的话。对于任何给定的算法,有几个有趣的输入大小函数可以计算asymptotic bounds,例如Big-O。

排序算法中最常见的此类函数是最坏情况下的比较次数。如果有人说排序算法是O(n^2),而没有指定任何其他内容,我会假设他们的意思是最坏情况比较计数是O(n^2),其中n 是输入大小。

另一个有趣的函数是工作空间的数量,除了被排序的数组之外的空间。冒泡排序的工作空间是O(1),常量空间,因为不管数组大小,它只使用几个变量。

冒泡排序可以编码为在最好的情况下只进行n-1 数组元素比较,通过在任何不进行交换的遍历之后完成。看到这个伪代码implementation,它使用swapped来记住是否有任何交换。如果数组已经排序,第一遍不进行交换,所以排序在一遍之后完成。

【讨论】:

  • 好答案。但是,我认为当算法使用两个嵌套的 for 循环时,为什么冒泡排序的最佳情况运行时间(根据他的说法)对于已经排序的数组是 O(n),我认为原始发帖人也可能会感到困惑。或类似的东西。最初的问题有点混乱和不清楚。
【解决方案3】:

n 通常是输入的大小。对于数组,这将是元素的数量。

要查看不同的情况,您需要更改算法:

for (int i = arr.length - 1; i > 0 ; i--) {
    boolean swapped = false;

    for (int j = 0; j<i; j++) {
        if (arr[j] > arr[j+1]) {
            switchPlaces(...);
            swapped = true;
        }
    }

    if(!swapped) {
        break;
    }
}

您的算法的最佳/最差情况都是O(n^2),但由于有可能提前返回,现在最好的情况是O(n)

【讨论】:

  • 我会给你最好的答案,因为它只解决了我的烦恼。
【解决方案4】:

n 是数组长度。你想找到T(n)算法复杂度。

访问内存然后检查条件是非常昂贵的。因此,您将 T(n) 定义为访问内存的数量。

在给定的算法 BC 和 WC 中使用 O(n^2) 访问内存,因为您检查 if 条件 O(n^2) 次。

使复杂性更好:持有一个标志,如果你不在主循环中做任何交换,这意味着你的数组已经排序,你可以休息。

现在,在 BC 中,数组已排序,并且您访问所有元素一次,因此 O(n)。 而在 WC 中仍然是 O(n^2)。

【讨论】:

  • 但是在排序数组的情况下,我所做的只是检查 if 条件?我做了 O(n^2) 次!抱歉,还是有点糊涂。
  • 使用更高级版本的冒泡排序来防止不必要的内存访问。
猜你喜欢
  • 1970-01-01
  • 2023-02-08
  • 1970-01-01
  • 2012-10-13
  • 2015-03-28
  • 2011-10-09
  • 2023-03-16
  • 2021-02-01
  • 1970-01-01
相关资源
最近更新 更多