重要说明

图片与实际代码无关,这里引用的图片有很大一部分是把头结点当做链表的第一个元素,我写的示例代码不将头结点视为第一个元素

链表的概念

简单来说就是说从头结点出发,每一个结点都有一个指向后继结点的 next 指针,表中最后一个元素的 next 指向 null

img

链表的构造结构

这里我们使用 Java 来创建单链表的基本结构,这里是 Leetcode 常用的构建方法

public class ListNode {
public int val;
public ListNode next;
ListNode(int x) {
val = x;
next = null;
}
ListNode node = new ListNode(1);
}

对于简化的构造方法,你应该使用以下的代码对结点进行修改赋值

node.next = new_node;
node.val = number;

为了练习 Java 的面向对象特性,这里我不用简化的构造方法。

public class ListNode {
// 链表数据
private int data;
// 链表指针
private ListNode next;

public ListNode(int data) {
this.data = data;
}

public int getData() {
return data;
}

public void setData(int data) {
this.data = data;
}

public ListNode getNext() {
return next;
}

public void setNext(ListNode next) {
this.next = next;
}

}

而这种方式的修改赋值就是调用 get 和 set 办法

node.setNext(new_node);
node.setNext(new_node.getNext());
node.setData(number);
node.getData();

链表的增删改查 - 增

任何数据结构都避免不了的最基础的操作,首先来看最基础的构建链表。

对于单链表来说,构建链表有两种方式,一种是头插法,一种是尾插法。

头插法

头插法简单说来就是逆序插入,每次插入将新节点的 next 指向头结点的 next,然后将头结点的 next 指向新结点。

头插法

示例代码

// 头插法插入新结点
public ListNode headInsert(ListNode head, ListNode node) {
node.setNext(head.getNext());
head.setNext(node);
return head;
}

尾插法

尾插法简单来说就是顺序插入,每次插入都将此时的“头结点”的 next 改成新结点 node,然后将新结点 node 座位链表尾部,next 改为 null。和头插法不一样的是,尾插法每次返回的结点并不是头结点,所以在构建链表的时候记得保存头结点的信息。

// 尾插法插入新结点
public ListNode tailInsert(ListNode head, ListNode node) {
head.setNext(node);
node.setNext(null);
return node;
}

构建链表

这段代码展示了在测试类中初始化链表的全过程,这里我用 L 结点保存尾插时头结点的信息,后续会用到。

public static void main(String[] args) {
ListNodeTest test = new ListNodeTest();


// 初始化头结点
ListNode head = new ListNode(-1);
head.setNext(null);

// 复制头结点
ListNode L = head;

// 构建链表
test.initList(head, 5);

}

// 构建新链表
public ListNode initList(ListNode head, int number) {
for (int i = 0; i < number; i++) {
ListNode new_node = new ListNode(i);
// 头插法
head = headInsert(head, new_node);
// 尾插法
head = tailInsert(head, new_node);
}
return head;
}

综合练习

// 具体位置插入结点
public ListNode insertNode(ListNode head, ListNode node, int position) {
// 头结点为空,意味着链表为空,故此时返回要插入的结点
if (head == null) {
return node;
}
// 检查插入的位置是否越界, 返回头结点等待下次插入
int length = getListLength(head);
if (position > length + 2 || position < 1) {
System.out.println("位置参数越界");
return head;
}

// 遍历找到前驱节点,使用头插法插入新结点
ListNode P = head;
for (int i = 0 ; i < position; i++) {
P = P.getNext();
}
headInsert(P, node);

return head;
}

运行结果

image-20240320101925597

链表的增删改查 - 查

先来看看链表的查询,对于单链表来说,一定是从头结点逐个向后访问,因此,头结点是一定不可以丢掉的。

我们写一个遍历函数,同时输出链表长度

// 获取链表长度
public int getListLength(ListNode head) {
int length = 0;
ListNode node = head;
while(node != null) {
length++;
System.out.printf("%d ", node.getData());
node = node.getNext();
}
return length;
}

这里可以看出,传入遍历函数的结点不同,因为头插法返回的都是头结点,此时的 head 可以说是没变,直接传入。而对于尾插法来说,我这里的处理是用了一个 L 结点存储头结点信息,所以传入遍历函数的是 L 结点。

// 头插法获取链表可以直接传入返回的 head,因为头插法每次返回的都是头结点
// 尾插法获取链表需要传入之前复制的头结点信息,因为尾插法每次返回的是结点
System.out.println("链表长度为: " + (test.getListLength(head) - 1));
System.out.println("链表长度为: " + (test.getListLength(L) - 1));

链表的增删改查 - 删

对于删除来说还是挺好办的,处理以下特殊位置,其他位置遍历到前驱结点,然后将前驱结点的 next 直接设置为删除结点的后置结点。

表头

这种情况只需要 head = head.getNext();

img

表尾和其他

遍历到删除结点的前驱就可以进行操作了

img

img

// 删除目标结点
public ListNode deleteNode(ListNode head, int position) {
// 头结点为空,意味着链表为空,无法删除
if (head == null) {
return null;
}
// 检查插入的位置是否越界, 返回头结点等待下次插入
int length = getListLength(head);
if (position > length + 1 || position < 1) {
System.out.println("位置参数越界");
return head;
}

// 表头元素删除
if (position == 1) {
head.setNext(head.getNext().getNext());
}

// 表尾和其他位置
ListNode P = head;
for (int i = 0; i < position - 1; i++) {
P = P.getNext();
}
P.setNext(P.getNext().getNext());
return head;
}

运行结果

image-20240320104420596

链表的增删改查 - 改

改也是水到渠成,同样的操作,只不过这次直接遍历到需要修改的结点即可

// 修改目标结点
public ListNode changeNode(ListNode head, int data, int position) {
// 头结点为空,意味着链表为空,无法删除
if (head == null) {
return null;
}
// 检查插入的位置是否越界, 返回头结点等待下次插入
int length = getListLength(head);
if (position > length + 1 || position < 1) {
System.out.println("位置参数越界");
return head;
}
// 遍历后修改
ListNode P = head;
for (int i = 0; i < position; i++) {
P = P.getNext();
}
P.setData(data);

return head;
}

运行结果

image-20240320104845697


欢迎关注我的其他平台