【问题标题】:Complexity of calling get() on a LinkedList in a for loop using O notation使用 O 表示法在 for 循环中对 LinkedList 调用 get() 的复杂性
【发布时间】:2013-03-04 00:13:34
【问题描述】:

我有一个实用的方法来确定使用 O() 表示法的一小段代码的复杂性。

代码是:

for (int i = 0; i < list.size(); i++)
    System.out.println(list.get(i));

有问题的列表是一个链表。对于我们的实际操作,我们得到了一个现成的 LinkedList 类,尽管我们必须编写自己的 size()get() 方法。

这个问题让我感到困惑的是,在最终计算中要计算什么。问题问:

如果列表中有 100 个元素,它将进行多少次查找?在此基础上,使用 O() 表示法计算程序的复杂度。

如果我只计算get() 方法,它将平均进行 n/2 次查找,从而导致 O(n) 的大 O 表示法。但是,for 循环的每次迭代都需要重新计算 size(),这涉及到查找(以确定链表中有多少个节点)。

在计算这段代码的复杂度时,是否应该考虑到这一点?还是计算大小不算作查找?

【问题讨论】:

  • 这是您自己实现的链表还是 Java 中包含的?
  • @AndrewMarshall:大学给了我们一个 LinkedList 课程。它拥有 LinkedList 类的所有方法,除了我们必须自己编写的 size() 和 get()。
  • 仅供参考,如果您正在实现 get() 和 size() 调用,则可以在那些计算这些函数必须调用列表查找函数的次数(getFirst() ,下一个()等)。这将为您提供问题的“100”元素部分的答案。尝试 1000 和 10000,O 表示法应该是相当明显的......以及为什么不应该以这种方式循环链接列表。

标签: java linked-list


【解决方案1】:

我可能回答得有点晚了,但我认为这个 for 循环实际上是

说明

您将访问列表的第 i 个索引的每个循环迭代。因此,您的调用顺序将是:

这是因为每次迭代 i 都会递增,而您正在循环 n 次。

因此,可以使用以下总和来评估方法调用的总数:

【讨论】:

  • 我同意,@JreJav 也同意。然而,他指定了单次迭代的 O(n) 复杂度,并将其留给读者“弄清楚整个循环的时间复杂度将是多少”。尤其是由于这个问题已经相当老了,最好让接受的答案更清楚地了解问题的实际答案,而不是只是其中的一部分。
  • 对于那些还不太了解的人...第一个 get(0) 调用将查看第一个元素。 get(1) 调用将从第一个元素开始,然后遍历到第二个元素……依此类推,每个遍历元素都等于索引: get(n) 调用将从索引 0 开始并进行 n 次遍历.因此,正如这个答案所暗示的,如果大小为 100,那么仅 get 函数的总遍历次数将为 1+2+3+4+5...+100。除非您在其他地方跟踪,否则计算大小将需要 N 次遍历。因此,如果它重新计算每个循环迭代,这也是 O(N^2) 次遍历。
【解决方案2】:

在Java LinkedList中,get(int)操作是O(N),size()操作是O(1)复杂度。

【讨论】:

    【解决方案3】:

    由于它是一个链表,确定大小将是一个 O(N) 操作,因为你必须遍历整个链表。

    另外,您错误地计算了 .get() 的时间复杂度。对于 big-O,重要的是 最坏情况 计算。对于链表,最坏的检索情况是元素在链表的末尾,所以也是O(N)。

    总而言之,您的算法每次迭代将花费 O(2N) = O(N) 时间。我希望你能从那里弄清楚整个循环的时间复杂度是多少。

    顺便说一句,在现实世界中,您可能只想在循环之前计算一次大小,正是因为这样可能效率低下。显然,如果列表的大小可以在循环期间发生变化,这不是一个选项,但对于这种非变异算法来说,情况似乎并非如此。

    【讨论】:

    • 我已经提出了在循环中计算大小的问题——在查找方面肯定效率不高。我可以问一下,为什么需要 O(2N) 变成 O(n) 查找?这是 size 和 get 的双重查找吗?
    • O(2N) 等价于 O(N)。常数乘数和加法因素。不,这只是意味着您对 .size() 使用 O(N),对 .get() 使用 O(N),因此时间复杂度为 O(N + N + C),其中 C 是另一个常数时间循环中的操作(不依赖于 N 的操作)。 O(N + N + C) = O(2N + C) = O(2N) = O(N)
    • 我假设如果循环运行 n 次,它将执行 2n^2 次查找,变成 n^2 次查找?
    • 是的。循环运行 N 次迭代,因此时间复杂度为 O((2N) * N) = O(2N^2) = O(N^2)。
    • 你是明星——而且反应很快!谢谢
    【解决方案4】:

    简答:这取决于问题的解释。

    如果问题是如果我想找到第 100 个位置(例如调用 .get(100)),我必须跳多少次列表,那么复杂性将是 O(N),因为我需要遍历整个列表一次。

    如果问题是通过检查每个索引(如.get(1).get(2)、...、.get(100))来查找第 i 个变量的复杂性,则复杂性将是 O(N²) 由 michael 解释。

    长答案:

    计算大小的复杂性取决于您的实现。如果您遍历整个列表以找到大小,则大小计算的复杂度将是 O(N) (第一种情况下的复杂度为 O(2N)O(N² + N) 在第二个)这最后一部分也取决于你的实现,因为我认为你正在计算 for 循环的大小。

    如果您将大小保存为每次添加元素时都会变大的实例变量,那么您将有 O(1) 的大小以及第一种和第二种情况的相同复杂性.

    我们将 O(2N)(或 O(aN + b) 的任何情况)四舍五入到 O(N) 的原因是因为我们只关心处理数据所花费的时间的增长。如果 N 很小,代码无论如何都会运行得很快。如果 N 很大,代码可能会运行更多时间,具体取决于复杂性,但与更差的复杂性实现相比,常量 a 和 b 不会有太大影响。

    假设对于 O(N) 复杂度的小输入 N,代码在 2 秒内运行。
    随着值变大:N, 2N, 3N, 4N, ..., kN
    如果代码复杂度O(N),则时间为:2、4、6、8、...、2k
    如果代码复杂度 O(2N),则时间为:4, 8, 12, 16, ..., 2k * 2
    如果代码复杂度 O(N²) 时间为:4, 16, 36, 64, ..., (2k)²

    如您所见,最后一个实现很快就失控了,而第二个实现只比简单的线性慢两倍。所以 O(2N) 速度较慢,但​​与 O(N²) 解决方案相比几乎没有什么。

    【讨论】:

      猜你喜欢
      • 2014-12-23
      • 1970-01-01
      • 1970-01-01
      • 2017-11-24
      • 1970-01-01
      • 2017-09-28
      • 2014-03-07
      • 1970-01-01
      • 2022-01-13
      相关资源
      最近更新 更多