特性/操作 | 单链表 (Singly Linked List) | 双链表 (Doubly Linked List) | 循环链表 (Circular Linked List) |
---|---|---|---|
节点结构 | 数据 + 指向下一个节点指针 | 数据 + 指向前一个节点指针 + 指向下一个节点指针 | 数据 + 指向下一个节点指针 |
头指针 | 指向第一个节点 | 指向第一个节点 | 指向第一个节点 |
遍历方向 | 单向遍历 | 双向遍历 | 从头节点开始,循环遍历 |
插入操作 | 在头部或尾部插入 | 在头部或尾部插入 | 在尾部插入,指向头节点 |
删除操作 | 需要找到前一个节点 | 直接访问前后节点 | 需调整指针保持循环结构 |
内存管理 | 节点删除需手动管理 | 节点删除需手动管理 | 节点删除需手动管理 |
应用场景 | 动态数据存储,栈 | 浏览器前进后退功能,音乐播放器 | 游戏回合制,任务调度 |
优缺点 | - 内存利用高 - 插入/删除简单 - 难以逆向遍历 | - 支持双向遍历 - 删除操作方便 - 内存开销大 | - 节省空间 - 循环结构适合循环任务 - 删除操作需注意循环 |
操作示例
操作 | 单链表例子 | 双链表例子 | 循环链表示例 |
---|---|---|---|
插入 | insert(30) 头部: 30 -> 10 -> 20 | insert(30) 头部: 30 <-> 10 <-> 20 | insert(30) 尾部: 10 -> 20 -> 30 -> 头 |
删除 | remove(10) 结果: 20 | remove(20) 结果: 10 <-> 30 | remove(10) 结果: 20 -> 30 -> 头 |
一、单链表(Singly Linked List)
1. 定义
单链表是一种线性数据结构,由一系列节点构成。每个节点包含数据部分和指向下一个节点的指针,最后一个节点的指针指向 nullptr
。
2. 原理
- 节点结构:每个节点包含数据和指向下一个节点的指针。
- 头指针:链表的起始点,通过它访问其他节点。
3. 操作
- 插入:在链表头部或尾部插入新节点。
- 删除:根据值删除节点,需调整指针。
- 遍历:从头节点开始,依次访问每个节点。
4. 应用场景
- 动态数据存储(如购物车)。
- 实现栈(后进先出)。
5. 示例代码
#include <iostream>
struct Node {
int data; // 节点数据
Node* next; // 指向下一个节点的指针
};
class SinglyLinkedList {
private:
Node* head; // 链表头指针
public:
SinglyLinkedList() : head(nullptr) {} // 初始化头指针
~SinglyLinkedList(); // 析构函数
void insert(int value); // 插入节点
void remove(int value); // 删除节点
void display() const; // 显示链表内容
};
// 析构函数,释放链表内存
SinglyLinkedList::~SinglyLinkedList() {
Node* current = head;
while (current) {
Node* next = current->next; // 保存下一个节点
delete current; // 删除当前节点
current = next; // 移动到下一个节点
}
}
// 在链表头插入新节点
void SinglyLinkedList::insert(int value) {
Node* newNode = new Node{value, head}; // 创建新节点
head = newNode; // 更新头指针
}
// 删除指定值的节点
void SinglyLinkedList::remove(int value) {
if (!head) return; // 链表为空直接返回
if (head->data == value) { // 如果要删除的是头节点
Node* temp = head;
head = head->next; // 更新头指针
delete temp; // 删除节点
return;
}
Node* current = head; // 当前节点
while (current->next && current->next->data != value) {
current = current->next; // 查找节点
}
if (current->next) { // 找到要删除的节点
Node* temp = current->next;
current->next = current->next->next; // 调整指针
delete temp; // 删除节点
}
}
// 显示链表内容
void SinglyLinkedList::display() const {
Node* current = head; // 当前节点
while (current) {
std::cout << current->data << " "; // 输出节点数据
current = current->next; // 移动到下一个节点
}
std::cout << std::endl; // 输出换行
}
int main() {
SinglyLinkedList sll; // 创建单链表实例
sll.insert(10); // 插入数据
sll.insert(20);
sll.display(); // 输出: 20 10
sll.remove(10); // 删除数据
sll.display(); // 输出: 20
return 0; // 程序结束
}
插入操作
-
在链表头插入:
-
插入 30
原链表: 10 -> 20 -> nullptr
插入后: 30 -> 10 -> 20 -> nullptr -
在链表尾插入:
-
插入 30
原链表: 10 -> 20 -> nullptr
插入后: 10 -> 20 -> 30 -> nullptr
删除操作
-
删除头节点:
-
删除 10
原链表: 10 -> 20 -> nullptr
删除后: 20 -> nullptr -
删除中间节点:
-
删除 20
原链表: 10 -> 20 -> 30 -> nullptr
删除后: 10 -> 30 -> nullptr
二、双链表(Doubly Linked List)
1. 定义
双链表是一种线性数据结构,每个节点包含数据、指向下一个节点的指针和指向前一个节点的指针。
2. 原理
- 节点结构:每个节点包含前后指针。
- 双向遍历:可以从任一节点向前或向后遍历。
3. 操作
- 插入:在链表头部或尾部插入新节点,需调整前后指针。
- 删除:根据值删除节点,需调整前后指针。
- 遍历:可以双向遍历链表。
4. 应用场景
- 浏览器的前进和后退功能。
- 音乐播放器的播放列表。
5. 示例代码
#include <iostream>
struct DNode {
int data; // 节点数据
DNode* next; // 指向下一个节点的指针
DNode* prev; // 指向前一个节点的指针
};
class DoublyLinkedList {
private:
DNode* head; // 链表头指针
public:
DoublyLinkedList() : head(nullptr) {} // 初始化头指针
~DoublyLinkedList(); // 析构函数
void insert(int value); // 插入节点
void remove(int value); // 删除节点
void display() const; // 显示链表内容
};
// 析构函数,释放链表内存
DoublyLinkedList::~DoublyLinkedList() {
DNode* current = head;
while (current) {
DNode* next = current->next; // 保存下一个节点
delete current; // 删除当前节点
current = next; // 移动到下一个节点
}
}
// 在链表尾部插入新节点
void DoublyLinkedList::insert(int value) {
DNode* newNode = new DNode{ value, nullptr, nullptr }; // 创建新节点
if (!head) { // 链表为空
head = newNode; // 更新头指针
return;
}
DNode* temp = head; // 当前节点
while (temp->next) {
temp = temp->next; // 遍历到链表尾部
}
temp->next = newNode; // 将新节点连接到尾部
newNode->prev = temp; // 更新新节点的前指针
}
// 删除指定值的节点
void DoublyLinkedList::remove(int value) {
if (!head) return; // 链表为空直接返回
if (head->data == value) { // 如果要删除的是头节点
DNode* temp = head;
head = head->next; // 更新头指针
if (head) head->prev = nullptr; // 更新新头节点的前指针
delete temp; // 删除节点
return;
}
DNode* current = head; // 当前节点
while (current && current->data != value) {
current = current->next; // 查找节点
}
if (current) { // 找到要删除的节点
if (current->next) current->next->prev = current->prev; // 更新后继节点的前指针
if (current->prev) current->prev->next = current->next; // 更新前驱节点的后指针
delete current; // 删除节点
}
}
// 显示链表内容
void DoublyLinkedList::display() const {
DNode* current = head; // 当前节点
while (current) {
std::cout << current->data << " "; // 输出节点数据
current = current->next; // 移动到下一个节点
}
std::cout << std::endl; // 输出换行
}
int main() {
DoublyLinkedList dll; // 创建双链表实例
dll.insert(1); // 添加到播放列表
dll.insert(2);
dll.insert(3);
dll.display(); // 输出: 1 2 3
dll.remove(2); // 从播放列表中移除
dll.display(); // 输出: 1 3
return 0; // 程序结束
}
插入操作
-
在链表头插入:
-
插入 30
原链表: 10 <-> 20
插入后: 30 <-> 10 <-> 20 -
在链表尾插入:
-
插入 30
原链表: 10 <-> 20
插入后: 10 <-> 20 <-> 30
删除操作
-
删除头节点:
-
删除 10
原链表: 10 <-> 20
删除后: 20 -
删除中间节点:
-
删除 20
原链表: 10 <-> 20 <-> 30
删除后: 10 <-> 30
三、循环链表(Circular Linked List)
1. 定义
循环链表是一种特殊的链表,最后一个节点的指针指向头节点,形成一个循环结构。
2. 原理
- 节点结构:与单链表相似,但最后一个节点的指针指向头节点。
- 遍历:可以无限循环遍历链表。
3. 操作
- 插入:在链表尾部插入新节点,需确保新节点指向头节点。
- 删除:根据值删除节点,需调整指针以保持循环结构。
- 遍历:从头节点开始,直到回到头节点。
4. 应用场景
- 游戏中的回合制。
- 任务调度。
5. 示例代码
#include <iostream>
struct CNode {
int data; // 节点数据
CNode* next; // 指向下一个节点的指针
};
class CircularLinkedList {
private:
CNode* head; // 链表头指针
public:
CircularLinkedList() : head(nullptr) {} // 初始化头指针
~CircularLinkedList(); // 析构函数
void insert(int value); // 插入节点
void remove(int value); // 删除节点
void display() const; // 显示链表内容
};
// 析构函数,释放链表内存
CircularLinkedList::~CircularLinkedList() {
if (!head) return; // 链表为空直接返回
CNode* current = head; // 当前节点
CNode* nextNode; // 下一个节点
do {
nextNode = current->next; // 保存下一个节点
delete current; // 删除当前节点
current = nextNode; // 移动到下一个节点
} while (current != head); // 遍历至回到头节点
}
// 在链表尾部插入新节点
void CircularLinkedList::insert(int value) {
CNode* newNode = new CNode{ value, nullptr }; // 创建新节点
if (!head) { // 如果链表为空
head = newNode; // 更新头指针
newNode->next = head; // 新节点指向自身
return;
}
CNode* temp = head; // 当前节点
while (temp->next != head) { // 遍历到链表尾部
temp = temp->next; // 移动到下一个节点
}
temp->next = newNode; // 将新节点连接到尾部
newNode->next = head; // 新节点指向头节点,形成循环
}
// 删除指定值的节点
void CircularLinkedList::remove(int value) {
if (!head) return; // 链表为空直接返回
CNode* current = head; // 当前节点
CNode* prev = nullptr; // 前驱节点
if (head->data == value) { // 如果要删除的是头节点
if (head->next == head) { // 如果链表只有一个节点
delete head; // 删除节点
head = nullptr; // 更新头指针
return;
}
while (current->next != head) { // 找到尾节点
current = current->next; // 移动到下一个节点
}
CNode* temp = head; // 保存头节点
head = head->next; // 更新头指针
current->next = head; // 更新尾节点的指针
delete temp; // 删除节点
return;
}
do {
prev = current; // 记录前驱节点
current = current->next; // 移动到下一个节点
} while (current != head && current->data != value); // 查找值
if (current != head) { // 找到要删除的节点
prev->next = current->next; // 删除节点
delete current; // 删除节点
}
}
// 显示链表内容
void CircularLinkedList::display() const {
if (!head) return; // 如果链表为空,直接返回
CNode* current = head; // 当前节点
do {
std::cout << current->data << " "; // 输出节点数据
current = current->next; // 移动到下一个节点
} while (current != head); // 遍历至回到头节点
std::cout << std::endl; // 输出换行
}
int main() {
CircularLinkedList cll; // 创建循环链表实例
cll.insert(1); // 模拟游戏回合
cll.insert(2);
cll.insert(3);
cll.display(); // 输出: 1 2 3
cll.remove(2); // 移除玩家 2
cll.display(); // 输出: 1 3
return 0; // 程序结束
}
插入操作
- 在链表尾插入:
- 插入 30
原链表: 10 -> 20 -> 头节点(10)
插入后: 10 -> 20 -> 30 -> 头节点(10)
删除操作
-
删除头节点:
-
删除 10
原链表: 10 -> 20 -> 30 -> 头节点(10)
删除后: 20 -> 30 -> 头节点(20) -
删除中间节点:
-
删除 20
原链表: 10 -> 20 -> 30 -> 头节点(10)
删除后: 10 -> 30 -> 头节点(10)