链表
链表的基本原理
链表,顾名思义就是像锁链一样的把数据都串连在一起;他通过定义链表结构中的指针来把各个节点连接在一起。而节点就是用来存储信息的,数据和下一个节点的指针都是储存在上一个节点中。当然在链表中有一个特殊的节点——头结点(head)他里面不存数据,但是并不代表他不可以存数据,知识大家都这么约定好了不存数据,因为这样的话方便对于链表节点的统一操作,后面我们会说到(对于头部数据的操作,不使用虚拟头结点的话需要进行特殊处理)。
链表与数组的优劣
数组
- 优点:定长分配一次申请内存,减少多次申请内存的时间;内存相连,查找数据快,不需要一个一个的从头查找数据就可以直接获取数据信息。
- 缺点:定长分配和内存相连的缺点也非常的明显,这种方式分配内存如果多次申请的话,会造成内存的浪费有一些零碎的内存空间就无法使用到了。而对于数组的操作来说扩容和增删来说是非常麻烦的;扩容我们需要重新定义一个新的更大的数组,然后把原数组里面的数据全部转移到新数组中去,这是很花费时间的(所以一般都是成倍扩容,以避免多次操作)。对数组元素进行增删操作的话,对于数组来说也需要移动大量的元素(具体操作在我的上一篇博客中有进行详细的解释),同样很花费时间。
链表
- 优点:链表的优点其实有一点和数组是相对的,链表不分配固定内存,他们各个节点的位置都是分散在内存区域中的,通过节点之间的指针进行联系,所以对于内存的浪费问题就没有了。同样对于增删数据来说,链表是非常快的(当然我们这里不考虑查找到该位置的时间消耗),他只需要操作后一个和前一个节点的指针连接就可以实现,而不需要去操作整个链表。
- 缺点:同样缺点也是非常的明显的,就是链表的节点数据在内存中不是相连的了,所以我们想要查找数据的话,就必须要从第一个头结点开始一个一个往后查找,直到找到该元素位置。
链表的实现
其实链表的实现基本的都是和我上一篇文章中的动态数组的实现的架子是基本一致的,所以这里就只介绍对于链表的核心操作的部分。
链表节点的定义
每一个节点的定义都需要有存储元素和指向下一个节点的指针(头结点除外)
public class LinkedList<E> {
private int size = 0;
private Node<E> first; // 链表的节点类型,也是头结点
private class Node<E>{
E e; // 节点中存放的数据
Node<E> next; // 指向下一个节点的地址
public Node(E e, Node<E> next) {
this.e = e;
this.next = next;
}
}
头结点的创建
虚拟头节点的创建,他作为第一个节点出现在链表中,他不存任何东西,只是为了方便我们链表的后续操作。
/**
* 头结点的改造:原来是没有头节点的,但是现在我们需要在链表的最前面添加一个空的节点,然后统一我们的操作
*/
public LinkedList() {
first = new Node<>