数组的查找时间复杂度与存储方式的关系

时间:2016-11-01 09:59:03

标签: java arrays time-complexity

众所周知,索引对数组访问的时间复杂度为O(1)。

由数组支持的Java ArrayList的文档说明了get操作的相同内容:

  

size,isEmpty,get,set,iterator和listIterator操作以恒定时间运行。

查找是通过在给定索引处获取元素的内存地址来完成的,与数组的大小无关(类似于start_address + element_size * index)。我的理解是数组的元素必须在内存中彼此相邻存储,才能使这种查找机制成为可能。

但是,从this question,我知道Java中的数组不能保证它们的元素在内存中连续存储。如果是这种情况,它怎么可能总是O(1)?

编辑:我非常了解ArrayList的工作原理。我的观点是,如果JVM规范无法保证数组的连续存储,则其元素可能位于内存中的不同区域。尽管这种情况极不可能,但它会使上面提到的查找机制变得不可能,而JVM将有另一种方法来进行查找,这不应该是O(1)。那时,它将违反本问题顶部所述的常识和ArrayList关于其get操作的文档。

感谢大家的回答。

编辑:最后,我认为这是一个特定于JVM的东西,但大多数(如果不是全部的话)JVM坚持连续存储一个数组的元素,即使在没有保证的情况下,也可以使用上面的查找机制。它简单,高效且具有成本效益。

据我所知,将元素存储在整个地方然后必须采用不同的方法进行查找会很愚蠢。

4 个答案:

答案 0 :(得分:4)

据我所知,规范并不能保证数组会连续存储。我推测大多数JVM实现。在基本情况下,它很简单,可以强制执行:如果你不能扩展数组,因为其他内存占用你需要的空间,那就把整个东西移到其他地方。

你的困惑源于对O(1)意义的误解。 O(1)并不意味着在单一操作中执行功能(例如start_address + element_size * index)。这意味着无论输入数据的大小如何 - 在这种情况下,阵列,都在恒定的时间内执行操作。对于未连续存储的数据,这是完全可以实现的。例如,您可以将索引映射到内存位置。

答案 1 :(得分:1)

从链接的问题可以看出,即使JVM规则没有强制要求,1D阵列很可能在内存中连续。

给定一个连续的数组,ArrayList的时间复杂度如下所示。然而,在特殊情况下或特殊JVM中,复杂性可能略有不同并非不可能。如果您必须考虑规范允许的各种虚拟机,则无法提供时间复杂性。

答案 2 :(得分:1)

每次添加一个元素,其容量都会被检查: http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b27/java/util/ArrayList.java#ArrayList.add%28java.lang.Object%29

public boolean add(E e) {
   ensureCapacity(size + 1);  // Increments modCount!!
   elementData[size++] = e;
   return true;
}

在这里,ensureCapacity()可以保持数组顺序。怎么样? http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b27/java/util/ArrayList.java#ArrayList.ensureCapacity%28int%29

public void ensureCapacity(int minCapacity) {
        modCount++;
        int oldCapacity = elementData.length;
        if (minCapacity > oldCapacity) {
           Object oldData[] = elementData;
        int newCapacity = (oldCapacity * 3)/2 + 1;
        if (newCapacity < minCapacity)
           newCapacity = minCapacity;
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
}

因此,在每个阶段,它都会尝试确保数组具有足够的容量并且是线性的,即范围内的任何索引都可以在O(1)中检索。

答案 3 :(得分:-1)

ArrayList包装一个真正的数组。 (在add上可能需要增长。)因此,getset,O(1)具有相同的复杂性。

然而,ArrayList可以(直到Java的某个未来版本)只包含Objects。对于像intchar这样的原始类型,需要低效的包装类,并且在整个分配的内存中对对象进行混乱划分。仍为O(1)但具有较大的常数因子。

因此,对于原始类型,您可以使用数组并自己实现增长:

    elementData = Arrays.copyOf(elementData, newCapacity);

或者如果适合,请使用 Bitset ,其中带有true的索引是值。