实用指南:链表的概念和单向链表的实现

一:链表的概念及结构

1.1链表是⼀种物理存储结构上⾮连续存储结构,数据元素的逻辑顺序是通过链表中的引⽤链接次序实现
的。
(1))物理非连续:链表的节点在内存中不一定连续,每个节点包含数据域(val)和引用域(next)(指向其他节点)。
(2)逻辑连续:通过引用域,节点之间形成链式关系,保证了数据在逻辑上的连续。
(3))节点来源:现实中,链表的节点一般从堆内存中申请,两次申请的空间可能连续也可能不连续。
1.2 链表的常见结构
链表的结构多样,通过以下三个维度组合,可形成 8 种不同的链表结构:
(1))单向 / 双向:单向链表节点只有一个引用域,指向后继节点;双向链表节点有两个引用域,分别指向前驱和后继节点。
(2))带头 / 不带头:带头链表有一个头节点(不存储实际数据),用于简化操作;不带头链表直接从存储数据的节点开始。
(3))循环 / 非循环:循环链表的尾节点引用指向头节点(或头节点相关节点),形成闭环;非循环链表的尾节点引用为 null。
示例:
(1)单向不带头非循环
在这里插入图片描述
(2)单向带头非循环
在这里插入图片描述(3)单向不带头循环
在这里插入图片描述
(4)单向带头循环
在这里插入图片描述
在实际应用中,我们重点掌握两种核心结构:

无头单向非循环链表:结构简单,一般不单独用于存储数据,常作为其他数据结构(如哈希桶、图的邻接表)的子结构,也是笔试面试中的高频考点。
无头双向非循环链表:Java 中 LinkedList 的底层实现就是这种结构,能兼顾插入、删除和查询操作的性能。

二:单向链表

                         我们自己实现一个⽆头单向⾮循环链表

(1)基础结构定义

public class MySingleList {
// 内部静态类:链表节点
static class ListNode{
public int val;    // 数据域
public ListNode next; // 引用域(下一个节点引用)
// 初始化数据域
public ListNode(int val) {
this.val = val;
}
}
ListNode head; // 链表的头引用,是访问链表的入口
}

(2)手动创建链表

public void createList(){
ListNode node1 = new ListNode(12);
ListNode node2 = new ListNode(23);
ListNode node3 = new ListNode(34);
ListNode node4 = new ListNode(45);
ListNode node5 = new ListNode(56);
node1.next = node2;
node2.next = node3;
node3.next = node4;
node4.next = node5;
this.head = node1;
}

在这里插入图片描述

(3)打印链表

public void display() {
//不要让head动
ListNode cur = this.head;
while (cur != null) {
System.out.print(cur.val + " ");
cur = cur.next;
}
System.out.println();
}

测试:

public class Test {
public static void main(String[] args) {
MySingleList mySingleList = new MySingleList();
mySingleList.createList();
mySingleList.display();
}
}

在这里插入图片描述

在这里我们会思考如果我们把循环条件改为cur.next !=null会怎么样

根据上面的链表图,我们可以推出不会打印最后一个val;

我们来修改一下代码来看结果
在这里插入图片描述
确实少打印了56,说明我们的推断是正确的
所以我们可以总结出一个结论:
如果要停在最后一个节点,那么cur.next !=null,如果要遍历完所有节点,那么cur !=null

(4)得到单链表的⻓度

public int size(){
int count = 0;
ListNode cur = this.head;
while(cur !=null){
count++;
cur = cur.next;
}
return count;
}

(5)查找是否包含关键字key是否在单链表当中

public boolean contains(int key){
ListNode cur = this.head;
while(cur != null){
if(cur.val == key){
return true;
}
cur = cur.next;
}
return false;
}

(6)头插法

1实例化一个节点对象node
2.修改指向

//时间复杂度为O(1)
public void addFirst(int data){
ListNode node = new ListNode(data);//实例化一个节点对象node
node.next = this.head;//修改指向
this.head = node;//修改指向
}

在这里插入图片描述
注意:修改指向这两步不能调换,因为调换之后相当于自己指向自己
结论:所有插入的时候先绑定后面

这时我们会有一个疑问,如果我们在一个空链表里使用头插法要不要特殊处理?

答案是不用!

因为你一开始节点的引用域本来就是null,你的head也是null,第一步修改指向没什么变化,第二步修改指向,head就存node的地址了,也就是正常插入了!

(7)尾插法

1看一下链表是不是空链表(一个节点都没有)head==null
2.找尾巴

//时间复杂度O(N)
public void addLast(int data){
ListNode node = new ListNode(data);
if(this.head == null){
this.head = node;
return;
}
//找尾巴
ListNode cur = this.head;
while(cur.next != null){
cur = cur.next;
}
cur.next = node;
}

(8)下标不合法异常

public class illegalIndexException extends RuntimeException {
public illegalIndexException() {
}
public illegalIndexException(String message) {
super(message);
}
}

(9)任意位置前面插⼊,第一个数据节点为0号下标

假如插入2位置,那就是插入2之前,所以要找到index位置的前一个节点

在这里插入图片描述

//任意位置前面插⼊,第一个数据节点为0号下标
public void addIndex(int index,int 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 cur = searchIndex(index);
  if(cur == null){
  return;
  }
  ListNode node = new ListNode(data);
  node.next = cur.next;
  cur.next = node;
  }
  /**
  * 找到index位置的前一个节点
  * @param index
  * @return
  */
  private ListNode searchIndex(int index){
  int len = size();
  if(index < 0 || index >len){
    return null;
    }
    ListNode cur = this.head;
    int count = 0;
    while(count !=index-1){
    cur = cur.next;
    count++;
    }
    return cur;
    }

在这里插入图片描述

(10)删除第⼀次出现关键字为key的节点

/**
* 查找关键字key的前一个节点,找到返回地址
* 找不到返回null
* @param key
* @return
*/
private ListNode findNode(int key){
if(this.head == null){
return null;
}
ListNode prev = this.head;
while(prev.next != null){
if(prev.next.val == key){
return prev;
}
prev = prev.next;
}
return null;
}
//删除第⼀次出现关键字为key的节点
public void remove(int key){
if(this.head == null){
return;
}
if(this.head.val == key){
this.head = this.head.next;
return;
}
//走到这里 第一个节点如果是要删除的节点 此时已经删除完毕
ListNode prev = findNode(key);
if(prev == null){
return;
}
ListNode del = prev.next;
prev.next = del.next;
}

(11)删除所有值为key的结点

public ListNode removeAllKey(int key) {
if(this.head == null) {
return null;
}
ListNode prev = this.head;
ListNode cur = this.head.next;
while(cur != null) {
if(cur.val == key) {
prev.next = cur.next;
cur = cur.next;
} else {
prev = cur;
cur = cur.next;
}
}
//最后处理头
if(this.head.val == key) {
this.head = this.head.next;
}
return this.head;
}

(12)清空链表中所有元素:

public void clear() {
while (this.head != null) {
ListNode curNext = head.next;
head.next = null;
head.prev = null;
head = curNext;
}
last = null;
}
posted @ 2025-11-23 12:33  yangykaifa  阅读(7)  评论(0)    收藏  举报