【问题标题】:Accessing Elements - Really O(1)?访问元素 - 真的是 O(1)?
【发布时间】:2016-09-04 09:12:45
【问题描述】:

is said 一个 O(1) 操作的示例是访问数组中的元素。根据source的说法,O(1)可以这样定义:

[Big-O of 1] 表示算法的执行时间不依赖于 输入的大小。它的执行时间是恒定的。

但是,如果要访问数组中的一个元素,那么操作的效率不取决于数组中元素的数量吗?例如

int[] arr = new int[1000000];
addElements(arr, 1000000); //A function which adds 1 million arbitrary integers to the array. 

int foo = arr[55]; 

我不明白最后一条语句如何被描述为在 O(1) 中运行;数组中的 1,000,000 个元素对操作的运行时间没有影响吗?找到元素 55 肯定比找到元素 1 需要更长的时间吗?如果有的话,这在我看来就像 O(n)。

我确信我的推理有缺陷,但是,我只是想澄清一下这可以说是在 O(1) 中运行吗?

【问题讨论】:

    标签: arrays algorithm big-o computer-science asymptotic-complexity


    【解决方案1】:

    数组是一种数据结构,其中对象存储在连续的内存位置。所以原则上,如果你知道基础对象的地址,你就能找到ith对象的地址。

    addr(a[i]) = addr(a[0]) + i*size(object)

    这使得访问数组 O(1) 的 ith 元素。

    编辑
    从理论上讲,当我们谈论访问数组元素的复杂性时,我们谈论的是固定索引i
    输入大小 = O(n)
    要访问ith 元素,addr(a[0]) + i*size(object)。该术语独立于n,因此称为O(1)。

    乘法仍然取决于i,而不是n。它是常数 O(1)。

    【讨论】:

      【解决方案2】:

      内存中元素的地址将是数组的基地址加上索引乘以数组中元素的大小。因此,要访问该元素,您只需访问memory_location + 55 * sizeof(int)

      这当然假设您假设乘法需要恒定的时间,而不管输入的大小如何,如果您是very precise,这可能是不正确的

      【讨论】:

      • 理论上,你不能假设乘法需要恒定的时间。但在我们的例子中,由于它涉及与输入大小无关的数字,因此我们认为它是恒定的。
      • 对,这更多是在“访问ith索引”的上下文中
      • 这是最准确的答案。假设是使用以恒定位大小表示的数字来寻址内存。
      • 大 O 表示法描述了限制行为并且是一个相当理论的概念,其中内存(磁带)是无限的和线性的 - 所以一般的“访问”操作通常应该被认为是 O(1),IMO。跨度>
      【解决方案3】:

      为语句生成的机器代码(或者,在 Java 的情况下,虚拟机器代码)

      int foo = arr[55];
      

      基本上是:

      1. 获取arr的起始内存地址到A中
      2. A 加 55
      3. 取A中内存地址的内容,放到foo的内存地址中

      这三个指令在标准机器上都需要 O(1) 时间。

      【讨论】:

        【解决方案4】:

        找到一个元素不是 O(1) - 但是访问数组中的元素与找到一个元素无关 - 准确地说,你不与其他元素交互,你不需要访问除了你的单个元素之外的任何东西——你总是计算地址,不管数组有多大,这就是那个单一的操作——因此 O(1)。

        【讨论】:

          【解决方案5】:

          理论上,数组访问是 O(1),正如其他人已经解释的那样,我猜你的问题或多或少是一个理论上的问题。我还是喜欢带来另一个方面。

          实际上,如果数组变大,数组访问会变慢。有两个原因:

          • 缓存:阵列不适合缓存或仅适合更高级别(较慢)的缓存。
          • 地址计算:对于大型数组,您需要更大的索引数据类型(例如 long 而不是 int)。这会使地址计算变慢,至少在大多数平台上是这样。

          【讨论】:

          • 我赞成这个答案,因为我认为这些知识越来越重要。但是,我反对“理论上”与“实践中”的二分法。您的解释在理论上也是正确的,它只需要一个不太常见和更精确的理论。 (如果您的理论在任何重要方面与实践有所不同,则说明您使用的理论不合适。)
          【解决方案6】:

          如果我们说下标运算符(索引)具有 O(1) 的时间复杂度,我们会在此语句中排除任何其他操作/语句/表达式/等的运行时。所以addElements不影响运行。

          确定找到第 55 个元素比找到第 1 个元素需要更长的时间吗?

          “找到”?不好了! “查找”意味着相对复杂的搜索操作。我们知道数组的基地址。要确定arr[55] 的值,我们只需将551 添加到基地址并检索该内存位置的值。这绝对是 O(1)。


          1 因为int 数组的每个元素至少占用两个字节(使用C 时),所以这并不完全正确。 55需要先乘以int的大小。

          【讨论】:

            【解决方案7】:

            数组连续存储数据,不像链接列表、树或图或其他使用引用来查找下一个/上一个元素的数据结构。

            很直观,第一个元素的访问时间是 O(1)。但是你觉得访问第 55th 元素的时间是 O(55)。那就是你弄错了。你知道第一个元素的地址,所以访问它的时间是 O(1)。

            但您也知道第 55th 元素的地址。它只是 1st + size_of_each_element*54 的地址。

            因此,您可以在 O(1) 时间内访问该元素以及数组的任何其他元素。这就是为什么你不能在一个数组中包含多种类型的元素的原因,因为这会完全弄乱数学来找到数组的第 nth 个元素的地址。

            因此,访问数组中的任何元素都是 O(1) 并且所有元素都必须是相同的类型

            【讨论】:

            • O(55) 和 O(1) 不一样吗?
            • @FrankPuffer:是的,但是当你取一个一般的 n 而不是特定的 55 时,它变成了 O(n)。
            • 已接受,但您的回答可能会给人错误的印象,即存在差异。
            • @FrankPuffer:我不应该这么想。我从问题中举了一个例子。任何完全阅读该问题的人都不会感到困惑。此外,此评论线程将消除任何进一步的疑问。
            猜你喜欢
            • 1970-01-01
            • 2016-09-17
            • 2021-12-18
            • 2010-11-06
            • 2010-10-16
            • 2013-10-05
            • 1970-01-01
            • 2013-04-10
            相关资源
            最近更新 更多