当前位置: 首页 > ai >正文

十二,数据结构-链表

定义

链表是最简单的基于节点的数据结构。节点表示散步在内存中的互相连接的数据,在链表中,每个节点都表示其中的一个元素。这个元素中除了信息内容之外,还有指向下一个/前一个节点的内存地址信息(指针形式)。

实现

这里先考虑单向链表的实现。首先是链表节点的实现:

class Node {
public:int data;unique_ptr<Node> nextNode;Node(int val) : data(val), nextNode(nullptr) {}
};

其实只使用上述的节点类即可以创建链表,但还需要一个简单的方法告知链表的开始位置,所以再创建一个LinkedList类,链表尾节点的下一节点指针为空,即nullptr。

class LinkedList {
private:unique_ptr<Node> firstNode;public:LinkedList(Node* node) : firstNode{ node } {}
};

链表的读取

数组的读取时间复杂度为O(1),而上述链表由于只记录了链表的第一个位置,因此只能遍历整个链表以读取指定位置的节点,其时间复杂度为O(N)。

	int read(int index) const {if (index < 0) {throw out_of_range("Index must be non-negative.");}Node* current = firstNode.get();int count = 0;while (current) {if (count == index) {return current->data;}current = current->nextNode.get();++count;}throw out_of_range("Index exceeds list length.");}

链表的查找

和读取过程类似,链表的查找也是从第一个节点开始遍历所有节点,并检查节点的值是否就是要查找的值。

	int indexOf(int value) const {Node* current = firstNode.get();int count = 0;while (current) {if (current->data == value) {return count;}current = current->nextNode.get();++count;}throw out_of_range("Index exceeds list length.");}

链表的插入

特定情况下,链表插入速度优于数组。当需要在数组头插入数据时由于需要右移所有元素,时间复杂度是O(N),但在链表头插入时间复杂度为O(1)。理论上在链表的任何位置插入一个节点,时间复杂度为O(1),但插入前还有个查找的过程,因此当需要在链表尾插入数据时,则其时间复杂度为O(N+1)。

	void insert(int index, int val) {if (index < 0) {throw out_of_range("Index must be non-negative.");}auto newNode = make_unique<Node>(val);if (index == 0) {newNode->nextNode = move(firstNode);firstNode = move(newNode);return;}Node* current = firstNode.get();int count = 0;while (current && count < index - 1) {current = current->nextNode.get();count++;}if (!current) {throw out_of_range("Index exceeds list length.");}newNode->nextNode = move(current->nextNode);current->nextNode = move(newNode);}

链表的删除

和插入一样,链表的删除也有其优势,即删除表头节点时,只需要让链表的firstNode指向第二个节点。而删除最后一个节点时,其步骤也只需要一步,但因为需要先遍历到最后一个节点,其时间复杂度仍然为O(N)。

	void removeAt(int index) {if (index < 0) {throw out_of_range("Index must be non-negative.");}if (!firstNode) {throw out_of_range("List is empty.");}if (index == 0) {firstNode = move(firstNode->nextNode);return;}Node* current = firstNode.get();int count = 0;while (current->nextNode && count < index - 1) {current = current->nextNode.get();count++;}if (!current->nextNode || count != index - 1) {throw out_of_range("Index exceeds list length.");}current->nextNode = move(current->nextNode->nextNode);}

如果设置为双链表的话,则在链表头或则链表尾进行删除和插入操作,都只需要花O(1)时间,和单链表不同的是,双链表节点需要额外存储前一个节点的内存地址的指针,同时双链表还要知道最后一个节点。双链表实现如下:

class DNode {
public:int data;unique_ptr<DNode> next;DNode* prev;DNode(int val) : data(val), next(nullptr), prev(nullptr) {}
};class DoublyLinkedList {
private:unique_ptr<DNode> head;DNode* tail;public:DoublyLinkedList() : head(nullptr), tail(nullptr) {}void append(int val) {auto newNode = make_unique<DNode>(val);if (!head) {tail = newNode.get();head = move(newNode);return;}newNode->prev = tail;tail->next = move(newNode);tail = tail->next.get();}void insert(int index, int val) {if (index < 0) throw out_of_range("Index must be non-negative.");auto newNode = make_unique<DNode>(val);if (index == 0) {newNode->next = move(head);if (newNode->next) newNode->next->prev = newNode.get();head = move(newNode);if (!tail) tail = head.get();return;}DNode* current = head.get();int count = 0;while (current && count < index - 1) {current = current->next.get();count++;}if (!current) throw out_of_range("Index exceeds list length.");newNode->next = move(current->next);if (newNode->next) newNode->next->prev = newNode.get();newNode->prev = current;current->next = move(newNode);if (!current->next->next) tail = current->next.get();}void removeAt(int index) {if (index < 0 || !head) throw out_of_range("Invalid index or empty list.");if (index == 0) {head = move(head->next);if (head) head->prev = nullptr;else tail = nullptr;return;}DNode* current = head.get();int count = 0;while (current && count < index) {current = current->next.get();count++;}if (!current) throw out_of_range("Index exceeds list length.");DNode* prevNode = current->prev;if (current->next) {current->next->prev = prevNode;prevNode->next = move(current->next);}else {prevNode->next = nullptr;tail = prevNode;}}
};

前文中提到的抽象数据结构队列也可以用双链表实现,不仅满足FIFO,同时其效率为O(1)。这里就不再赘述其实现。

http://www.xdnf.cn/news/18239.html

相关文章:

  • Docker Compose命令一览(Docker Compose指令、docker-compose命令)
  • 【基础-判断】@CustomDialog装饰器用于装饰自定义弹窗组件,使得弹窗可以动态设置内容及样式
  • ubuntu下安装vivado2015.2时报错解决方法
  • 1-2前端撸码前的准备,包管理工具和环境搭建
  • SPI 机制深度剖析:Java、Spring、Dubbo 的服务发现哲学与实战指南
  • 基于Java虚拟线程的高并发作业执行框架设计与性能优化实践指南
  • ReAct Agent:让AI像人类一样思考与行动的革命性框架
  • 使用 FastAPI 的 WebSockets 和 Elasticsearch 来构建实时应用
  • Python HTML/XML实体处理完全指南:从基础到安全工程实践
  • mac电脑软件左上角的关闭/最小化/最大化按钮菜单的宽度和高度是多少像素
  • 阿里云ECS服务器的公网IP地址
  • 服务器硬件电路设计之 SPI 问答(一):解密 SPI—— 从定义到核心特性
  • 【机器学习深度学习】AI大模型高并发挑战:用户负载部署策略
  • 雷卯针对香橙派Orange Pi 3B开发板防雷防静电方案
  • 运用平均值填充后的数据进行模型预测
  • 计算机毕设Spark项目实战:基于大数据技术的就业数据分析系统Django+Vue开发指南
  • 函数式编程“闭包”概念深入解析
  • 【LeetCode 热题 100】279. 完全平方数——(解法三)空间优化
  • 应用在运行时,向用户索取(相机、存储)等权限,未同步告知权限申请的使用目的,不符合相关法律法规要求--教你如何解决华为市场上架难题
  • 手机截图如何优雅地放在word里
  • Hangfire定时部署(.NET 8 + SQL Server)
  • 读者写者问题
  • Linux多线程——线程池
  • Spark学习
  • MySQL基础操作
  • 网络连接的核心机制
  • HTML+CSS:浮动详解
  • Python 文件操作与异常处理全解析
  • Zemax光学设计输出3D
  • idea进阶技能掌握, 使用自带HTTP测试工具,完全可替代PostMan