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

链表(C++)

这是本人第二次学习链表,第一次学习链表是在大一上的C语言课上,首次接触,感到有些难;第二次是在大一下学习数据结构时(就是这次),使用C++再次理解链表。同时,这也是开启数据结构学习写的第一篇文章,但愿以后有时间一直写下去。

当然,学习数据结构还是保持着学习计算机的基本素养——增、删、查、改。


一、为什么需要链表?

首先,理解数组与链表区别,数组是一块连续的内存空间,有了这块内存空间,可以通过数组索引计算出任意位置元素内存;而链表,不需要一块连续的内存空间,可以分散在各处,只需通过节点连接起来,这是相对于数组的好处,但是也有弊端,由于链表中每个元素不是连续挨着的,所以访问时,需要从头结点开始遍历直至找到你要的元素

二、单链表基本操作

首先,创建一条单链表:

class ListNode {
public:int val;ListNode* next;ListNode(int x):val(x),next(NULL){}
};ListNode* createLinkedList(std::vector<int> arr) {//输入数组,转换成单链表if (arr.empty()) {return nullptr;}ListNode* head = new ListNode(arr[0]);ListNode* cur = head;for (int i = 0; i < arr.size(); i++) {cur->next = new ListNode(arr[i]);cur = cur->next;}return head;
}
1、单链表查找、遍历、修改
ListNode* head = createLinkedList({1, 2, 3, 4, 5});
for (ListNode* p = head; p != nullptr; p = p->next) {std::cout << p->val << std::endl;
}

这是遍历一个单链表↑

如果是要通过索引访问或修改链表中的某个节点,也只能用 for 循环从头结点开始往后找,直到找到索引对应的节点,然后进行访问或修改。

2、增加

2.1头插

ListNode* head = createLinkedList({1, 2, 3, 4, 5}); 
ListNode* newHead = new ListNode(6);
newHead->next = head;
head = newHead; // 现在的链表 6 -> 1 -> 2 -> 3 -> 4 -> 5

2.2尾插

比头插只复杂一步,需要遍历到末尾,再插入

ListNode* head = createLinkedList(std::vector<int>{1, 2, 3, 4, 5});
ListNode* p = head;
while (p->next != nullptr) {p = p->next;
}
p->next = new ListNode(6);// 现在链表变成了 1 -> 2 -> 3 -> 4 -> 5 -> 6

2.3中间插入

在链表的中间插入,只需要找到前驱节点,然后插入

ListNode* head = createLinkedList({ 1,2,3,4,5 });
ListNode* p = head;
for (int i = 0; i < 2; i++) {p = p->next;
}
ListNode* newNode = new ListNode(66);
newNode->next = p->next;
p->next = newNode;// 现在链表变成了 1 -> 2 -> 3 -> 66 -> 4 -> 5
3、删除

3.1中间删除

还是找到要删除的节点的前一个节点,把前一个节点的next指针指向删除节点的next指针

ListNode* head = createLinkedList({1, 2, 3, 4, 5});
ListNode* p = head;
for (int i = 0; i < 2; i++) {p = p->next;
}
p->next = p->next->next;// 现在链表变成了 1 -> 2 -> 3 -> 5

这里不懂可以看看我之前发的链表文章(配图的那篇)链表 
3.2尾部删除

这种删除是最简单的,找到倒数第二个节点,将它的next指针设为null

ListNode* head = createLinkedList({1, 2, 3, 4, 5});
ListNode* p = head;
while (p->next->next != nullptr) {p = p->next;
}
p->next = nullptr;// 现在链表变成了 1 -> 2 -> 3 -> 4

3.3头部删除

ListNode* head = createLinkedList(vector<int>{1, 2, 3, 4, 5});
head = head->next;// 现在链表变成了 2 -> 3 -> 4 -> 5

第一次学习链表时,这里就出现了困惑,困惑出在第一个节点身上,原第一个节点的next还指向第二个,所以看起来没有删除,只是没有访问,是否会造成内存泄漏?但实际上,没有其它引用第一个节点,它就会被回收掉,当然也可以把第一个节点的next设为null,这就避免这个问题,如下:

ListNode* head = createLinkedList(vector<int>{1, 2, 3, 4, 5});
ListNode* oldHead = head;
head = head->next;
oldHead->next = nullptr;
delete oldHead;// 现在链表变成了 2 -> 3 -> 4 -> 5

这样就严谨了。

三、双链表基本操作

首先,创建双链表:

class DoublyListNode {
public:int val;DoublyListNode *next, *prev;DoublyListNode(int x) : val(x), next(NULL), prev(NULL) {}
};DoublyListNode* createDoublyLinkedList(vector<int>& arr) {if (arr.empty()) {return NULL;}DoublyListNode* head = new DoublyListNode(arr[0]);DoublyListNode* cur = head;// for 循环迭代创建双链表for (int i = 1; i < arr.size(); i++) {DoublyListNode* newNode = new DoublyListNode(arr[i]);cur->next = newNode;newNode->prev = cur;cur = cur->next;}return head;
}
1、遍历、查找、修改

对于双链表,从头节点或尾节点,向后或向前遍历

DoublyListNode* head = createDoublyLinkedList(new int[]{1, 2, 3, 4, 5});
DoublyListNode* tail = nullptr;
// 从头节点向后遍历双链表
for (DoublyListNode* p = head; p != nullptr; p = p->next) {cout << p->val << endl;tail = p;
}
// 从尾节点向前遍历双链表
for (DoublyListNode* p = tail; p != nullptr; p = p->prev) {cout << p->val << endl;
}

访问或修改节点时,可以根据索引是靠近头部还是尾部,选择合适的方向遍历,这样可以一定程度上提高效率。


2、增加

2.1头插
需要改变新节点和原头结点指针

DoublyListNode* head = createDoublyLinkedList({1, 2, 3, 4, 5});
DoublyListNode* newHead = new DoublyListNode(0);
newHead->next = head;
head->prev = newHead;
head = newHead; // 现在链表变成了 0 -> 1 -> 2 -> 3 -> 4 -> 5

头插步骤如图

2.2尾插

双链表尾插与单链表尾插一样,需要遍历到最后一个节点,如果已知尾节点的引用,就简单很多了(不需要遍历了)

DoublyListNode* head = createDoublyLinkedList({1, 2, 3, 4, 5});
DoublyListNode* tail = head;
while (tail->next != nullptr) {tail = tail->next;
}
DoublyListNode* newNode = new DoublyListNode(6);
tail->next = newNode;
newNode->prev = tail;
// 更新尾节点引用
tail = newNode;  // 现在链表变成了 1 -> 2 -> 3 -> 4 -> 5 -> 6

比较简单,先是尾节点的next指针指向newNode,然后newNode的prev指针再指向原tail,这是一个互逆过程,即我指向你,你也需要指向我,这样才符合双链表的定义。

最后,更新一下尾节点,方便下一次在尾部直接插入,重复执行上面的操作。

2.3中间插入

双链表的中间插入需要同时关注前驱指针和后继指针

如:把元素 66 插入到索引 3(第 4 个节点)的位置

DoublyListNode* head = createDoublyLinkedList({1, 2, 3, 4, 5});
DoublyListNode* p = head;
for (int i = 0; i < 2; i++) {p = p->next;
}
DoublyListNode* newNode = new DoublyListNode(66);
newNode->next = p->next;
newNode->prev = p;p->next->prev = newNode;
p->next = newNode;  // 现在链表变成了 1 -> 2 -> 3 -> 66 -> 4 -> 5

下面,用画图解释一下:

第一步,初始化和p的遍历(遍历到第三个节点)

第二步,newNode->next = p->next;(红色箭头)

第三步,newNode->prev = p;(绿色箭头)

第四步,p->next->prev = newNode;(蓝色)

第四步,p->next = newNode;(黄色)

这样,66就成功插入到了第三个节点之后


3、删除

3.1中间删除

DoublyListNode* head = createDoublyLinkedList(std::vector<int>{1, 2, 3, 4, 5});
// 删除第 4 个节点
// 先找到第 3 个节点
DoublyListNode* p = head;
for (int i = 0; i < 2; ++i) {p = p->next;
}
// 现在 p 指向第 3 个节点,我们将它后面那个节点摘除出去
DoublyListNode* toDelete = p->next;
// 把 toDelete 从链表中摘除
p->next = toDelete->next;
toDelete->next->prev = p;
// 把 toDelete 的前后指针都置为 null 是个好习惯(可选)
toDelete->next = nullptr;
toDelete->prev = nullptr;  // 现在链表变成了 1 -> 2 -> 3 -> 5

中间删除比较复杂,还是采用画图的方法理解

第一步,还是初始化和遍历

第二步,摘出要删除的节点(即4)

第三步,p->next = toDelete->next;(蓝色)

第四步,toDelete->next->prev = p;(黄色)

其实,到这里已经结束了,但还是最开始的问题,为了规范,把要删除的节点前驱和后继指针置为null

3.2头删

DoublyListNode* head = createDoublyLinkedList({1, 2, 3, 4, 5});
DoublyListNode* toDelete = head;
head = head->next;
head->prev = nullptr;toDelete->next = nullptr;  // 现在链表变成了 2 -> 3 -> 4 -> 5

3.3尾删

在单链表中,由于缺乏前驱指针,所以删除尾节点时需要遍历到倒数第二个节点,操作它的 next 指针,才能把尾节点摘除出去。但在双链表中,由于每个节点都存储了前驱节点的指针,所以我们可以直接操作尾节点,把它自己从链表中摘除:

DoublyListNode* head = createDoublyLinkedList(std::vector<int>{1, 2, 3, 4, 5});
DoublyListNode* p = head;
while (p->next != nullptr) {p = p->next;
}
// 现在 p 指向尾节点
// 把尾节点从链表中摘除
p->prev->next = nullptr;
// 把被删结点的指针都断开是个好习惯(可选)
p->prev = nullptr;   // 现在链表变成了 1 -> 2 -> 3 -> 4

双链表的头删和尾删比较简单,所以就没画图

以上就是关于单双链表的基本操作,学识浅薄,错误内容还望指正

相关文章:

链表(C++)

这是本人第二次学习链表&#xff0c;第一次学习链表是在大一上的C语言课上&#xff0c;首次接触&#xff0c;感到有些难&#xff1b;第二次是在大一下学习数据结构时&#xff08;就是这次&#xff09;&#xff0c;使用C再次理解链表。同时&#xff0c;这也是开启数据结构学习写…...

nginx 设置隐藏版本号

Nginx默认会在Server头里包含版本信息&#xff0c;比如“nginx/1.18.0”&#xff0c;这可能存在安全隐患&#xff0c;因为攻击者知道了版本号后&#xff0c;可以针对特定版本的漏洞进行攻击。所以&#xff0c;隐藏版本号是一个常见的安全措施。 可通过在http块里加上server_to…...

29_项目

目录 http.js 1、先注册账号 register.html 2、再登录 login.html 3、首页 index.html 4 详情 details.html cart.html css index.css register.css details.css 演示 进阶 http.js let baseURL "http://localhost:8888"; let resgiterApi baseURL &…...

排序算法1--插入排序

目录 1.常见排序算法 2.排序算法的预定函数 2.1交换函数 2.2测试算法运行时间的函数 3.插入排序 3.1直接插入排序 3.2希尔排序 3.3插入排序的时间复杂度分析 4.总结 1.常见排序算法 我将分别讲解五种排序算法&#xff0c;但是不代表只有五种固定的代码&#xff0c;之后…...

PolarDB数据库表恢复实战指南:通过控制台恢复表的完整操作流程

在数据库运维过程中,表数据恢复是一项常见且关键的操作。本文将详细介绍如何通过阿里云PolarDB控制台从备份中恢复特定表,并通过表重命名操作确保业务平稳过渡。 一、背景介绍 在日常数据库运维中,我们可能会遇到以下场景需要从备份中恢复表: 表数据被误删或损坏需要获取历…...

业之峰与宏图智能战略携手,开启家装数字化新篇章

3月8日&#xff0c;业之峰装饰集团董事长张钧携高管团队与宏图智能董事长庭治宏及核心团队&#xff0c;在业之峰总部隆重举行了战略合作签约仪式&#xff0c;标志着双方将携手探索业之峰的数字化转型之路&#xff0c;共同推动家装行业的变革与发展。 近年来&#xff0c;家装行业…...

matplotlib标题比x,y轴字体大,明明标题字体更大?

原始代码&#xff1a; plt.xlabel(训练轮次&#xff08;Epochs&#xff09;, fontsize14, fontweightbold, fontpropertieschinese_font) # 设置中文字体、加大、加粗 plt.ylabel(R值, fontsize14, fontweightbold, fontpropertieschinese_font) # 设置中文字体、加大、加粗…...

【云原生】docker 搭建单机PostgreSQL操作详解

目录 一、前言 二、前置准备 2.1 服务器环境 2.2 docker环境 三、docker安装PostgreSQL过程 3.1 获取PostgreSQL镜像 3.2 启动容器 3.2.1 创建数据卷目录 3.2.2 启动pg容器 3.3 客户端测试连接数据库 四、创建数据库与授权 4.1 进入PG容器 4.2 PG常用操作命令 4.2…...

解决 Gradle 构建错误:Could not get unknown property ‘withoutJclOverSlf4J’

解决 Gradle 构建错误&#xff1a;Could not get unknown property ‘withoutJclOverSlf4J’ 在构建 Spring 源码或其他基于 Gradle 的项目时&#xff0c;可能会遇到如下错误&#xff1a; Could not get unknown property withoutJclOverSlf4J for object of type org.gradle…...

免费使用!OpenAI 全量开放 GPT-4o 图像生成能力!

2025年3月26日&#xff0c;OpenAI正式推出GPT-4o原生图像生成功能&#xff0c;这一更新不仅标志着多模态AI技术的重大突破&#xff0c;更引发了全球AI厂商的激烈竞争。从免费用户到企业开发者&#xff0c;从创意设计到科学可视化&#xff0c;GPT-4o正在重塑图像生成的边界。本文…...

jetson orin nano super AI模型部署之路(三)stable diffusion部署

先看一下部署后的界面和生成的图片。 在jetson orin nano super上部署stable diffusion比较简单&#xff0c;有现成的docker image和代码可用。 docker image拉取 使用的docker image是dustynv/stable-diffusion-webui&#xff0c;对于jetson orin nano super的jetpack6.2来说…...

深入理解:阻塞IO、非阻塞IO、水平触发与边缘触发

深入理解&#xff1a;阻塞IO、非阻塞IO、水平触发与边缘触发 在网络编程和并发处理中&#xff0c;理解不同的 I/O 模型和事件通知机制至关重要。本文将深入探讨阻塞IO&#xff08;Blocking IO&#xff09;、非阻塞IO&#xff08;Non-Blocking IO&#xff09;、水平触发&#x…...

WebRTC的ICE之TURN协议的交互流程中继转发Relay媒体数据的turnserver的测试

WebRTC的ICE之TURN协议的交互流程和中继转发Relay媒体数据的turnserver的测试 WebRTC的ICE之TURN协议的交互流程中继转发Relay媒体数据的turnserver的测试 WebRTC的ICE之TURN协议的交互流程和中继转发Relay媒体数据的turnserver的测试前言一、TURN协议1、连接Turn Server 流程①…...

HTTP---基础知识

天天开心&#xff01;&#xff01;&#xff01; 文章目录 一、HTTP基本概念1. 什么是HTTP&#xff0c;又有什么用&#xff1f;2. 一次HTTP请求的过程3.HTTP的协议头4.POST和GET的区别5. HTTP状态码6.HTTP的优缺点 二、HTTP的版本演进1.各个版本的应用场景2、注意要点 三、HTTP与…...

Redis6数据结构之List类型

redis的List类型底层结构是双向链表&#xff0c;插入删除时间复杂度O(1)快,查找为O(n)慢。 应用场景&#xff1a;简单队列、最新评论列表、非实时排行榜&#xff08;定时计算榜单&#xff0c;如笔记本日销榜单&#xff09;。 常用命令&#xff1a; lpush将一个或多个值从左边…...

DeepSeek接入飞书多维表格,效率起飞!

今天教大家把DeepSeek接入飞书表格使用。 准备工作&#xff1a;安装并登录飞书&#xff1b;可以准备一些要处理的数据&#xff0c;确保数据格式正确&#xff0c;如 Excel、CSV 等&#xff0c;也可直接存储到飞书多维表格。 创建飞书多维表格&#xff1a;打开飞书&#xff0c;点…...

[FGPA基础学习]分秒计数器的制作

分秒计数器设计 ​ 本次实验内容为&#xff1a;DE2-115板子上用 Verilog编程实现一个 分秒计数器&#xff0c;并具备按键暂停、按键消抖功能 一、系统架构设计 顶层模块划分 顶层模块&#xff08;top&#xff09; ├── 按键消抖模块&#xff08;key_debounce&#xff09; ├…...

【Spring Boot 与 Spring Cloud 深度 Mape 之十】体系整合、部署运维与进阶展望

【Spring Boot 与 Spring Cloud 深度 Mape 之十】体系整合、部署运维与进阶展望 #微服务实战 #Docker #Kubernetes #SpringSecurity #OAuth2 #分布式事务 #Seata #ServiceMesh #总结 #SpringCloud #SpringBoot 系列终章&#xff1a;经过前九篇 [【深度 Mape 系列】] 的系统学习…...

【商城实战(97)】ELK日志管理系统的全面应用

【商城实战】专栏重磅来袭!这是一份专为开发者与电商从业者打造的超详细指南。从项目基础搭建,运用 uniapp、Element Plus、SpringBoot 搭建商城框架,到用户、商品、订单等核心模块开发,再到性能优化、安全加固、多端适配,乃至运营推广策略,102 章内容层层递进。无论是想…...

高并发系统下的订单号生成服务设计与实现

目录 引言 订单号设计的关键考量因素 基础需求分析 唯一性保障 数据量预估 可读性设计 系统架构考量 分库分表兼容 可扩展性设计 技术选型与比较 性能优化 高可用性保障 实践案例&#xff1a;高并发系统订单号结构设计 结构详解 业务类型标识(2位) 唯一标识部分…...

每日算法-250329

记录今天学习的三道算法题&#xff1a;两道滑动窗口和一道栈的应用。 2904. 最短且字典序最小的美丽子字符串 题目描述 思路 滑动窗口 解题过程 题目要求找到包含 k 个 ‘1’ 的子字符串&#xff0c;并且需要满足两个条件&#xff1a; 最短长度&#xff1a;在所有包含 k 个 …...

向量数据库学习笔记(2) —— pgvector 用法 与 最佳实践

关于向量的基础概念&#xff0c;可以参考&#xff1a;向量数据库学习笔记&#xff08;1&#xff09; —— 基础概念-CSDN博客 一、 pgvector简介 pgvector 是一款开源的、基于pg的、向量相似性搜索 插件&#xff0c;将您的向量数据与其他数据统一存储在pg中。支持功能包括&…...

蓝桥杯 之 二分

文章目录 习题肖恩的n次根分巧克力2.卡牌 二分是十分重要的一个算法&#xff0c;常常用于求解一定范围内&#xff0c;找到满足条件的边界值的情况主要分为浮点数二分和整数二分二分问题&#xff0c;最主要是写出这个check函数&#xff0c;这个check函数最主要就是使用模拟的方法…...

从零开始研发GPS接收机连载——18、北斗B1的捕获和跟踪

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 从零开始研发GPS接收机连载——18、北斗B1的捕获和跟踪 B1信号的捕获B1信号的跟踪 前面已经验证了射频能够接收到B1的信号&#xff0c;通过FPGA采集了IQ信号之后能够通过matl…...

sqlalchemy:将mysql切换到OpenGauss

说明 之前python的项目使用的mysql&#xff0c;近期要切换到国产数据库OpenGauss。 之前的方案是fastapisqlalchemy&#xff0c;测试下来发现不用改代码&#xff0c;只要改下配置即可。 切换方案 安装openGauss-connector-python-psycopg2 其代码工程在&#xff1a;https:…...

memtest86检测内存

上次在R730安装万兆网卡的时候&#xff0c;使用过memtest64进行测试。这个操作肯定是有用的&#xff0c;我就用这个方法查出有问题的内存条&#xff0c;及时找商家进行了更换。 但是该方案有个问题&#xff0c;只能锁定部分内存&#xff0c;如下图&#xff0c;只测试到了23G左…...

OpenCV图像拼接(10)用于实现图像拼接过程中的时间流逝(timelapse)效果的一个类cv::detail::Timelapser

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 cv::detail::Timelapser 是 OpenCV 库中用于实现图像拼接过程中的时间流逝&#xff08;timelapse&#xff09;效果的一个类。它通常用于将一系列…...

验证linux多进程时间片切换的程序

一、软件需求 在同时运行一个或多个一味消耗 CPU 时间执行处理的进程时&#xff0c;采集以下统计信息。 ・在某一时间点运行在逻辑 CPU 上的进程是哪一个 ・每个进程的运行进度 通过分析这些信息&#xff0c;来确认本章开头对调度器的描述是否正确。实验程序的…...

【Basys3】外设-灯和数码管

灯 约束文件 set_property PACKAGE_PIN W5 [get_ports CLK] set_property PACKAGE_PIN U18 [get_ports rst] set_property PACKAGE_PIN U16 [get_ports {led[0]}] set_property PACKAGE_PIN E19 [get_ports {led[1]}] set_property PACKAGE_PIN U19 [get_ports {led[2]}] set…...

axios文件下载使用后端传递的名称

java后端通过HttpServletResponse 返回文件流 在Content-Disposition中插入文件名 一定要设置Access-Control-Expose-Headers&#xff0c;代表跨域该Content-Disposition返回Header可读&#xff0c;如果没有&#xff0c;前端是取不到Content-Disposition的&#xff0c;可以在统…...