0. 前言
上一章的顺序表底层是数组,封装了增删查改的功能接口成为了顺序表。存储顺序表的空间是连续的,顺序表非常大时,内存可能无法提供这么大的连续空间,由此,链表就被创造了。在一个复杂的系统中,空闲的内存空间散落在内存各处,而链表通过指针很好的链接起了各个分散的内存空间。
如图:

在这里插入图片描述
内存空间分配并不连续,但是指针称为维系各个空间的桥梁
链表(linked list) 是一种线性数据结构,每个元素是一个节点对象,各个节点通过指针(或引用)相连接,指针(或引用)记录了下一个节点的内存地址,通过它可以从当前节点访问到下一个节点
1. 链表的分类
链表有多种结构,以下情况组合起来有
23
种:
- 单项或双向

在这里插入图片描述
- 带头或不带头

在这里插入图片描述
- 循环或不循环

在这里插入图片描述
这八种结构种,最常用的是最简单和最复杂的两种结构:
单链表(无头单项不循环链表):结构简单,一般作为其他数据结构的子结构

在这里插入图片描述
双向链表(带头双向循环链表):结构最复杂,实际使用中基本都是此结构,操作简单

在这里插入图片描述
注意:我们把这个带头的头称为哨兵位,哨兵位不存储有效数据(有些时候可能存储链表节点个数),位于链表第一个有效节点(头节点) 的前面。
现在我们来实现一下单链表和双向链表
2. 单链表的实现
2.1 链表的基本结构——节点(Node)
链表的最小单元是节点,C++用结构体或类定义,链表的一个节点包含指针域和数据域,指针域保存该节点指向下一个结点的地址,数据域保存该节点的有效值。还有一个头指针的概念:头指针是链表的门把手,指向链表头节点的指针变量,是链表的入口
代码语言:javascript
AI代码解释
// 节点结构体,存储数据和指向下一个结点的指针
struct Node {
T _value; // 数据域
Node* _next; // 指针域
// 节点构造函数 确保新节点创建时_next处于安全状态
Node(const T& val) : _value(val), _next(nullptr) {}
};
然后我们将节点放入类中,加入单链表的基本信息:头指针、节点个数
代码语言:javascript
AI代码解释
template <typename T>
class single_list{
private:
// 节点结构体,存储数据和指向下一个结点的指针
struct Node {
T _value;
Node* _next;
// 节点构造函数 确保新节点创建时_next处于安全状态
Node(const T& val) : _value(val), _next(nullptr) {}
};
Node* _head; // 头指针,指向链表的第一个节点
size_t _count; // 节点数量
};
我们引入类模板,能使链表的数据类型更多样化,需要什么样的类型我们就可以显式实例化什么类型的链表结构
2.2 核心操作详解
2.2.1 构造和析构
代码语言:javascript
AI代码解释
single_list() : _head(nullptr), _count(0) {}
利用初始化列表将链表初始化为空链表,方便后续操作
代码语言:javascript
AI代码解释
~single_list() { clear();}
调用清空函数删除所有节点,该函数在下文实现
2.2.2 插入操作
- 头插
代码语言:javascript
AI代码解释
// 头插 O(1)
void push_front(const T& val) {
// 创建新节点
Node* new_node = new Node(val);
new_node->_next = _head;
_head = new_node;
++_count;
}

在这里插入图片描述
创建新的节点,新节点的指针指向原先的头节点,然后更新头指针的指向
- 尾插
代码语言:javascript
AI代码解释
// 尾插 O(n)
void push_back(const T& val) {
Node* new_node = new Node(val);
// 如果是空链表 new_node就是新的头节点
if (!_head) {
_head = new_node;
}
else {
// 创建临时变量开始找尾节点
Node* cur = _head;
while (cur->_next != nullptr) cur = cur->_next;
cur->_next = new_node;
}
++_count;
}

在这里插入图片描述
空链表要做单独处理,新节点就是头节点了;链表非空,则要创建临时变量遍历整个链表找尾,一定不能用头指针遍历,使用头指针遍历会丢失头节点的数据
- 指定位置插入节点
代码语言:javascript
AI代码解释
// 插入指定位置 O(n)
void insert(int pos, const T& val) {
// 确保pos的合法性
// pos能取到_count,取到相当于尾插
assert(pos >= 0 && pos <= _count);
if (pos == 0) return push_front(val);
// 创建临时变量找pos位置的节点
Node* cur = _head;
// 找到pos的前一个节点 cur最终指向pos-1的位置
for (size_t i = 0; i < pos - 1; i++)// 循环pos - 1次
{
cur = cur->_next;
}
Node* new_node = new Node(val);
new_node->_next = cur->_next;
cur->_next = new_node;
++_count;
}

在这里插入图片描述对于用户输入的pos要做检查,pos的值代表索引,从0开始。pos == 0,可以复用头插法,而pos == _count则代表尾插,在下面的循环逻辑中,是找到pos的前一个节点,所以pos的边界判断都可以取到。然后更新指针指向即可.

1748

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



