数组:内存中开辟的连续的、有序的空间
- 动态数组时间复杂度
- 动态数组的扩容 reSize,涉及到数组的复制,时间复杂度为O(n)
- 添加操作:O(n)
addFirst() 不扩容,则单纯的将所有元素后移O(n) ,若扩容, reSize O(n) + 元素后移 O(n) =2O(n) -->O(n)
addLast() 不扩容,则为O(1),扩容 O(n) -->O(n)
add(Index,e) 不扩容,去index为size/2,则O(n/2)--> -->O(n)
最终动态数组的add时间复杂度为O(n)
- 移除操作O(n)
removeFirst() 不扩容,则单纯的将所有元素前移O(n) ,若扩容, reSize O(n) + 元素后移 O(n) =2O(n) -->O(n)
removeLast() 不扩容,则为O(1),扩容 O(n) -->O(n)
remove(Index,e) 不扩容,去index为size/2,则O(n/2)--> -->O(n)
- 修改操作:O(1)
- 查询
get(Index),根据索引查询,则为O(1) (已知索引)
contains(e)/find(e) 则为O(n) (未知索引)
以上操作均为最坏情况分析,但是不可能每次操作都涉及扩容,所以总以最坏情况分析并不是完全合理的。
我们平常分析复杂度一般是分析一个算法从头运行到尾它的复杂度是怎样的(最坏情况分析)。但我们在项目中经常会写一个复杂度较高的算法,这个高复杂度的算法是为了简化其他的算法。我们通常会将这个复杂度较高的算法和其他的算法放在一起来分析复杂度。这个复杂度较高的算法复杂度将会均摊到其他的操作中。这种复杂度分析法我们就叫做均摊复杂度分析法。动态数组的扩容或者移除,就会涉及到均摊复杂度分析法。 - 均摊复杂度分析和防止复杂度震荡
数组的添加操作时间复杂度分析
再向此动态数组添加元素时,触发扩容条件,数组进行扩容,此次操作时间复杂度为O(1)。
此时采取均摊复杂度分析,假设前n此操作每次操作都为时间都为1,前n次操作总时间为n,
第n+1次,操作时间为n,则前n+1次总时间为n+n=2n,平均每次操作时间则为2,时间复杂度仍为O(1)。
一次线性操作(第n+1次)的复杂度均摊到前面n次操作中,这就是典型的均摊复杂度分析。
数组移除元素复杂度分析
当我们删除元素,删除到一定程度时,为了避免空间的浪费,就要对数组进行缩容。
当触发缩容条件时,假设数组量减小一半。那么前面(n-1)次操作耗费时间总和为(n-1),
第n次操作耗费时间为(n+1),n为对数组进行缩容操作耗时,
1为删除这个元素耗时。所以均摊来看每次耗费时间仍然是2,时间复杂度为O(1)。
复杂度震荡
解决办法:推迟数组的缩容操作。当元素个数等于数组容量的1/4时,再进行缩容操作,缩容为当前数组容量的1/2。这样数组就有了一个缓冲的空间(lazy),无论是添加还是删除,则都是O(1)级别的