虚拟头结点:为了让代码更加精简,统一所有结点的处理逻辑,可以在最前面增加一个虚拟的头结点(不存储数据)
1.数据结构需要改变,即链表加构造函数
public LinkedList2(){
first = new Node<>(null, null);
}
2.获取index对应的结点对象
第一个为空结点,所以从first.next开始遍历
private Node<E> node(int index) {
rangeCheck(index);
Node<E> node = first.next;
for (int i = 0; i < index; i++) {
node = node.next;
}
return node;
}
3.add和remove不需要考虑index为0的情况
因为有虚拟头结点,所以为0的情况也通用,即减少代码,只需要在找前一个结点的时候进行判断即可
Node<E> prev = index == 0 ? first : node(index - 1);
最好、最坏、平均、均摊复杂度
- 最好:一次找到O(1)
- 最坏:最后一次找到O(n)、O(n^2)等
- 平均:每一次时间复杂度相加求平均即(1+1+ 1+1+1+n)/n
- 均摊:前几次都是1,最后一次是n,即最后一次均摊给前面n-1个,即复杂度为O(2),也就是O(1)复杂度
···一般均摊复杂度等于最好,
···经过连续的多次复杂度比较低的情况后,出现个别复杂度较高的情况时候使用均摊复杂度
动态数组扩容缩容操作
1.扩容
private void ensureCapacity(int capacity){
int oldCapacity = elements.length;
if(oldCapacity < capacity){
int newCapacity = oldCapacity + (oldCapacity >> 1); //右移一位相当于除以2
E[] newElements = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++){
newElements[i] = elements[i];
}
elements = newElements;
}
}
2.缩容
····在删除元素的时候进行缩容,容量小于等于元素个数的1.5倍 和 容量小于最小容量的时候都不需要缩容
private void trim(){
int capacipy = elements.length;
if(size >= (capacity>>1) && capacity <= DEFAULT_CAPACITY)return;
// 剩余空间还很多
int newCapacity = capacity >> 1;
E[] newElements = (E[]) new Object[newCapacity];
for( int i = 0; i < size; i++){
newElements[i] = elements[i];
}
elements = newElements;
}