1-6 列表:链表实现
一、链表的基本概念
什么是链表
链表是一种线性数据结构,元素存储在节点中,每个节点包含数据域和指针域,通过指针将节点串联起来。
数组 vs 链表 内存布局对比:
数组: [A][B][C][D][E] 连续内存,支持随机访问
↑
基地址
链表: [A|•]-->[B|•]-->[C|•]-->[D|•]-->[E|⊘] 非连续内存,只能顺序访问
↑
头指针(head) ⊥ 表示空指针(nullptr)
节点结构定义:
template <typename T>
struct Node
{
T data; // 数据域
Node* next; // 指针域,指向下一个节点
Node(const T& val) : data(val), next(nullptr) {}
};
二、基于链表的列表实现
| 编号 | 函数名 | 功能 | 时间复杂度 | 空间复杂度 |
|---|---|---|---|---|
| 2.1 | 构造函数 析构函数 |
创建空链表/指定初始化列表 释放所有节点内存 |
O(1) O(n) |
O(1) O(1) |
| 2.2 | pushBack | 在链表尾部添加元素 | O(n) / O(1)* | O(1) |
| 2.3 | pushFront | 在链表头部添加元素 | O(1) | O(1) |
| 2.4 | insert | 在指定位置插入元素 | O(n) | O(1) |
| 2.5 | popBack | 删除尾部元素 | O(n) | O(1) |
| 2.6 | popFront | 删除头部元素 | O(1) | O(1) |
| 2.7 | remove | 删除指定位置元素 | O(n) | O(1) |
| 2.8 | removeByValue | 查找并删除第一个匹配元素 | O(n) | O(1) |
| 2.9 | operator[] at front back |
访问元素(链表不支持O(1)随机访问) 安全访问 访问首元素 访问尾元素 |
O(n) O(n) O(1) O(n) / O(1)* |
O(1) |
| 2.10 | find contains |
线性查找 | O(n) | O(1) |
| 2.11 | clear empty |
清空链表 判断是否为空 |
O(n) O(1) |
O(1) O(1) |
| 2.12 | 拷贝控制 | 深拷贝/移动语义 | O(n)/O(1) | O(n)/O(1) |
| 2.13 | begin end |
迭代器支持 | O(1) | O(1) |
*带尾指针优化后可达 O(1)
2.1 节点结构与基础框架
#include <iostream>
#include <stdexcept>
#include <initializer_list>
template <typename T>
class LinkedList
{
private:
// 节点结构
struct Node
{
T data;
Node* next;
Node(const T& val)
: data(val)
, next(nullptr)
{}
};
Node* m_head; // 头指针
Node* m_tail; // 尾指针(优化尾部操作)
int m_size; // 节点数量
public:
// 默认构造函数
LinkedList()
: m_head(nullptr)
, m_tail(nullptr)
, m_size(0)
{}
// 初始化列表构造
LinkedList(std::initializer_list<T> initList)
: m_head(nullptr), m_tail(nullptr), m_size(0)
{
for (const auto& val : initList)
{
pushBack(val);
}
}
// 析构函数
~LinkedList()
{
clear();
}
// 禁止拷贝(或实现深拷贝,见2.12)
// 基础功能实现...
};
2.2 尾部添加 (pushBack)
带尾指针优化,O(1) 时间复杂度
// 在尾部添加元素 - O(1)
void pushBack(const T& value)
{
Node* newNode = new Node(value);
if (m_head == nullptr)
{
// 空链表
m_head = m_tail = newNode;
}
else
{
// 非空链表,尾插
m_tail->next = newNode;
m_tail = newNode;
}
m_size++;
}
// 可视化演示
初始: 空链表 head=nullptr, tail=nullptr
pushBack(10): [10] head=tail=10
pushBack(20): [10]-->[20] tail=20
pushBack(30): [10]-->[20]-->[30] tail=30
2.3 头部添加 (pushFront)
// 在头部添加元素 - O(1)
void pushFront(const T& value)
{
Node* newNode = new Node(value);
newNode->next = m_head;
m_head = newNode;
if (m_tail == nullptr)
{
// 原链表为空,尾指针也要更新
m_tail = newNode;
}
m_size++;
}
// 可视化演示
初始: [10]-->[20]-->[30]
pushFront(5): [5]-->[10]-->[20]-->[30]
↑
head更新
2.4 指定位置插入 (insert)
// 在指定索引处插入 - O(n)
void insert(int index, const T& value)
{
if (index < 0 || index > m_size)
{
throw std::out_of_range("Insert index out of range");
}
if (index == 0)
{
pushFront(value);
return;
}
if (index == m_size)
{
pushBack(value);
return;
}
// 找到第 index-1 个节点
Node* prev = m_head;
for (int i = 0; i < index - 1; i++)
{
prev = prev->next;
}
Node* newNode = new Node(value);
newNode->next = prev->next;
prev->next = newNode;
m_size++;
}
// 可视化演示: 在索引1处插入X
初始: [A]-->[B]-->[C]-->[D] 在index=1插入X
步骤1: 找到prev = A (index-1 = 0)
步骤2: newNode->next = B
步骤3: A->next = X
结果: [A]-->[X]-->[B]-->[C]-->[D]
2.5 尾部删除 (popBack)
// 删除尾部元素 - O(n) 需要找到倒数第二个节点
void popBack()
{
if (isEmpty())
{
throw std::runtime_error("Cannot pop from empty list");
}
if (m_head == m_tail)
{
// 只有一个节点
delete m_head;
m_head = m_tail = nullptr;
}
else
{
// 找到倒数第二个节点
Node* prev = m_head;
while (prev->next != m_tail)
{
prev = prev->next;
}
delete m_tail;
m_tail = prev;
m_tail->next = nullptr;
}
m_size--;
}
// 可视化演示
初始: [A]-->[B]-->[C]-->[D] tail=D
步骤1: 找到prev = C (C->next == D)
步骤2: delete D
步骤3: C->next = nullptr, tail = C
结果: [A]-->[B]-->[C] tail=C
2.6 头部删除 (popFront)
// 删除头部元素 - O(1)
void popFront()
{
if (isEmpty())
{
throw std::runtime_error("Cannot pop from empty list");
}
Node* temp = m_head;
m_head = m_head->next;
delete temp;
if (m_head == nullptr)
{
// 链表变空
m_tail = nullptr;
}
m_size--;
}
2.7 指定位置删除 (remove)
// 删除指定索引元素 - O(n)
void remove(int index)
{
checkIndex(index);
if (index == 0)
{
popFront();
return;
}
// 找到第 index-1 个节点
Node* prev = m_head;
for (int i = 0; i < index - 1; i++)
{
prev = prev->next;
}
Node* toDelete = prev->next;
prev->next = toDelete->next;
// 如果删除的是尾节点,更新tail
if (toDelete == m_tail)
{
m_tail = prev;
}
delete toDelete;
m_size--;
}
2.8 按值删除 (removeByValue)
// 查找并删除第一个匹配元素 - O(n)
bool removeByValue(const T& value)
{
if (isEmpty()) return false;
// 检查头节点
if (m_head->data == value)
{
popFront();
return true;
}
// 遍历查找
Node* prev = m_head;
while (prev->next != nullptr && prev->next->data != value)
{
prev = prev->next;
}
if (prev->next == nullptr)
{
return false; // 未找到
}
Node* toDelete = prev->next;
prev->next = toDelete->next;
if (toDelete == m_tail)
{
m_tail = prev;
}
delete toDelete;
m_size--;
return true;
}
2.9 元素访问
⚠️ 链表不支持 O(1) 随机访问,必须遍历
// 下标访问 - O(n) 不推荐频繁使用
T& operator[](int index)
{
return getNode(index)->data;
}
const T& operator[](int index) const
{
return getNode(index)->data;
}
// 安全访问 - O(n)
T& at(int index)
{
checkIndex(index);
return getNode(index)->data;
}
// 获取指定索引节点 - O(n)
Node* getNode(int index) const
{
Node* curr = m_head;
for (int i = 0; i < index; i++)
{
curr = curr->next;
}
return curr;
}
// 首尾访问 - O(1)
T& front()
{
if (isEmpty()) throw std::runtime_error("List is empty");
return m_head->data;
}
T& back()
{
if (isEmpty()) throw std::runtime_error("List is empty");
return m_tail->data;
}
2.10 查找操作
注意:由于源代码采用ASCII字符编写,编程语言通常使用
!=来表示数学不等号,而≠并不属于ASCII字符集, 但此处写在markdown代码块中显示出来却是≠。
// 线性查找 - O(n)
int find(const T& value) const
{
Node* curr = m_head;
int index = 0;
while (curr != nullptr)
{
if (curr->data == value)
{
return index;
}
curr = curr->next;
index++;
}
return -1;
}
bool contains(const T& value) const
{
return find(value) != -1;
}
2.11 容量管理
// 清空链表 - O(n)
void clear()
{
Node* curr = m_head;
while (curr != nullptr)
{
Node* temp = curr;
curr = curr->next;
delete temp;
}
m_head = m_tail = nullptr;
m_size = 0;
}
int size() const { return m_size; }
bool isEmpty() const { return m_size == 0; }
2.12 拷贝控制(深拷贝)
// 拷贝构造函数 - O(n)
LinkedList(const LinkedList& other)
: m_head(nullptr), m_tail(nullptr), m_size(0)
{
Node* curr = other.m_head;
while (curr != nullptr)
{
pushBack(curr->data);
curr = curr->next;
}
}
// 拷贝赋值 - O(n)
LinkedList& operator=(const LinkedList& other)
{
if (this != &other)
{
clear();
Node* curr = other.m_head;
while (curr != nullptr)
{
pushBack(curr->data);
curr = curr->next;
}
}
return *this;
}
// 移动构造 - O(1)
LinkedList(LinkedList&& other) noexcept
: m_head(other.m_head)
, m_tail(other.m_tail)
, m_size(other.m_size)
{
other.m_head = other.m_tail = nullptr;
other.m_size = 0;
}
// 移动赋值 - O(1)
LinkedList& operator=(LinkedList&& other) noexcept
{
if (this != &other)
{
clear();
m_head = other.m_head;
m_tail = other.m_tail;
m_size = other.m_size;
other.m_head = other.m_tail = nullptr;
other.m_size = 0;
}
return *this;
}
2.13 迭代器支持
// 简单迭代器:直接返回节点指针
class Iterator
{
Node* m_node;
public:
Iterator(Node* node) : m_node(node) {}
T& operator*() const { return m_node->data; }
T* operator->() const { return &(m_node->data); }
Iterator& operator++()
{
m_node = m_node->next;
return *this;
}
bool operator!=(const Iterator& other) const
{
return m_node != other.m_node;
}
bool operator==(const Iterator& other) const
{
return m_node == other.m_node;
}
};
Iterator begin() { return Iterator(m_head); }
Iterator end() { return Iterator(nullptr); }
2.14 完整实现代码
#include <iostream>
#include <stdexcept>
#include <initializer_list>
template <typename T>
class LinkedList
{
private:
struct Node
{
T data;
Node* next;
Node(const T& val) : data(val), next(nullptr) {}
};
Node* m_head;
Node* m_tail;
int m_size;
void checkIndex(int index) const
{
if (index < 0 || index >= m_size)
{
throw std::out_of_range("Index out of range: " + std::to_string(index));
}
}
Node* getNode(int index) const
{
Node* curr = m_head;
for (int i = 0; i < index; i++)
{
curr = curr->next;
}
return curr;
}
public:
// 构造与析构
LinkedList() : m_head(nullptr), m_tail(nullptr), m_size(0) {}
LinkedList(std::initializer_list<T> initList)
: m_head(nullptr), m_tail(nullptr), m_size(0)
{
for (const auto& val : initList)
{
pushBack(val);
}
}
~LinkedList() { clear(); }
// 拷贝控制
LinkedList(const LinkedList& other)
: m_head(nullptr), m_tail(nullptr), m_size(0)
{
Node* curr = other.m_head;
while (curr != nullptr)
{
pushBack(curr->data);
curr = curr->next;
}
}
LinkedList& operator=(const LinkedList& other)
{
if (this != &other)
{
clear();
Node* curr = other.m_head;
while (curr != nullptr)
{
pushBack(curr->data);
curr = curr->next;
}
}
return *this;
}
LinkedList(LinkedList&& other) noexcept
: m_head(other.m_head), m_tail(other.m_tail), m_size(other.m_size)
{
other.m_head = other.m_tail = nullptr;
other.m_size = 0;
}
LinkedList& operator=(LinkedList&& other) noexcept
{
if (this != &other)
{
clear();
m_head = other.m_head;
m_tail = other.m_tail;
m_size = other.m_size;
other.m_head = other.m_tail = nullptr;
other.m_size = 0;
}
return *this;
}
// 添加操作
void pushBack(const T& value)
{
Node* newNode = new Node(value);
if (m_head == nullptr)
{
m_head = m_tail = newNode;
}
else
{
m_tail->next = newNode;
m_tail = newNode;
}
m_size++;
}
void pushFront(const T& value)
{
Node* newNode = new Node(value);
newNode->next = m_head;
m_head = newNode;
if (m_tail == nullptr) m_tail = newNode;
m_size++;
}
void insert(int index, const T& value)
{
if (index < 0 || index > m_size)
{
throw std::out_of_range("Insert index out of range");
}
if (index == 0) { pushFront(value); return; }
if (index == m_size) { pushBack(value); return; }
Node* prev = getNode(index - 1);
Node* newNode = new Node(value);
newNode->next = prev->next;
prev->next = newNode;
m_size++;
}
// 删除操作
void popBack()
{
if (isEmpty()) throw std::runtime_error("Cannot pop from empty list");
if (m_head == m_tail)
{
delete m_head;
m_head = m_tail = nullptr;
}
else
{
Node* prev = m_head;
while (prev->next != m_tail) prev = prev->next;
delete m_tail;
m_tail = prev;
m_tail->next = nullptr;
}
m_size--;
}
void popFront()
{
if (isEmpty()) throw std::runtime_error("Cannot pop from empty list");
Node* temp = m_head;
m_head = m_head->next;
delete temp;
if (m_head == nullptr) m_tail = nullptr;
m_size--;
}
void remove(int index)
{
checkIndex(index);
if (index == 0) { popFront(); return; }
Node* prev = getNode(index - 1);
Node* toDelete = prev->next;
prev->next = toDelete->next;
if (toDelete == m_tail) m_tail = prev;
delete toDelete;
m_size--;
}
bool removeByValue(const T& value)
{
if (isEmpty()) return false;
if (m_head->data == value) { popFront(); return true; }
Node* prev = m_head;
while (prev->next != nullptr && prev->next->data != value)
{
prev = prev->next;
}
if (prev->next == nullptr) return false;
Node* toDelete = prev->next;
prev->next = toDelete->next;
if (toDelete == m_tail) m_tail = prev;
delete toDelete;
m_size--;
return true;
}
// 访问操作
T& operator[](int index) { return getNode(index)->data; }
const T& operator[](int index) const { return getNode(index)->data; }
T& at(int index)
{
checkIndex(index);
return getNode(index)->data;
}
T& front()
{
if (isEmpty()) throw std::runtime_error("List is empty");
return m_head->data;
}
T& back()
{
if (isEmpty()) throw std::runtime_error("List is empty");
return m_tail->data;
}
// 查找
int find(const T& value) const
{
Node* curr = m_head;
int idx = 0;
while (curr != nullptr)
{
if (curr->data == value) return idx;
curr = curr->next;
idx++;
}
return -1;
}
bool contains(const T& value) const
{
return find(value) != -1;
}
// 容量管理
void clear()
{
Node* curr = m_head;
while (curr != nullptr)
{
Node* temp = curr;
curr = curr->next;
delete temp;
}
m_head = m_tail = nullptr;
m_size = 0;
}
int size() const { return m_size; }
bool isEmpty() const { return m_size == 0; }
// 迭代器
class Iterator {
Node* m_node;
public:
Iterator(Node* node) : m_node(node) {}
T& operator*() const { return m_node->data; }
Iterator& operator++() { m_node = m_node->next; return *this; }
bool operator!=(const Iterator& other) const { return m_node != other.m_node; }
};
Iterator begin() { return Iterator(m_head); }
Iterator end() { return Iterator(nullptr); }
// 输出
void print() const
{
std::cout << "[";
Node* curr = m_head;
while (curr != nullptr)
{
std::cout << curr->data;
if (curr->next != nullptr) std::cout << " -> ";
curr = curr->next;
}
std::cout << "] (size=" << m_size << ")\n";
}
};
三、测试代码
void testLinkedList()
{
std::cout << "=== LinkedList Test ===\n";
// 基本操作
LinkedList<int> list = {10, 20, 30};
list.print(); // [10 -> 20 -> 30]
list.pushFront(5);
list.pushBack(40);
list.print(); // [5 -> 10 -> 20 -> 30 -> 40]
// 插入删除
list.insert(2, 15);
list.print(); // [5 -> 10 -> 15 -> 20 -> 30 -> 40]
list.remove(2);
list.print(); // [5 -> 10 -> 20 -> 30 -> 40]
// 查找
std::cout << "Find 20: " << list.find(20) << "\n";
std::cout << "Contains 100: " << list.contains(100) << "\n";
// 遍历
std::cout << "Iterate: ";
for (auto& val : list)
{
std::cout << val << " ";
}
std::cout << "\n";
// 拷贝测试
LinkedList<int> copy = list;
copy.pushBack(999);
std::cout << "Original: "; list.print();
std::cout << "Copy: "; copy.print();
}
int main()
{
testLinkedList();
return 0;
}
四、数组 vs 链表 对比总结
| 特性 | 数组实现 (ArrayList) | 链表实现 (LinkedList) |
|---|---|---|
| 内存结构 | 连续内存 | 非连续内存,节点分散 |
| 随机访问 | O(1) | O(n) |
| 头部插入/删除 | O(n) | O(1) ✓ |
| 尾部插入/删除 | O(1) 均摊 | O(1) 带尾指针 |
| 中间插入/删除 | O(n) | O(n),但无需移动元素 |
| 缓存友好性 | 高(局部性好) | 低(指针跳转) |
| 内存开销 | 可能浪费(预留容量) | 额外指针开销(每个节点) |
| 适用场景 | 频繁随机访问,较少插入删除 | 频繁头尾操作,不确定数据量 |
选择建议:
- 需要频繁按索引访问 → 数组
- 需要频繁在头部操作 → 链表
- 数据量巨大且不确定 → 链表(无需预分配大块内存)

浙公网安备 33010602011771号