文章目录
- [一 : LinkedList的模拟实现](#一 : LinkedList的模拟实现)
-
- [1.1 IList接口](#1.1 IList接口)
- [1.2 基础结构定义](#1.2 基础结构定义)
-
- [(1) void display()](#(1) void display())
- [(2) void addFirst(T data)](#(2) void addFirst(T data))
- [(3) void addLast(T data)](#(3) void addLast(T data))
- [(3) boolean contains(T key)](#(3) boolean contains(T key))
- [(4) int size()](#(4) int size())
- [(5) void addIndex(int index, T data)](#(5) void addIndex(int index, T data))
- [(6) void remove(T key)](#(6) void remove(T key))
- [(7) void removeAllKey(T key)](#(7) void removeAllKey(T key))
- [(8) void clear()](#(8) void clear())
- [二: LinkedList详解](#二: LinkedList详解)
-
- [2.1 LinkedList 的核心特性](#2.1 LinkedList 的核心特性)
- [2.2 LinkedList 的构造方法](#2.2 LinkedList 的构造方法)
- [2.3 LinkedList 的常用方法](#2.3 LinkedList 的常用方法)
- [2.4 LinkedList 的遍历方式](#2.4 LinkedList 的遍历方式)
- [2.5 ArrayList 与 LinkedList 的区别](#2.5 ArrayList 与 LinkedList 的区别)

作者:当战神遇到编程
文章专栏:数据结构欢迎大家点赞👍评论📝收藏⭐文章



无头双向链表的基础结构

例子

一 : LinkedList的模拟实现
1.1 IList接口
java
public interface IList<T> {
// 头插法:在链表头部插入数据
void addFirst(T data) ;
// 尾插法:在链表尾部插入数据
void addLast(T data) ;
// 任意位置插入:第一个节点为0号下标
void addIndex(int index, T data) ;
// 查找:判断关键字key是否存在
boolean contains(T key) ;
// 删除:移除第一次出现的key节点
void remove(T key);
// 删除:移除所有值为key的节点
void removeAllKey(T key) ;
// 统计:获取链表长度
int size() ;
// 清空:释放链表所有节点
void clear() ;
// 遍历:打印链表所有元素
void display() ;
//反转链表
void reverseList();
}
1.2 基础结构定义
java
public class MyLinkedList<T> implements IList<T>{
//静态内部类:链表节点
static class ListNode<T>{
public T val;//数据域
public ListNode<T> prev;//引用域(上一个节点的引用)
public ListNode<T> next;//引用域(下一个节点的引用)
public ListNode(T val) {
this.val = val;
}
}
public ListNode<T> head;//链表的头引用
public ListNode<T> last;//链表的尾引用
@Override
public void addFirst(T data) {
}
@Override
public void addLast(T data) {
}
@Override
public void addIndex(int index, T data) {
}
@Override
public boolean contains(T key) {
return false;
}
@Override
public void remove(T key) {
}
@Override
public void removeAllKey(T key) {
}
@Override
public int size() {
return 0;
}
@Override
public void clear() {
}
@Override
public void display() {
}
@Override
public void reverseList() {
}
}
(1) void display()
java
public void display() {
ListNode<T> cur = this.head;
while(cur != null) {
System.out.print(cur.val + " ");
cur = cur.next;
}
System.out.println();
}
(2) void addFirst(T data)
链表不为空时

链表为空时

java
public void addFirst(T data) {
ListNode<T> node = new ListNode<>(data);
if(this.head == null) {
this.head = node;
this.last = node;
}else{
node.next = this.head;// 1. 新节点的 next 指向当前的头
this.head.prev = node;// 2. 当前头的 prev 指向新节点
this.head = node;// 3. 更新 head 指针指向新节点
}
}
(3) void addLast(T data)
链表不为空时

链表为空时

java
public void addLast(T data) {
ListNode<T> node = new ListNode<>(data);
if(this.head == null) {
this.head = node;
this.last = node;
}else {
this.last.next = node;
node.prev = this.last;
this.last = node;
}
}
(3) boolean contains(T key)
java
public boolean contains(T key) {
ListNode<T> cur = this.head;
while(cur != null) {
if(cur.val.equals(key)){
return true;
}
cur = cur.next;
}
return false;
}
(4) int size()
java
public int size() {
ListNode<T> cur = this.head;
int count = 0;
while(cur != null) {
count++;
cur = cur.next;
}
return count;
}
下标不合法异常
java
public class IllegalIndexException extends RuntimeException{
public IllegalIndexException() {
}
public IllegalIndexException(String message) {
super(message);
}
}
(5) void addIndex(int index, T data)

java
public void addIndex(int index, T data) {
int len = size();
if(index < 0 || index > len) {
throw new IllegalIndexException("下标不合法异常");
}
//头插
if(index == 0) {
addFirst(data);
return;
}
//尾插
if(index == len) {
addLast(data);
return;
}
//中间位置插
ListNode<T> cur = search(index);
ListNode<T> node = new ListNode<>(data);//实例化一个节点
//修改指向
node.next = cur;
cur.prev.next = node;
node.prev = cur.prev;
cur.prev = node;
}
/**
* 找到index位置节点
* @param index
* @return
*/
private ListNode<T> search(int index) {
ListNode<T> cur = this.head;
int count = 0;
while(count < index) {
count++;
cur = cur.next;
}
return cur;
}
(6) void remove(T key)
1.要删除的元素在中间位置

2.要删除的元素在头节点
(1)有很多节点

(2)只有一个节点

)
3.要删除的元素在尾节点

java
public void remove(T key) {
ListNode<T> cur = this.head;
// 1. 遍历寻找目标节点
while (cur != null) {
if (cur.val.equals(key)) {
//头节点
if(cur == this.head) {
this.head = this.head.next;
if(this.head == null) {
this.last = null;
}else {
this.head.prev = null;
}
}else {
//尾节点
if(cur.next == null) {
this.last = this.last.prev;
this.last.next = null;
}else{
//中间节点
cur.prev.next = cur.next;
cur.next.prev = cur.prev;
}
}
return;
}
cur = cur.next;
}
}
(7) void removeAllKey(T key)
java
public void removeAllKey(T key) {
ListNode<T> cur = this.head;
// 1. 遍历寻找目标节点
while (cur != null) {
if (cur.val.equals(key)) {
//头节点
if(cur == this.head) {
this.head = this.head.next;
if(this.head == null) {
this.last = null;
}else {
this.head.prev = null;
}
}else {
//尾节点
if(cur.next == null) {
this.last = this.last.prev;
this.last.next = null;
}else{
//中间节点
cur.prev.next = cur.next;
cur.next.prev = cur.prev;
}
}
}
cur = cur.next;
}
}
(8) void clear()
java
public void clear() {
ListNode<T> cur = this.head;
while(cur != null) {
ListNode<T> nextNode = cur.next;
cur.val = null;
cur.prev = null;
cur.next = null;
cur = nextNode;
}
this.head = null;
this.last = null;
}
二: LinkedList详解
2.1 LinkedList 的核心特性
1.底层基于无头双向不循环链表 实现.
2.未实现 RandomAccess 接口,其随机访问效率较低,且本身是非线程安全的.
3.在链表中进行任意位置的增删,其性能瓶颈在于查找定位O(n),而非操作本身O(1).
4.ArrayList 采用预分配的连续内存策略,需通过 O(n) 的扩容操作来应对容量增长;而 LinkedList 采用按需分配的离散内存策略,无扩容需求,但以更高的单个节点内存开销和频繁的内存申请为代价
2.2 LinkedList 的构造方法
| 方法 | 解释 |
|---|---|
| LinkedList() | 无参构造,创建一个空的 LinkedList |
| LinkedList(Collection<? extends E> c) | 创建一个包含指定集合中所有元素的链表 |
使用示例
java
import java.util.LinkedList;
import java.util.List;
public class Test {
public static void main(String[] args) {
// 构造空的 LinkedList
List<String> list1 = new LinkedList<>();
list1.add("JavaSE");
list1.add("数据结构");
list1.add("算法");
System.out.println(list1);
//构造一个包含指定集合中所有元素的链表
List<String> list2 = new LinkedList<>(list1);
System.out.println(list2);
}
}
//运行结果
[JavaSE, 数据结构, 算法]
[JavaSE, 数据结构, 算法]
2.3 LinkedList 的常用方法
LinkedList 提供了丰富的方法用于操作元素,以下是核心方法:
| 方法 | 解释 | 备注 |
|---|---|---|
| boolean add(E e) | 将元素 e 追加到链表末尾 | 成功返回 true |
| void add(int index,E element) | 在索引 index 处插入元素 element | 索引从 0 开始 |
| boolean addAll(Collection<? extends E> c) | 将集合 c 中的所有元素按顺序追加到末尾 | 只要链表改变就返回 true |
| E remove(int index) | 移除指定索引处的元素,并返回该元素 | 会触发后续元素前移 |
| boolean remove(Object o) | 移除链表中第一个与 o 相等的元素 | 依据 equals 方法判断 |
| E get(int index) | 返回指定索引处的元素 | 不改变链表结构 |
| E set(int index,E element) | 用 element 替换索引 index 处的元素,返回原值 | 属于更新操作 |
| void clear() | 移除链表中的所有元素 | 链表长度变为 0 |
| boolean contains(Object o) | 检查链表中是否包含至少一个元素 o | 返回 true/false |
| int indexOf(Object o) | 返回 o 首次出现的索引,不存在则返回 -1 | 从头向后搜索 |
| int lastIndexOf(Object o) | 返回 o 最后一次出现的索引,不存在则返回 -1 | 从后向前搜索 |
| List< E> subList(int fromIndex,int toIndex) | 返回索引从 from 到 to (左闭右开) 的视图 | 对该视图的修改会反映在原列表中 |
使用示例
java
public class Test {
public static void main(String[] args) {
LinkedList<String> linkedList = new LinkedList<>();
//默认尾插
linkedList.add("JavaSE");
linkedList.add("数据结构");
System.out.println(linkedList);//[JavaSE, 数据结构]
//尾插
linkedList.addLast("算法");
//头插
linkedList.addFirst("Java之旅");
System.out.println(linkedList);//[Java之旅, JavaSE, 数据结构, 算法]
//任意位置插入
linkedList.add(3,"Mysql");
System.out.println(linkedList);//[Java之旅, JavaSE, 数据结构, Mysql, 算法]
//删除指定索引处的元素,并返回该元素
System.out.println(linkedList.remove(0));//Java之旅
System.out.println(linkedList);//[JavaSE, 数据结构, Mysql, 算法]
linkedList.add(0,"Java之旅");
linkedList.add(1,"Java之旅");
System.out.println(linkedList);//[Java之旅, Java之旅, JavaSE, 数据结构, Mysql, 算法]
//移除链表中第一个与o相等的元素
linkedList.remove("Java之旅");
System.out.println(linkedList);//[Java之旅, JavaSE, 数据结构, Mysql, 算法]
//返回指定索引处的元素
System.out.println(linkedList.get(2));//数据结构
//用 element 替换索引 index 处的元素,返回原值
System.out.println(linkedList.set(0, "Java成神之路"));//Java之旅
System.out.println(linkedList);//[Java成神之路, JavaSE, 数据结构, Mysql, 算法]
//检查链表中是否包含至少一个元素 o
System.out.println(linkedList.contains("数据结构"));//true
// 返回 o 首次出现的索引,不存在则返回 -1
System.out.println(linkedList.indexOf("数据结"));//-1
System.out.println(linkedList.indexOf("数据结构"));//2
// 返回 o 最后一次出现的索引,不存在则返回 -1
System.out.println(linkedList);//[Java成神之路, JavaSE, 数据结构, Mysql, 算法]
System.out.println(linkedList.lastIndexOf("数据结构"));//2
linkedList.add(3,"数据结构");
System.out.println(linkedList);//[Java成神之路, JavaSE, 数据结构, 数据结构, Mysql, 算法]
System.out.println(linkedList.lastIndexOf("数据结构"));//3
System.out.println(linkedList.lastIndexOf("数控技术"));//-1
//返回索引从 from 到 to (左闭右开) 的视图
List<String> temp = linkedList.subList(1,4);
System.out.println(temp);//[JavaSE, 数据结构, 数据结构]
temp.remove("数据结构");
System.out.println(temp);//[JavaSE, 数据结构]
System.out.println(linkedList);//[Java成神之路, JavaSE, 数据结构, Mysql, 算法] 对该视图的修改会反映在原列表中
LinkedList<String> linkedList2 = new LinkedList<>();
//将集合 c 中的所有元素按顺序追加到末尾
linkedList2.addAll(linkedList);
System.out.println(linkedList2);//[Java成神之路, JavaSE, 数据结构, Mysql, 算法]
//移除链表中的所有元素
linkedList2.clear();
System.out.println(linkedList2);//[]
}
}
2.4 LinkedList 的遍历方式
java
public class Test {
public static void main(String[] args) {
LinkedList<Integer> linkedList = new LinkedList<>();
linkedList.add(1);
linkedList.add(2);
linkedList.add(3);
linkedList.add(4);
// 1. foreach 遍历
for (int x : linkedList) {
System.out.print(x + " ");
}
System.out.println(); // 1 2 3 4
// 2. ListIterator迭代器正向遍历
ListIterator<Integer> list = linkedList.listIterator();
while(list.hasNext()) {
System.out.print(list.next() + " ");//1 2 3 4
}
System.out.println();
// 3. ListIterator迭代器反向遍历
while(list.hasPrevious()) {
System.out.print(list.previous() + " ");//4 3 2 1
}
System.out.println();
// 4.for循环
for (int i = 0; i < linkedList.size(); i++) {
System.out.print(linkedList.get(i) + " ");//1 2 3 4
}
System.out.println();
// 5.Iterator迭代器遍历
Iterator<Integer> it = linkedList.iterator();
while(it.hasNext()) {
System.out.print(it.next() + " ");//1 2 3 4
}
System.out.println();
}
}
2.5 ArrayList 与 LinkedList 的区别
| 对比维度 | ArrayList | LinkedList |
|---|---|---|
| 存储空间 | 物理上一定连续 | 物理上不一定连续 |
| 随机访问 | 支持 | 不支持 |
| 头插操作 | 需搬移所有元素,时间复杂度O(n) | 需修改节点引用,时间复杂度为O(1) |
| 插入操作 | 空间不足时需扩容(1.5倍扩容),可能浪费内存 | 无扩容需求,插入时直接创建节点 |
| 尾插操作 | 若无需扩容,时间复杂度为O(1);若需扩容,时间复杂度O(n) | 时间复杂度为O(1) |
| 删除操作 | 需搬移后续元素,时间复杂度O(n) | 按索引删除 remove(int index):时间复杂度O(n) ; 按引用删除:时间复杂度 O(1) |
| 应用场景 | 元素存储稳定,频繁进行查询操作 | 元素频繁进行任意位置插入/删除操作 |