链表与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 循环每次都需要从链表头部开始查找,性能很差。

相关推荐
Chester_19994 小时前
CSP202203C.计算资源调度器
开发语言·数据结构·c++·蓝桥杯
EXI-小洲5 小时前
Java 操作 Word:字符串替换、图片插入、动态生成表格与API接口下载
java·开发语言·spring boot·word
2601_961901706 小时前
SpringBoot使用Nacos进行application.yml配置管理
java·spring boot·spring
何以解忧,唯有..7 小时前
LangChain 工具调用(Tool Calling)实战指南
java·前端·langchain
QQ_21696290967 小时前
【源码编号:project79475】SpringBoot校内二手交易平台:商品发布、分类检索、留言交流、订单管理全流程实战
java·spring boot·后端
前端 贾公子9 小时前
第09章:上下文与记忆 (2)
java·服务器·前端
2601_962203519 小时前
Java进阶07集合(续)
java·开发语言
郑州光合科技余经理9 小时前
本地生活服务系统:模块边界与结算字段怎么拆
java·开发语言·前端·后端·系统架构·uni-app·php
专注仿真9 小时前
Spring AI 实现智能对话系统项目指南
数据结构·spring·机器学习
roman_日积跬步-终至千里9 小时前
【数据治理(6)】DataOps 不是调度工具,而是让数据产品长期可信的运行机制
java·服务器·网络