我们常见的时间复杂度表示:O(1), O(n), O(lgn), O(nlog2n), O(n2)。这里的O是渐进时间复杂度,表示n趋于无穷大时,运行时间和输入数据之间的关系。下面用一个例子来简单的分析时间复杂度。
public int sum(int[] nums){
int sum = 0; //(1)
for(int num : nums) //(2)
sum+=num; //(3)
return sum; //(4)
}
在这个例子里nums中的元素个数就是n,这个方法消耗时间T与n成线性关系:T = c1*n+c2。忽略常数c1,c2。即我们的时间复杂度为O(n),n趋于无穷大时,低阶项忽略。
例一
分析上一篇动态数组的添加操作:
1.addLast(e) 尾插 从数组元素尾部直接插入,不用挪动数组,所以时间复杂度是O(1)
2.addFirst(e) 头插 从数组头部插入元素,需要挪动所有(n)元素,所以时间复杂度是O(n)
3.add(index,e) 在index插入元素 需要挪动n-index个元素,这里的index可以等于从0-size任何一个数,等于每个数的概率相等,可以算出平均时间复杂度的期望。平均而言,时间为n/2,所以时间复杂度为O(n/2)=>O(n)
在算法复杂度的计算上通常考虑0最坏情况
另外在添加时,会有可能resize,所以对于动态数组的添加操作的时间复杂度是O(n)
均摊复杂度
如果一个相对比较耗时的操作,不会每次都触发,那么这个操作的时间可以分摊到其他操作中。
- 例二
分析上述中addLast(e)的均摊复杂度
复杂度震荡
对于上一篇动态数组的添加(addLast)和删除(removeLast)操作可以分析出它们的均摊时间复杂度都是O(1),但是对于下面的场景:
当前数组的size = data.length,如果我们不停的重复
addLast(e),removeLast(e),addLast(e),removeLast(e)……
那么会不停的触发resize,这样每一次的复杂度都是O(n)。这就是复杂度的震荡。
这里的原因是由于我们的removeLast中对于resize过于eager了,每次size等于一半时就立马resize。
解决办法:我们可以lazy一点,把1/2变成3/4或其他。