链表与LinkedList

链表与 LinkedList

链表是什么

链表也是一个比较基础的数据结构,数组与ArrayList 中提到数组占用连续的一片内存空间,在空间不足时进行扩容需要一块更大的连续的内存空间,但很多时候我们的空闲内存并不是连续的,因此可能出现空闲的总空间足以存储这么多的元素,但找不到一个连续的可以存储这么多元素的空闲空间。

链表对物理空间的连续性不做要求,每一个节点使用 next 和 pre 两个指针指向后面和前面的元素,因此链表对空间的利用率没有数组高,因为每个节点不仅仅要存储数据,还要多两个指针的大小。

链表的种类五花八门,链表也可以只存储 next 指针,这样就只能从前向后遍历,而且无法在指定节点的前面插入元素,所以为了方便一般使用的都是存储前后节点指针的链表,也叫双向链表。如果尾节点的 next 指针指向头节点,那么这就是一个循环链表。

以下就是一个常规链表节点的定义:

java 复制代码
    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;
        }
    }

对于一个链表而言,如果是在给定指针的位置添加或删除元素,那么速度很快,因为只需要更改指针指向的空间即可,不需要像数组那样迁移数据:

bash 复制代码
1 -> 2 -> 3 -> 4 -> 5
	| 2 的位置添加 6
1 -> 2 -> 6 -> 3 -> 4 -> 5	# 这里的元素 6 可以在内存中的任意位置

删除也是同理

bash 复制代码
# 当前节点的前一个节点的下一个指针指向当前节点的下一个节点
current.prev.next = current.next
# 当前节点的下一个节点的上一个指针指向当前节点的上一个节点
current.next.prev = current.prev

所以如果在指定元素位置添加或删除元素,时间复杂度为 O(1),这里强调了指定元素,如果是根据值来进行添加或删除,找到这个值所在的位置也需要时间,而且链表不像数组那样有那么多的查找方式(例如有序数组中的二分查找)。

即使都需要查找 n 个元素才能找到某个值对应的节点,一般数组也比链表要快,因为 cpu 缓存的原因,数组一个访问可能把后续 n 个元素都加载进了 cpu 缓存,而链表通常每次访问都需要去内存操作。

Java 中的链表实现

java 复制代码
public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    // 链表元素数量
    transient int size = 0;
    // 头节点指针
    transient Node<E> first;
	// 尾节点指针
    transient Node<E> last;
}    

Node 其实就是每一个元素,其定义已经在上面介绍过了,使用 first 和 last 的原因是通常我们需要判断如果首尾节点为 null,则特殊处理,例如删除首节点时如果首节点为 null,则把首节点的 next 节点设为首节点。为了使首尾节点可以统一处理,LinkedList 设置了专门的首尾节点,这样在进行操作时就无需特殊处理了。

前面其实强调过,链表只在指定元素的前后或首尾节点进行插入删除操作时时间复杂度才为 O(1),否则由于需要遍历找到对应元素的原因,时间复杂度并不乐观,当然我们使用的时候也不能只看时间复杂度,举例:ArrayList 在扩容时默认是当前容量的 1.5 倍,那么如果当前元素 3GB,则下次会直接申请4.5GB的空间,即使不考虑剩余的内存大小,仅仅是拷贝当前的 3GB 元素也很耗时。

如果不考虑 ArrayList 的扩容,其实 ArrayList 在尾部新增元素也未必比 LinkedList 差,因为 LinkedList 需要 new 一个 Node 并交换指针,而 ArrayList 空间已经分配好了。

同时还需要注意,LinkedList 在遍历时一定要使用迭代器而不是普通 for 循环,普通 for 循环每次都需要从链表头部开始查找,性能很差。

相关推荐
寻星探路41 分钟前
【深度长文】万字攻克网络原理:从 HTTP 报文解构到 HTTPS 终极加密逻辑
java·开发语言·网络·python·http·ai·https
曹牧3 小时前
Spring Boot:如何测试Java Controller中的POST请求?
java·开发语言
ValhallaCoder3 小时前
hot100-二叉树I
数据结构·python·算法·二叉树
爬山算法4 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
kfyty7254 小时前
集成 spring-ai 2.x 实践中遇到的一些问题及解决方案
java·人工智能·spring-ai
猫头虎4 小时前
如何排查并解决项目启动时报错Error encountered while processing: java.io.IOException: closed 的问题
java·开发语言·jvm·spring boot·python·开源·maven
李少兄4 小时前
在 IntelliJ IDEA 中修改 Git 远程仓库地址
java·git·intellij-idea
忆~遂愿4 小时前
ops-cv 算子库深度解析:面向视觉任务的硬件优化与数据布局(NCHW/NHWC)策略
java·大数据·linux·人工智能
小韩学长yyds4 小时前
Java序列化避坑指南:明确这4种场景,再也不盲目实现Serializable
java·序列化
仟濹4 小时前
【Java基础】多态 | 打卡day2
java·开发语言