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

相关推荐
信徒_15 分钟前
API 网关技术选型
java
simple-L615 分钟前
Java开发痛点技术文章大纲
java·开发语言
千寻girling41 分钟前
滑动窗口刷了快一个月(26天)了 , 还没有刷完. | 含(操作系统学什么的Java 后端)
java·开发语言·javascript·c++·人工智能·后端·python
小手cool42 分钟前
Java字符串按空行分割,包括末尾的空行
java
呱牛do it1 小时前
企业级门户网站设计与实现:基于SpringBoot + Vue3的全栈解决方案(Day 9)
java
liuyao_xianhui1 小时前
进程概念与进程状态_Linux
linux·运维·服务器·数据结构·c++·哈希算法·宽度优先
如君愿1 小时前
考研复习 Day 26 | 习题--计算机网络第三章(数据链路层 下)、数据结构 多维数组与广义表
数据结构·计算机网络·考研·记录考研
bqq198610261 小时前
MySQL分库分表
数据结构·mysql
迷途之人不知返1 小时前
List的模拟实现
数据结构·c++·学习·list
鸡蛋灌Bean1 小时前
mybatis分页深入了解
java·数据库·mybatis