链表(Linked List)是一种常见的数据结构,它由一系列节点(Node)组成,每个节点包含数据元素以及指向下一个节点的指针,与数组不同,链表中的元素在内存中并不是连续存储的,而是通过指针链接起来。
分类
根据节点之间指针的不同连接方式,链表可以分为多种类型:
- 单链表:每个节点只包含一个指向下一个节点的指针,最后一个节点的指针指向 null,表示链表的结尾。
- 双链表:每个节点除了包含指向下一个节点的指针外,还包含一个指向前一个节点的指针,这样可以实现双向遍历,在某些操作上更加灵活。
- 循环链表:对于单链表来说,如果最后一个节点的指针指向链表的头节点,就形成了单循环链表;同理,双链表也可以构成循环结构,首尾节点相互连接,循环链表在一些特定场景下有其应用优势,比如实现环形缓冲区等。
单链表在Java中的实现
首先需要定义链表的节点类,如下所示:
class ListNode {
int val; // 节点存储的数据,可以是任意类型,这里以整数举例
ListNode next; // 指向下一个节点的引用
ListNode(int val) {
this.val = val;
this.next = null;
}
}
val表示节点存储的数据值,next就是指向后续节点的引用,初始化为null表示当前节点暂时没有后续连接的节点。
接下来构建单链表类,实现诸如链表的创建、插入、删除、查找等基本操作。
创建单链表通常从一个空链表开始,然后逐个添加节点。示例代码如下:
class SinglyLinkedList {
ListNode head; // 链表的头节点
SinglyLinkedList() {
head = null;
}
// 在链表末尾添加节点的方法
public void append(int val) {
ListNode newNode = new ListNode(val);
if (head == null) {
head = newNode;
return;
}
ListNode current = head;
while (current.next!= null) {
current = current.next;
}
current.next = newNode;
}
}
插入操作可以分为在表头插入、在表中指定位置插入和在表尾插入
在表头插入节点:
public void insertAtHead(int val) {
ListNode newNode = new ListNode(val);
newNode.next = head;
head = newNode;
}
该方法先创建一个新节点,然后让新节点的 next 指针指向当前的头节点,最后将新节点设置为头节点,实现了在表头插入的功能,时间复杂度为O(1),非常高效。
在指定位置插入节点:
public void insertAtPosition(int val, int position) {
if (position == 0) {
insertAtHead(val);
return;
}
ListNode newNode = new ListNode(val);
ListNode current = head;
int count = 0;
while (count < position - 1 && current!= null) {
current = current.next;
count++;
}
if (current == null) {
throw new IndexOutOfBoundsException("Invalid position");
}
newNode.next = current.next;
current.next = newNode;
}
这段代码先判断要插入的位置是否为 0,如果是则调用insertAtHead方法。否则,通过循环遍历找到指定位置的前一个节点,然后将新节点插入到相应位置。
同样,删除操作也有多种情况,比如删除表头节点、删除表尾节点、删除指定位置的节点等。
删除表头节点:
public void deleteAtHead() {
if (head!= null) {
head = head.next;
}
}
删除指定位置的节点:
public void deleteAtPosition(int position) {
if (position == 0) {
deleteAtHead();
return;
}
ListNode current = head;
int count = 0;
while (count < position - 1 && current!= null) {
current = current.next;
count++;
}
if (current == null || current.next == null) {
throw new IndexOutOfBoundsException("Invalid position");
}
current.next = current.next.next;
}
先判断要删除的位置是否为 0,若是则调用deleteAtHead方法,否则,通过循环找到指定位置的前一个节点,然后跳过要删除的节点,将其前后节点连接起来,实现删除功能。
查找链表中是否存在某个值的节点可以通过遍历链表来实现:
public boolean contains(int val) {
ListNode current = head;
while (current!= null) {
if (current.val == val) {
return true;
}
current = current.next;
}
return false;
}
从链表头开始,逐个节点比较其存储的值与要查找的值是否相等,直到遍历完整个链表或者找到匹配的值。