news 2026/6/15 11:45:52

C/C++ 数据结构(六)链表迭代器与底层

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C/C++ 数据结构(六)链表迭代器与底层

本篇核心知识:自定义双向链表迭代器、链表删除 / 插入 / 拼接 / 反转操作、链表冒泡排序、STL 容器嵌套、容器底层与面试要点


一、双向链表迭代器详解

概念

迭代是对原生指针的封装类,自定义双向链表的迭代器本质就是封装节点指针,重载++--*->等运算符,模拟指针行为,实现统一遍历接口。迭代器隶属于对应容器,不同容器迭代器底层实现不同。

特性

  1. 双向链表迭代器支持前置 ++/ 后置 ++、前置 --/ 后置 --,可双向遍历;

  2. 前置自增 / 自减:先移动指针,再返回自身,可返回引用;

  3. 后置自增 / 自减:先保留当前状态,再移动指针,需生成临时对象,不能返回引用

  4. begin():指向链表第一个有效数据节点;

  5. end():指向尾结点(尾后位置),遵循 ST 标准前闭后开遍历区间;

  6. 迭代器仅操作节点指针,不修改节点数据本身。

代码示例(双向链表迭代器完整实现)

#include <iostream> using namespace std; ​ // 链表节点模板 template<typename T> struct Node { T data; Node<T>* prev; Node<T>* next; Node(const T& val) : data(val), prev(nullptr), next(nullptr) {} }; ​ // 双向链表类(内嵌迭代器) template<typename T> class DoubleList { public: // 自定义迭代器类(内嵌类) class Iterator{ private: Node<T>* cur; // 封装节点指针 public: Iterator(Node<T>* p = nullptr) : cur(p) {} ​ // 解引用 T& operator*() { return cur->data; } // 成员访问 Node<T>* operator->() { return cur; } ​ // 前置++ Iterator& operator++(){ cur = cur->next; return *this; } // 后置++(int占位符标识后置) Iterator operator++(int){ Iterator temp = *this; cur = cur->next; return temp; // 临时对象,不能返回引用 } // 前置-- Iterator& operator--(){ cur = cur->prev; return *this; } // 后置-- Iterator operator--(int){ Iterator temp = *this; cur = cur->prev; return temp; } // 判等、判不等 bool operator==(const Iterator& it) const{ return cur == it.cur; } bool operator!=(const Iterator& it) const{ return cur != it.cur; } }; ​ // 链表对外接口 Iterator begin() { return Iterator(head->next); } Iterator end() { return Iterator(tail); } ​ private: Node<T>* head; // 头结点(无数据) Node<T>* tail; // 尾结点(无数据) public: DoubleList(){ head = new Node<T>(T()); tail = new Node<T>(T()); head->next = tail; tail->prev = head; } ~DoubleList(){ Node<T>* p = head->next; while (p != tail){ Node<T>* del = p; p = p->next; delete del; } delete head; delete tail; } // 尾插 void push_back(const T& val); };

拓展

vector迭代器底层就是普通指针,无需重载过多运算符;链表、树等容器必须自定义迭代器,这是不同容器迭代器的核心区别。


二、链表删除操作(按迭代器删除)

概念

接收迭代器参数,删除该迭代器指向的节点,修改前后节点的指针指向,释放内存并返回下一个有效迭代器(解决迭代器失效问题)。

特性

  1. 操作核心:先保存待删节点的后继节点,再修改前驱与后继的指针连接;

  2. 步骤:保存后继 → 重定向指针 → 释放节点内存 → 返回后继迭代器;

  3. 带头尾结点链表无需判断边界,代码统一;

  4. 删除后原迭代器失效,必须使用返回值继续遍历。

代码示例

template<typename T> typename DoubleList<T>::Iterator DoubleList<T>::erase(Iterator pos) { Node<T>* delNode = pos.operator->(); // 获取待删节点 Node<T>* nextNode = delNode->next; // 保存后继节点 ​ // 重定向指针,完成断链 delNode->prev->next = nextNode; nextNode->prev = delNode->prev; ​ delete delNode; // 释放内存 return Iterator(nextNode); // 返回下一个迭代器 }

易错点

不可先释放节点再修改指针,会导致前驱节点失去后继地址,造成断链、内存泄漏。


三、区间删除

概念

删除[start, end)区间内所有节点(遵循前闭后开规则),仅修改首尾指针,批量摘除中间节点并释放内存。

特性

  1. 区间为左闭右开,end指向的节点不删除;

  2. 先记录区间首尾的前驱、后继节点,再批量释放中间节点;

  3. 操作完成后将两端节点重新连接。

代码示例

template<typename T> void DoubleList<T>::erase(Iterator start, Iterator end) { Node<T>* s = start.operator->(); Node<T>* e = end.operator->(); Node<T>* pre = s->prev; Node<T>* next = e; ​ // 批量释放区间内节点 Node<T>* cur = s; while (cur != e) { Node<T>* temp = cur; cur = cur->next; delete temp; } // 重连两端 pre->next = next; next->prev = pre; }

四、链表拼接操作

概念

将一个完整链表整体插入到当前链表指定迭代器位置,合并两条链表,原链表置空(不释放内存)

特性

  1. 仅修改指针指向,不拷贝节点数据,效率极高;

  2. 拼接完成后,原链表必须重置为空链表(头结点 next 指向尾结点),防止野指针;

  3. 区分:插入到迭代器之前/之后,指针修改逻辑不同。

代码示例

// 将链表lst插入到pos迭代器之前 template<typename T> void DoubleList<T>::splice(Iterator pos, DoubleList<T>& lst) { if (lst.begin() == lst.end()) return; // 原链表为空,直接返回 ​ Node<T>* cur = pos.operator->(); Node<T>* lstHead = lst.head->next; Node<T>* lstTail = lst.tail->prev; ​ // 完成拼接 cur->prev->next = lstHead; lstHead->prev = cur->prev; lstTail->next = cur; cur->prev = lstTail; ​ // 原链表置空,不释放头尾结点 lst.head->next = lst.tail; lst.tail->prev = lst.head; }

拓展

链表拼接相比数组拼接优势极大,数组需要批量拷贝数据,链表仅修改指针。


五、链表冒泡排序

概念

基于冒泡思想对双向链表排序,优先交换节点指针,不交换数据(大数据 / 自定义对象场景性能更优)。

特性

  1. 排序前提:元素个数 ≥ 2,空链表 / 单个节点无需排序;

  2. 冒泡规则:多层循环,外层控制排序趟数,内层相邻节点比较;

  3. 双向链表优势:可通过prev直接获取前驱节点,单链表无法做到;

  4. 每一趟排序都会将当前最大值 “冒泡” 到末尾,下一趟比较次数递减。

代码示例

template<typename T> void DoubleList<T>::sort() { int len = 0; // 统计链表有效节点个数 for (auto it = begin(); it != end(); ++it) len++; if (len < 2) return; ​ // 外层:排序趟数 for (int i = 0; i < len - 1; ++i) { Node<T>* p = head->next; // 内层:相邻节点比较 for (int j = 0; j < len - 1 - i; ++j) { if (p->data > p->next->data) { // 交换两个相邻节点(修改指针,不交换数据) Node<T>* p1 = p; Node<T>* p2 = p->next; Node<T>* p3 = p2->next; ​ p1->next = p3; p2->prev = p1->prev; p1->prev->next = p2; p2->next = p1; p1->prev = p3->prev; p3->prev = p1; } p = p->next; } } }

对比

数组冒泡:直接交换元素值,内存连续操作简单;

链表冒泡:交换节点指针,逻辑复杂,但海量数据下开销更小。


六、链表反转(头插法实现)

概念

利用头插法遍历原链表,逐个将节点插入新链表头部,实现整体反转。

特性

  1. 不新建节点,仅修改指针指向;

  2. 时间复杂度 (O(n)),一次遍历即可完成;

  3. 双向链表也可直接交换每个节点prevnext指针实现反转。

代码示例(头插法反转)

template<typename T> void DoubleList<T>::reverse() { Node<T>* newHead = head; Node<T>* cur = head->next; head->next = tail; tail->prev = head; ​ while (cur != tail) { Node<T>* temp = cur->next; // 头插 cur->next = newHead->next; newHead->next->prev = cur; newHead->next = cur; cur->prev = newHead; cur = temp; } }

七、容器嵌套与拓展应用

概念

STL / 自定义容器的模板参数可以是另一个容器,实现二维、多层数据结构。

特性

  1. 基础用法:vector<vector<int>>模拟二维数组;

  2. 进阶用法:vector<list<int>>模拟哈希表拉链法(解决哈希冲突经典方案);

  3. 模板类型约束:嵌套容器内的类型,必须支持拷贝、对应运算符重载。

代码示例

#include <vector> #include <list> using namespace std; ​ // 1. vector嵌套 = 二维动态数组 vector<vector<int>> vec2d; ​ // 2. vector + list 模拟哈希拉链法 vector<list<int>> hashTable;

拓展

C++ 标准库未直接提供哈希表容器(早期),开发中常使用vector+list组合自行实现,是底层开发常用技巧。


八、容器核心对比 & 选型总结

1. vector(顺序表)

底层:连续内存数组;

优点:下标随机访问 (O(1)),尾部操作效率高;

缺点:中间增删会挪动元素,频繁扩容影响性能;

适用:查询多、尾部操作多,尽量提前预留容量减少扩容。

2. list(双向链表)

底层:离散节点,前驱 + 后继双指针;

优点:任意位置增删仅改指针 (O(1)),无内存挪动;

缺点:不支持随机访问,遍历效率低;

适用:频繁随机增删、数据动态变化的场景。

通用编码规范

  1. 等值判断:nullptr == p(常量放左侧),避免漏写等号引发 bug;

  2. 指针操作:优先画图分析指针走向,杜绝断链、野指针;

  3. 内存管理:堆节点使用后必须delete,防止泄漏。


九、补充知识点:容器类型要求

概念

STL 容器 / 模板类对存储类型存在隐性约束。

特性

  1. 自定义类型必须支持拷贝构造(容器添加元素会拷贝对象);

  2. 排序、比较场景,需要重载==<>等运算符;

  3. 类中包含指针成员时,必须实现深拷贝,防止内存错误。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/15 11:44:53

Windows 11旧电脑升级终极指南:如何让老设备焕发新生

Windows 11旧电脑升级终极指南&#xff1a;如何让老设备焕发新生 【免费下载链接】MediaCreationTool.bat Universal MCT wrapper script for all Windows 10/11 versions from 1507 to 21H2! 项目地址: https://gitcode.com/gh_mirrors/me/MediaCreationTool.bat 还在为…

作者头像 李华
网站建设 2026/6/15 11:41:52

哔哩下载姬:解锁B站8K超高清视频收藏的终极指南

哔哩下载姬&#xff1a;解锁B站8K超高清视频收藏的终极指南 【免费下载链接】downkyi 哔哩下载姬downkyi&#xff0c;哔哩哔哩网站视频下载工具&#xff0c;支持批量下载&#xff0c;支持8K、HDR、杜比视界&#xff0c;提供工具箱&#xff08;音视频提取、去水印等&#xff09;…

作者头像 李华
网站建设 2026/6/15 11:39:50

终极指南:4步免费让你的老Mac运行最新macOS系统

终极指南&#xff1a;4步免费让你的老Mac运行最新macOS系统 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 还在为老Mac无法升级最新系统而烦恼吗&#xff1f…

作者头像 李华
网站建设 2026/6/15 11:37:00

遗传算法三大核心参数:选择强度、交叉策略与变异率的工业级调优指南

1. 项目概述&#xff1a;为什么“遗传算法第二讲”比第一讲更值得你花时间重读“遗传算法”这四个字&#xff0c;十年前在高校课堂里是《人工智能导论》最后一章的冷门配角&#xff0c;今天却已悄然渗透进电商推荐系统的排序逻辑、新能源汽车电池管理系统的实时调度策略、甚至小…

作者头像 李华