大家好,我是三叔,很高兴这期又和大家见面了,一个奋斗在互联网的打工人。
前言:什么是链表
什么是链表,链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
链表的入口节点称为链表的头结点也就是head。
如上图,是一个单链表,单链表的指针只能指向下一个data域。链表的节点在内存中是分散存储的,通过指针连在一起;链表的最后一个节点的引用为空,表示链表的结束,也就是我们所说的空指针。
为什么要引入虚拟头节点
虚拟头节点是一个在链表头部添加的额外节点,它不存储任何实际的数据,只是作为辅助。虚拟头节点的引入可以简化链表操作的逻辑,并解决一些特殊情况下的边界问题。
通过引入虚拟头节点,我们可以在处理链表时统一操作逻辑。无论是在链表的开头、中间还是末尾插入或删除节点,我们都可以使用相同的逻辑进行操作。这减少了代码中的条件判断,使得代码更加简洁和易于理解。
虚拟头节点还能够解决空链表的情况。如果链表为空且没有虚拟头节点,我们需要对空链表和非空链表进行不同的处理。然而,通过添加虚拟头节点,链表的头部始终存在,我们可以统一对链表进行处理,无论链表是否为空。
举例说明:
比如有一条链表,现在我要对链表中的某个节点进行删除操作,现在我要将链表中的第三个节点删除,那么应该怎么操作?
如图所示:我只需要将2的指针域 next 指向下一个节点,也就是第四个节点;此时需要注意的是,在c语言中,data域3还在内存中,需要手动释放内存,但是在java中,则不需要自己去手动释放内存。
同理,新增操作如下:
我们只需要将2的指针next指向新增的对象,新增的对象的指针域next指向节点3,这样就成功在节点2和3之间插入了新增节点。
删除链表元素:使用虚拟节点(Dummy Head Node)
正如我上面所说,如果删除链表中的某个元素,只需要删除的这个值的前一个对象的指针指向删除对象的下一个值即可,但是如果我现在删除的对象是链表的第一个元素,那该怎么办,已经是第一个元素了前面已经没有指针域指向下一个值了。
移除头结点和移除其他节点的操作是不一样的,因为链表的其他节点都是通过前一个节点来移除当前节点,而头结点没有前一个节点。所以头结点如何移除呢,其实只要将头结点向后移动一位就可以,这样就从链表中移除了一个头结点。这样移除了一个头结点,你会发现,在单链表中移除头结点 和 移除其他节点的操作方式是不一样,需要单独写一段逻辑来处理移除头结点的情况。
我们来看一个示例应用,展示虚拟头节点如何简化链表操作。假设我们要在一个有序链表中插入一个新节点。如果没有虚拟头节点,我们需要单独处理链表为空和不为空的情况。而有了虚拟头节点,我们只需要找到插入位置,创建新节点,并更新引用即可,无需特殊处理空链表情况。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode removeElements(ListNode head, int val) {
if(head == null) {
return head;
}
ListNode dummy = new ListNode(-1, head);// 设置虚拟首节点
ListNode cur = head;// 临时节点
ListNode pre = dummy;
while(cur != null) {
if(cur.val == val) {
pre.next = cur.next;
} else {
pre = cur;
}
cur = cur.next;
}
return dummy.next;// 注意,返回的是虚拟节点的下一个节点,因为虚拟节点只是我们定义的,不是真正意义上存在数据的节点,虚拟节点的next节点才是真正的头节点
}
}