前言. 链表概述
-
定义:链表是一种线性数据结构,其中每个元素是一个对象,称为节点。每个节点包含两个部分:数据和一个指向下一个节点的引用(或指针)。
-
类型:
- 单向链表(Singly Linked List)
- 双向链表(Doubly Linked List)
- 循环链表(Circular Linked List)
1. 链表概述
比喻:把链表想象成一条队伍,队伍中的每个人就是一个节点,每个人都知道下一个人的信息(单向链表)或者知道前后两个人的信息(双向链表)。
2. 单向链表(Singly Linked List)
比喻:这是一条单向队伍,每个人只能知道下一个人是谁,不能知道前一个人是谁。
结构
- 节点:队伍中的人,包含自己的信息(数据)和指向下一个人的引用(指针)。
- 头节点:队伍的第一个人。
- 尾节点:队伍的最后一个人,指向空(null)。
基本操作
- 创建链表:创建一个空的队伍。
- 插入节点:在队伍的开头或结尾增加一个人。
- 删除节点:从队伍中移除某个人。
- 遍历链表:从队伍的第一个人开始,一个接一个地数过去。
- 搜索节点:找到队伍中某个特定的人。
模型展示
class Node {
int data; // 个人信息
Node next; // 下一个人的指针
Node(int data) {
this.data = data;
this.next = null;
}
}
class SinglyLinkedList {
Node head; // 队伍的第一个人
// 插入到队伍开头
public void insertAtHead(int data) {
Node newNode = new Node(data);
newNode.next = head; // 新人排在第一个人前面
head = newNode; // 新人变成队伍的第一个人
}
// 插入到队伍尾部
public void insertAtEnd(int data) {
Node newNode = new Node(data);
if (head == null) { // 如果队伍没有人
head = newNode; // 新人变成第一个人
return;
}
Node temp = head;
while (temp.next != null) { // 找到队伍的最后一个人
temp = temp.next;
}
temp.next = newNode; // 新人排在最后一个人后面
}
// 删除指定值的节点
public void deleteNode(int key) {
Node temp = head, prev = null;
// 如果第一个人就是要删除的人
if (temp != null && temp.data == key) {
head = temp.next; // 第一个人离开队伍
return;
}
// 搜索要删除的人
while (temp != null && temp.data != key) {
prev = temp;
temp = temp.next;
}
// 如果找不到要删除的人
if (temp == null) return;
// 断开连接
prev.next = temp.next;
}
// 遍历队伍
public void traverse() {
Node temp = head;
while (temp != null) {
System.out.print(temp.data + " -> ");
temp = temp.next;
}
System.out.println("null");
}
}
3. 双向链表(Doubly Linked List)
比喻:这是一条双向队伍,每个人不仅知道下一个人是谁,还知道前一个人是谁。
结构
- 节点:队伍中的人,包含自己的信息(数据)、指向下一个人的引用(指针)和指向前一个人的引用(指针)。
- 头节点:队伍的第一个人。
- 尾节点:队伍的最后一个人,指向空(null)。
基本操作
- 创建链表:创建一个空的队伍。
- 插入节点:在队伍的开头或结尾增加一个人。
- 删除节点:从队伍中移除某个人。
- 遍历链表:从队伍的第一个人开始,一个接一个地数过去,或者从最后一个人开始,倒着数回去。
- 搜索节点:找到队伍中某个特定的人。
模型展示
class DoublyNode {
int data; // 个人信息
DoublyNode next; // 下一个人的指针
DoublyNode prev; // 上一个人的指针
DoublyNode(int data) {
this.data = data;
this.next = null;
this.prev = null;
}
}
class DoublyLinkedList {
DoublyNode head; // 队伍的第一个人
// 插入到队伍开头
public void insertAtHead(int data) {
DoublyNode newNode = new DoublyNode(data);
if (head != null) {
head.prev = newNode;
}
newNode.next = head; // 新人排在第一个人前面
head = newNode; // 新人变成队伍的第一个人
}
// 插入到队伍尾部
public void insertAtEnd(int data) {
DoublyNode newNode = new DoublyNode(data);
if (head == null) { // 如果队伍没有人
head = newNode; // 新人变成第一个人
return;
}
DoublyNode temp = head;
while (temp.next != null) { // 找到队伍的最后一个人
temp = temp.next;
}
temp.next = newNode; // 新人排在最后一个人后面
newNode.prev = temp;
}
// 删除指定值的节点
public void deleteNode(int key) {
DoublyNode temp = head;
// 搜索要删除的人
while (temp != null && temp.data != key) {
temp = temp.next;
}
// 如果找不到要删除的人
if (temp == null) return;
// 断开连接
if (temp.prev != null) {
temp.prev.next = temp.next;
} else {
head = temp.next;
}
if (temp.next != null) {
temp.next.prev = temp.prev;
}
}
// 正向遍历队伍
public void traverseForward() {
DoublyNode temp = head;
while (temp != null) {
System.out.print(temp.data + " -> ");
temp = temp.next;
}
System.out.println("null");
}
// 反向遍历队伍
public void traverseBackward() {
DoublyNode temp = head;
while (temp != null && temp.next != null) {
temp = temp.next;
}
while (temp != null) {
System.out.print(temp.data + " -> ");
temp = temp.prev;
}
System.out.println("null");
}
}
在index = 3处添加33
删除index = 2的元素
4. 循环链表(Circular Linked List)
比喻:这是一条循环队伍,每个人不仅知道下一个人是谁,队伍的最后一个人还知道第一个人是谁,形成一个环。
结构
- 节点:队伍中的人,包含自己的信息(数据)和指向下一个人的引用(指针)。
- 头节点:队伍的第一个人。
- 尾节点:队伍的最后一个人,指向头节点。
基本操作
- 创建链表:创建一个空的队伍。
- 插入节点:在队伍的开头或结尾增加一个人。
- 删除节点:从队伍中移除某个人。
- 遍历链表:从队伍的第一个人开始,一个接一个地数过去,直到回到头部。
- 搜索节点:找到队伍中某个特定的人。
模型展示
class CircularNode {
int data; // 个人信息
CircularNode next; // 下一个人的指针
CircularNode(int data) {
this.data = data;
this.next = null;
}
}
class CircularLinkedList {
CircularNode head; // 队伍的第一个人
// 插入到队伍开头
public void insertAtHead(int data) {
CircularNode newNode = new CircularNode(data);
if (head == null) {
head = newNode;
newNode.next = head;
} else {
CircularNode temp = head;
while (temp.next != head) {
temp = temp.next;
}
temp.next = newNode;
newNode.next = head;
head = newNode;
}
}
// 插入到队伍尾部
public void insertAtEnd(int data) {
CircularNode newNode = new CircularNode(data);
if (head == null) {
head = newNode;
newNode.next = head;
} else {
CircularNode temp = head;
while (temp.next != head) {
temp = temp.next;
}
temp.next = newNode;
newNode.next = head;
}
}
// 删除指定值的节点
public void deleteNode(int
key) {
if (head == null) return;
CircularNode temp = head, prev = null;
// 如果第一个人就是要删除的人
if (temp.data == key) {
while (temp.next != head) {
temp = temp.next;
}
temp.next = head.next;
head = head.next;
return;
}
// 搜索要删除的人
while (temp.next != head && temp.data != key) {
prev = temp;
temp = temp.next;
}
// 如果找不到要删除的人
if (temp.data != key) return;
// 断开连接
prev.next = temp.next;
}
// 遍历队伍
public void traverse() {
if (head == null) return;
CircularNode temp = head;
do {
System.out.print(temp.data + " -> ");
temp = temp.next;
} while (temp != head);
System.out.println("(head)");
}
}
5. 时间复杂度和空间复杂度
- 时间复杂度:
- 插入操作:O(1)(头部插入),O(n)(尾部插入)
- 删除操作:O(1)(头部删除),O(n)(搜索删除)
- 搜索操作:O(n)
- 遍历操作:O(n)
- 空间复杂度:O(n),其中n是节点数。
6. 总结
链表就像一条队伍,单向链表是单向队伍,双向链表是双向队伍,循环链表是一条环形队伍。通过这些比喻和建模,希望你能更容易理解链表的结构和操作。