访问元素——真的是 O(1) 吗?

Accessing Elements - Really O(1)?

is said that an example of a O(1) operation is that of accessing an element in an array. According to one source,O(1)可以定义如下:

[Big-O of 1] means that the execution time of the algorithm does not depend on the size of the input. Its execution time is constant.

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

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)中被描述为运行ning;数组中的 1,000,000 个元素对操作的 运行ning 时间没有影响吗?找到元素 55 肯定比找到元素 1 需要更长的时间吗?如果有的话,这在我看来就像 O(n)。

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

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

这当然假设你假设乘法需要恒定的时间而不管输入的大小,如果你是very precise

,这可以说是不正确的

数组是一种数据结构,其中对象存储在连续的内存位置。所以原则上,如果你知道基对象的地址,你就能找到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).

查找元素不是 O(1) - 但访问数组中的元素与查找元素无关 - 准确地说,您不与其他元素交互,您不需要访问除您的单个元素之外的任何内容 - 您总是计算地址,无论数组有多大,这就是单个操作 - 因此 O(1).

如果我们说下标运算符(索引)具有 O(1) 时间复杂度,则我们将此语句排除任何其他 operations/statements/expressions/etc 的运行时间。所以addElements不影响运行。

Surely it'd take longer to find element 55 than it would element 1?

"find"?不好了! "Find" 表示比较复杂的搜索操作。我们知道数组的基地址。要确定 arr[55] 处的值,我们只需将 551 添加到基地址并检索该内存位置的值。这绝对是 O(1).


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

数组连续存储数据,这与链表、树、图或其他使用引用查找 next/previous 元素的数据结构不同。

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

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

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

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

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

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

  • 缓存:数组将无法放入缓存或只能放入更高级别(较慢)的缓存。
  • 地址计算:对于大型数组,您需要更大的索引数据类型(例如 long 而不是 int)。这将使地址计算变慢,至少在大多数平台上是这样。

为语句

生成的机器代码(或者,在Java的情况下,虚拟机器代码)
int foo = arr[55];

本质上是:

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

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