均摊复杂度和防止复杂度的震荡
resize的复杂度分析
// 数组扩容
private void resize(int newCapacity){
E[] newData = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
newData[i] = data[i];
}
data = newData;
}
(1)addLast(e)均摊时间复杂度分析
resize(n) O(n)
假设当前capacity=8,并且每一次添加操作都使用addlast
17基本操作 = 9次addLast操作 + 8次元素转移操作
均摊每次addLast操作进行大约两次基本操作:
Average = 17 / 9 ≈ 2
假设capacity=n,n+1次addLast操作,触发resize,总共进行了2n+1=(n+1)+ n次基本操作;
均摊每次addLast操作进行大约两次基本操作:
Average = 2n+1 / n+1 ≈ 2
结论:因此addLast均摊时间复杂度为O(1),均摊时间复杂度会比最坏情况有意义。
同理,removeLast操作均摊时间复杂度也是O(1)
(2)addLast(e)和remove(e)复杂度震荡分析
addLast(e)程序:
// 在数组末尾添加元素(复用add方法)
public void addLast(E e) {
add(size, e);
}
// 数组指定位置添加元素
public void add(int index, E e) {
if (index < 0 || index > size)
throw new IllegalArgumentException("Add failed. Require index >= 0 and index <= size");
if (size == data.length)
resize(2 * data.length);
for (int i = size - 1; i >= index; i--)
data[i + 1] = data[i];
data[index] = e;
size++;
}
// 删除数组最后一个元素
public E removeLast() {
return remove(size - 1);
}
remove(e):
// 从数组中删除index位置的元素,返回删除的元素
public E remove(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Remove failed.Index is illegal");
}
E ret = data[index];
for (int i = index + 1; i < size; i++) {
data[i - 1] = data[i];
}
size--;
// loitering objects != memory leak 手动释放内存空间
data[size] = null;
if(size == data.length / 2) {
resize(data.length / 2);
}
return ret;
}
当我们同时看到addLast(e)和removeLast(e)操作:那么add和remove每次操作的时间复杂度都是O(n)。
addLast(e)和remove(e)复杂度震荡演示:
第一次执行addLast(e)时间复杂度:O(n)
第二次执行removeLast(e)时间复杂度:O(n)
第三次执行addLast(e)时间复杂度:O(n)
第四次执行removeLast(e)时间复杂度:O(n)
出现addLast(e)和remove(e)复杂度震荡原因:removeLast时resize过于着急(Eager)
解决方案:Lazy(remove延迟执行resize)
容量2n,size=n+1:
容量2n,size=n,进行缩容1/2:
容量2n,size=1/4*2n,进行缩容1/2 :
程序优化方案:
如果感兴趣的童鞋,可以观看我下一篇博客:栈和栈的应用:撤销操作和系统栈