链表是一种常用的基础数据结构,它由一系列节点组成,每个节点包含两个部分:一个是存储数据的数据域,另一个是指向下一个节点的指针(在双向链表中还会有指向前一个节点的指针)。链表是动态的数据结构,可以在运行时动态地添加或删除节点,而不需要重新分配整个数据结构的空间。
链表的类型
- 单向链表(Singly Linked List):每个节点只包含一个指向下一个节点的指针。
- 双向链表(Doubly Linked List):每个节点包含两个指针,一个指向下一个节点,一个指向前一个节点。
- 循环链表(Circular Linked List):链表的尾部节点指向头部节点,形成一个环。
- 双向循环链表(Doubly Circular Linked List):每个节点有两个指针,链表的尾部指向头部,头部也指向尾部,形成一个双向的环。
链表的操作
链表的常见操作包括:
- 插入(Insertion):在链表的指定位置插入一个新的节点。
- 删除(Deletion):删除链表中的一个节点。
- 搜索(Search):查找链表中是否存在指定的值。
- 更新(Update):更新链表中某个节点的数据值。
- 遍历(Traversal):遍历链表并访问每个节点的数据。
链表与数组的对比
-
内存分配:
- 数组:通常在内存中占据一段连续的空间,并在创建时就需要指定大小。
- 链表:每个节点分别分配空间,可以在需要时动态地添加或删除,不需要预先声明大小。
-
时间复杂度:
- 数组:读取(访问)操作是 O(1),插入和删除操作可能是 O(n),因为可能需要移动元素。
- 链表:插入和删除通常是 O(1),但访问操作是 O(n),因为需要从头开始遍历链表。
-
内存利用率:
- 数组:可能会有未使用的内存空间(如果数组不满),或者需要重新分配并复制整个数组以扩大容量。
- 链表:相比之下,内存利用率更高,因为它可以根据需要进行增长。
实现单向链表
以下是如何在Java中实现一个基本的单向链表节点的示例:
public class ListNode {
int data;
ListNode next;
public ListNode(int data) {
this.data = data;
this.next = null;
}
}
public class SinglyLinkedList {
private ListNode head;
public SinglyLinkedList() {
head = null;
}
// 示例操作:在链表末尾添加节点
public void append(int data) {
if (head == null) {
head = new ListNode(data);
return;
}
ListNode current = head;
while (current.next != null) {
current = current.next;
}
current.next = new ListNode(data);
}
// 示例操作:删除链表中的节点
public void delete(int data) {
if (head == null) return;
if (head.data == data) {
head = head.next;
return;
}
ListNode current = head;
while (current.next != null) {
if (current.next.data == data) {
current.next = current.next.next;
return;
}
current = current.next;
}
}
}
在这个示例中,我们定义了链表节点ListNode
,它具有一个整数数据字段和一个指向下一个节点的指针。SinglyLinkedList
类管理一个链表,包括在末尾添加节点和删除具有特定值的节点的操作。
链表的灵活性使其在很多情况下都是数组的好替代品,特别是在需要频繁插入和删除操作时。掌握链表的使用和实现对于理解更高级的数据结构如栈、队列和图等是非常有帮助的。