一、链表的基础概念
链表是一种线性表,但不像数组那样连续存储数据,而是通过指针将零散的内存块(节点)串联起来。
- 核心优势:插入 / 删除元素时无需移动其他元素,效率高(时间复杂度 O (1));内存动态分配,不会浪费空间。
- 核心劣势:访问元素需要从头遍历(时间复杂度 O (n)),不能像数组那样随机访问;每个节点需要额外存储指针,占用更多内存。
- C++ 中链表的两种使用方式:
- 手动实现自定义链表(理解底层原理);
- 使用 STL 自带的
std::list(实际开发首选)。
二、手动实现单向链表(核心重点)
单向链表的每个节点包含两部分:数据域(存储数据)和指针域(指向下一个节点),最后一个节点的指针指向nullptr。
完整实现代码
cpp
运行
#include <iostream>
using namespace std;
// 定义链表节点结构体
struct ListNode {
int val; // 数据域:存储整型数据
ListNode* next; // 指针域:指向下一个节点
// 构造函数:方便创建节点
ListNode(int value) : val(value), next(nullptr) {}
};
// 定义链表类(封装核心操作)
class LinkedList {
private:
ListNode* head; // 头节点:指向链表第一个节点
public:
// 构造函数:初始化空链表
LinkedList() : head(nullptr) {}
// 析构函数:释放链表所有节点内存(避免内存泄漏)
~LinkedList() {
ListNode* current = head;
while (current != nullptr) {
ListNode* temp = current;
current = current->next;
delete temp; // 逐个释放节点
}
head = nullptr;
}
// 1. 尾部插入节点
void addAtTail(int val) {
ListNode* newNode = new ListNode(val);
// 链表为空时,头节点直接指向新节点
if (head == nullptr) {
head = newNode;
return;
}
// 遍历到链表尾部
ListNode* current = head;
while (current->next != nullptr) {
current = current->next;
}
current->next = newNode;
}
// 2. 头部插入节点
void addAtHead(int val) {
ListNode* newNode = new ListNode(val);
newNode->next = head; // 新节点指向原头节点
head = newNode; // 头节点更新为新节点
}
// 3. 删除指定值的第一个节点
void deleteNode(int val) {
// 空链表直接返回
if (head == nullptr) return;
// 要删除的是头节点
if (head->val == val) {
ListNode* temp = head;
head = head->next;
delete temp;
return;
}
// 遍历找到目标节点的前一个节点
ListNode* current = head;
while (current->next != nullptr && current->next->val != val) {
current = current->next;
}
// 找到目标节点,删除
if (current->next != nullptr) {
ListNode* temp = current->next;
current->next = current->next->next;
delete temp;
}
}
// 4. 遍历打印链表
void printList() {
ListNode* current = head;
while (current != nullptr) {
cout << current->val << " -> ";
current = current->next;
}
cout << "nullptr" << endl;
}
};
// 测试代码
int main() {
LinkedList list;
list.addAtTail(1); // 链表:1 -> nullptr
list.addAtTail(2); // 链表:1 -> 2 -> nullptr
list.addAtHead(0); // 链表:0 -> 1 -> 2 -> nullptr
list.printList(); // 输出:0 -> 1 -> 2 -> nullptr
list.deleteNode(1); // 删除值为1的节点,链表:0 -> 2 -> nullptr
list.printList(); // 输出:0 -> 2 -> nullptr
return 0;
}
关键代码解释
- 节点结构体
ListNode:val:存储节点数据(这里用 int,可替换为任意类型);next:ListNode*类型指针,指向下一个节点,初始化为nullptr。
- 链表类
LinkedList:head:头节点指针,是访问整个链表的入口;- 析构函数:必须手动释放每个节点的内存,否则会造成内存泄漏;
- 核心操作:尾部插入、头部插入、删除节点、遍历打印,覆盖了链表的基础使用场景。
三、STL 中的std::list(实际开发推荐)
C++ 标准库提供了封装好的双向链表std::list,无需手动管理内存,直接调用接口即可,示例如下:
cpp
运行
#include <iostream>
#include <list> // 包含list头文件
using namespace std;
int main() {
// 创建空的list,存储int类型
list<int> myList;
// 1. 尾部插入
myList.push_back(1);
myList.push_back(2);
// 2. 头部插入
myList.push_front(0);
// 此时链表:0 -> 1 -> 2
// 3. 遍历打印
cout << "遍历链表:";
for (int num : myList) { // 范围for循环
cout << num << " ";
}
cout << endl; // 输出:0 1 2
// 4. 删除指定值的元素
myList.remove(1); // 删除所有值为1的元素
// 5. 插入元素(在指定位置前插入)
auto it = myList.begin(); // 迭代器指向第一个元素
++it; // 移动到第二个元素位置
myList.insert(it, 3); // 在2前插入3,链表:0 -> 3 -> 2
// 6. 再次遍历
cout << "修改后遍历:";
for (auto it = myList.begin(); it != myList.end(); ++it) {
cout << *it << " ";
}
cout << endl; // 输出:0 3 2
return 0;
}
std::list核心接口说明
| 接口 | 功能 |
|---|---|
push_back() | 尾部插入元素 |
push_front() | 头部插入元素 |
pop_back() | 尾部删除元素 |
pop_front() | 头部删除元素 |
remove(val) | 删除所有值为 val 的元素 |
insert(it, val) | 在迭代器 it 位置前插入 val |
begin()/end() | 获取首尾迭代器 |
四、链表的常见类型
- 单向链表:只有一个指针域,只能从头遍历到尾(上面手动实现的就是);
- 双向链表:每个节点有
prev(指向前一个)和next(指向后一个)指针,可双向遍历(std::list就是双向链表); - 循环链表:最后一个节点的指针指向头节点,形成闭环,可无限遍历。
总结
- 链表的核心是节点 + 指针,通过指针串联零散内存,插入 / 删除效率高,访问效率低;
- 手动实现链表需重点关注内存释放(析构函数)和边界条件(空链表、删除头节点);
- 实际开发中优先使用 STL 的
std::list,无需手动管理内存,接口丰富且高效。
1545

被折叠的 条评论
为什么被折叠?



