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

十二,数据结构-链表

定义

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

实现

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

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.dtcms.com/a/341167.html

相关文章:

  • BeyondWeb:大规模预训练合成数据的启示
  • 解决程序无响应自动重启
  • 高压柜无线测温:给智能化配电室装上“智能体温监测仪”
  • 前端基础知识操作系统系列 - 03(linux系统下 文件操作常用的命令有哪些)
  • C++ string(reserve , resize , insert , erase)
  • Clonezilla live 再生龙还原系统各个版本的不同
  • Sklearn 机器学习 房价预估 拆分训练集和测试集
  • Pydantic介绍(基于Python类型注解的数据验证和解析库)(BaseModel、校验邮箱校验EmailStr、BaseSettings)
  • SeaweedFS深度解析(五):裸金属集群部署(上)
  • Java 集合超详细教程
  • 循环神经网络(RNN)、LSTM 与 GRU (一)
  • 基于深度学习的订单簿异常交易检测与短期价格影响分析
  • 【深度学习】PyTorch中间层特征提取与可视化完整教程:从零开始掌握Hook机制与特征热力图
  • lua入门以及在Redis中的应用
  • 【ElasticSearch实用篇-03】QueryDsl高阶用法以及缓存机制
  • Java程序启动慢,DNS解析超时
  • 基于STM32的APP遥控视频水泵小车设计
  • K8S-Pod资源对象——标签
  • 【AI学习100天】Day08 使用Kimi每天问100个问题
  • 【指纹浏览器系列-绕过cdp检测】
  • 数据预处理:机器学习的 “数据整容术”
  • nginx-下载功能-状态统计-访问控制
  • 【数据结构】线性表——顺序表
  • 循环神经网络(RNN, Recurrent Neural Network)
  • Effective C++ 条款52:写了placement new也要写placement delete
  • 使用acme.sh自动申请AC证书,并配置自动续期,而且解决华为云支持问题,永久免费自动续期!
  • Spring Boot 定时任务与 xxl-job 灵活切换方案
  • 层在init中只为创建线性层,forward的对线性层中间加非线性运算。且分层定义是为了把原本一长个代码的初始化和运算放到一个组合中。
  • B站 韩顺平 笔记 (Day 24)
  • C++ std::optional 深度解析与实践指南