1. 推荐资源链接
- 图解数据结构——链表 - BobCheng - 博客园(有对应的可视化)
- 数据结构之链表
2. 学习内容
2.1 链表的基础知识
链表是计算机科学中一种基础的数据结构,它由一组节点组成,每个节点包含数据和指向下一个节点的指针(或引用)。与数组不同,链表中的元素在内存中不必是连续存储的,这使得链表在某些操作上比数组更灵活。
- 单向链表(Singly Linked List):
- 结构:每个节点包含两个部分:数据和指向下一个节点的指针。
- 特点:从第一个节点开始,只能沿着指针方向逐个访问后续节点,无法逆向访问。
- 代码示例:
#include <iostream>
using namespace std;
class Node {
public:
int data;
Node* next;
Node(int data) {
this->data = data;
this->next = nullptr;
}
};
class SinglyLinkedList {
public:
Node* head;
SinglyLinkedList() {
head = nullptr;
}
void append(int data) {
Node* newNode = new Node(data);
if (head == nullptr) {
head = newNode;
} else {
Node* current = head;
while (current->next != nullptr) {
current = current->next;
}
current->next = newNode;
}
}
void printList() {
Node* current = head;
while (current != nullptr) {
cout << current->data << " -> ";
current = current->next;
}
cout << "null" << endl;
}
};
int main() {
SinglyLinkedList list;
list.append(10);
list.append(20);
list.append(30);
list.printList();
return 0;
}
- 双向链表(Doubly Linked List):
- 结构:每个节点包含三个部分:数据、指向下一个节点的指针和指向上一个节点的指针。
- 特点:可以双向遍历链表,既可以从头节点向后遍历,也可以从尾节点向前遍历。
- 代码示例:
#include <iostream>
using namespace std;
class Node {
public:
int data;
Node* next;
Node* prev;
Node(int data) {
this->data = data;
this->next = nullptr;
this->prev = nullptr;
}
};
class DoublyLinkedList {
public:
Node* head;
DoublyLinkedList() {
head = nullptr;
}
void append(int data) {
Node* newNode = new Node(data);
if (head == nullptr) {
head = newNode;
} else {
Node* current = head;
while (current->next != nullptr) {
current = current->next;
}
current->next = newNode;
newNode->prev = current;
}
}
void printList() {
Node* current = head;
while (current != nullptr) {
cout << current->data << " <-> ";
current = current->next;
}
cout << "null" << endl;
}
};
int main() {
DoublyLinkedList list;
list.append(10);
list.append(20);
list.append(30);
list.printList();
return 0;
}
- 循环链表(Circular Linked List):
- 结构:链表的最后一个节点指向头节点,从而形成一个环。
- 特点:可以从任意一个节点开始进行循环遍历,适用于需要反复遍历的数据结构。
- 代码示例(单向循环链表):
#include <iostream>
using namespace std;
class Node {
public:
int data;
Node* next;
Node(int data) {
this->data = data;
this->next = nullptr;
}
};
class CircularLinkedList {
public:
Node* head;
CircularLinkedList() {
head = nullptr;
}
void append(int data) {
Node* newNode = new Node(data);
if (head == nullptr) {
head = newNode;
newNode->next = head;
} else {
Node* current = head;
while (current->next != head) {
current = current->next;
}
current->next = newNode;
newNode->next = head;
}
}
void printList() {
if (head == nullptr) return;
Node* current = head;
do {
cout << current->data << " -> ";
current = current->next;
} while (current != head);
cout << "(head)" << endl;
}
};
int main() {
CircularLinkedList list;
list.append(10);
list.append(20);
list.append(30);
list.printList();
return 0;
}
最常用的链表是:
-
无头单向非循环链表
-
带头双向循环链表
链表的基本操作
链表是由一系列节点连接在一起形成的结构。每个节点包含两个部分:数据域和指针域。数据域存储具体的数据,类似于数组中的元素;指针域则保存指向下一个节点的内存地址。在遍历链表时,我们通过读取当前节点的指针域找到下一个节点,并依次向下访问,从而完成增、删、改、查等基本操作。通过这种方式,链表实现了节点之间的串联,使得操作灵活且高效。
-
插入(Insertion):
- 可以在链表的开头、中间或末尾插入节点。
- 插入操作的时间复杂度通常为 O ( 1 ) O(1) O(1)(在链表头插入)或 O ( n ) O(n) O(n)(在任意位置插入)。
-
删除(Deletion):
- 可以删除链表中的指定节点。
- 删除操作的时间复杂度通常为 O ( 1 ) O(1) O(1)(删除头节点)或 O ( n ) O(n) O(n)(删除指定节点)。
-
查找(Search):
- 可以遍历链表查找某个特定值。
- 查找操作的时间复杂度为 O ( n ) O(n) O(n)。
-
遍历(Traversal):
- 从头节点开始,依次访问链表中的每个节点,直到结束。
链表的优缺点
优点:
-
动态大小:链表不需要预先分配固定大小的内存,可以随时扩展。
-
插入和删除操作高效:链表在任意位置插入或删除节点时,只需要调整指针,不需要移动其他节点。
缺点:
-
访问速度较慢:链表不支持随机访问(Random Access),查找元素需要从头开始遍历。
-
额外内存开销:每个节点除了存储数据外,还需要存储指针,占用额外的内存。
链表的应用场景
-
实现堆栈和队列:链表可以轻松实现堆栈(Stack)和队列(Queue),并支持动态调整大小。
-
符号表和字典:在哈希表中,链表常用于处理冲突(例如拉链法)。
-
内存管理:在操作系统中,链表常用于管理空闲内存块的分配与回收。
2.2 链表的相关技巧
相关视频:ACM 金牌选手教如何做链表。力扣 No.61 旋转链表,真·动画教编程,适合语言初学者或编程新人。_哔哩哔哩_bilibili
-
初始化一个根节点,指向链表头
-
同时把最后一个节点指向的空指针也标注出来
链表的主要思路是:找到关键的指针