数组、链表与跳表是常见的,也是工作中经常使用到的数据结构,学好这三个数据结构将为自己未来的学习工作打下良好的基础,本文针对三者的实现与特性进行了分析说明,也提到了一些重点的面试常用考点内容。
数组是一种最简单的数据结构常见的声明有以下几种:
Java: int a[100] (= [?]);
Python: a = [];
JavaScript: let a = [1, 2, 3];
当你声明一个数组的时候,计算会在内存中开辟连续的地址,计算机是通过内存管理器进行访问,访问任意一个元素,时间复杂度都是O(1),这也是数组的基本特性之一。
数组中如果增加一个元素的话,就必须把插入未知的元素及其后面的元素都要向后移动一位,这样导致了插入元素的时间复杂度为O(n)。
跟插入元素内容类似,首先现将该位置的元素移出,然后将后续的元素向前移动一位,最后将最后一位空出来的位置内容置为空,能够唤醒Java的垃圾回收机制即可,或者手动管理内存的话,将数组size()减少即可。
可以看到ArrayList的源码里针对于add()的操作;
public boolean add(E e)
{
modCount++;
//判断数组大小是否等于数据长度
if (size == data.length)
//保证数组长度大于数据长度
ensureCapacity(size + 1);
//最后在数组的末尾加入该元素,并且将size++
data[size++] = e;
return true;
}
public void add(int index, E element) {
//确保插入的位置小于等于当前数组长度,并且不小于0,否则抛出异常
rangeCheckForAdd(index);
//确保数组长度(size)加1之后足够存入下一个数据
//修改次数(modCount)标识加1,如果当前数组长度(size)加1后大于当前的数组长度,则调用grow(),增长数组
ensureCapacityInternal(size + 1); // Increments modCount!!
//grow方法会将当前数组长度变为原来容量的1.5倍
//确保有足够的容量之后,System.arraycopy 将需要插入的位置(index)后面的元素往后移动一位
System.arraycopy(elementData, index, elementData, index + 1, size - index);
//将新的数据内容存放到数组的指定位置(index)上
elementData[index] = element;
size++;
}
//将修改次数(modCount)自增1,判断是否需要扩充数组长度,判断条件就是用当前所需的数组最小长度与数组长度对比,如果大于0,则增长
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//确保添加的元素有地方存储
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
可以看到数组在插入和删除数据频繁的时候,需要进行大量的数组copy操作,这样导致它并不高效,所欲如果大量涉及添加删除操作的时候,我们会考虑其他数据结构;
Linked List内部元素不管包含Value值,而且每一个元素都有一个Next指向下一个元素,每一个元素都是用class来定义的,包含的Value可以是class类型,而Next为指针;
头指针用Head来表示,尾指针用Tail来表示,最后一个指针指向空,如果尾指针指向头部,该链表就是循环链表;
//指针为一前一后,所以Java中的链表是双向链表
Entry<T> next;
Entry<T> previous;
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
f (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
Linked List 增加节点
Linked List 删除节点
链表中增删数据没有涉及到相应的数据整体复制移动,所以效率比较高;但是访问列表任意元素的话,必须从头结点一次查询,直到找到对应元素,所以复杂度较高O(n);
Linked List 的时间复杂度
prepend: O(1)
append: O(1)
lookup: O(n)
insert: O(1)
delete: O(1)
Array 的时间复杂度
prepend: O(1)
append: O(1)
lookup: O(1)
insert: O(n)
delete: O(n)
注意:调表本身只能用于有序情况下;
调表对应的是二叉搜索中的平衡树和二分查找,是一种插入、删除、查找都是*
优点:原理简单、容易实现、便于扩展、效率更高,如Redis、LevelDB中替代平衡树;