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

相关推荐
Anastasiozzzz19 分钟前
Java Lambda 揭秘:从匿名内部类到底层原理的深度解析
java·开发语言
骇客野人21 分钟前
通过脚本推送Docker镜像
java·docker·容器
铁蛋AI编程实战37 分钟前
通义千问 3.5 Turbo GGUF 量化版本地部署教程:4G 显存即可运行,数据永不泄露
java·人工智能·python
晚霞的不甘1 小时前
CANN 编译器深度解析:UB、L1 与 Global Memory 的协同调度机制
java·后端·spring·架构·音视频
SunnyDays10111 小时前
使用 Java 冻结 Excel 行和列:完整指南
java·冻结excel行和列
摇滚侠1 小时前
在 SpringBoot 项目中,开发工具使用 IDEA,.idea 目录下的文件需要提交吗
java·spring boot·intellij-idea
爱敲代码的TOM1 小时前
数据结构总结
数据结构
云姜.1 小时前
java多态
java·开发语言·c++
李堇1 小时前
android滚动列表VerticalRollingTextView
android·java
泉-java1 小时前
第56条:为所有导出的API元素编写文档注释 《Effective Java》
java·开发语言