2.单链表练习

1. 链表的基本概念

链表(Linked List)是一种常见的数据结构,用于存储一系列元素,这些元素可以是任意类型的数据。链表中的每个元素被称为节点(Node),每个节点包含两部分:一个存储数据的值(或称为数据域),以及一个指向下一个节点的引用(或称为指针或链接)。

链表与数组相比,具有一些优势和不同之处:

  1. 动态性: 链表的大小可以在运行时动态地改变,而数组的大小通常是静态的。

  2. 插入和删除: 在链表中插入或删除元素相对容易,只需修改节点的引用,不需要像数组一样移动大量元素。

  3. 空间利用: 链表可以有效地利用内存,因为每个节点只需存储自身的值和下一个节点的引用,而数组可能需要一块连续的内存空间。

链表有几种常见的类型,其中最常见的是单向链表和双向链表:

  1. 单向链表(Singly Linked List): 每个节点包含一个数据域和一个指向下一个节点的引用。链表的首节点称为头节点,链表的尾节点的下一个节点引用为空。

  2. 双向链表(Doubly Linked List): 每个节点包含一个数据域和两个引用,分别指向前一个节点和后一个节点。这使得在链表中可以双向遍历。

基本操作:

  1. 插入操作: 插入节点涉及创建一个新的节点,并将其插入到合适的位置。对于单向链表,需要修改前一个节点的引用,对于双向链表,还需要修改后一个节点的前向引用。

  2. 删除操作: 删除节点涉及将待删除节点的前一个节点的引用指向待删除节点的下一个节点。同样,对于双向链表,还需要修改后一个节点的前向引用。

  3. 搜索操作: 从头节点开始,按顺序遍历链表,查找特定值的节点。

  4. 遍历操作: 从头节点开始,按顺序访问链表的每个节点,执行所需的操作。

链表在许多编程场景中都有用途,例如实现栈、队列、缓存等数据结构,也常用于解决某些特定的问题,如链表反转、寻找中间节点等。然而,需要注意链表的操作可能比数组稍微复杂,因为需要更多的指针操作。

2. 题目中的结构体

struct ListNode {int val;ListNode* next;ListNode() : val(0), next(nullptr) {}ListNode(int x) : val(x), next(nullptr) {}ListNode(int x, ListNode* next) : val(x), next(next) {}
};

ListNode* next;是单链表特性,除了储存value以外还都储存了next,指向下一个节点的指针。

这段代码定义了一个名为 ListNode 的结构体,用于表示链表中的节点。这个结构体有三个不同的构造函数,以便于创建节点对象:

  1. 默认构造函数:
ListNode() : val(0), next(nullptr) {}

这个构造函数会创建一个值为0、下一个节点为空的链表节点。

  1. 带有一个整数参数的构造函数:
ListNode(int x) : val(x), next(nullptr) {}

这个构造函数会创建一个具有给定整数值 x、下一个节点为空的链表节点。

  1. 带有两个参数的构造函数:
ListNode(int x, ListNode* next) : val(x), next(next) {}

这个构造函数会创建一个具有给定整数值 x 和指向下一个节点的指针 next 的链表节点。

在这个结构体中,val 成员变量用于存储节点的值,而 next 成员变量是一个指向下一个节点的指针。这样,我们就可以通过创建多个 ListNode 对象,并使用 next 指针将它们链接在一起,从而形成一个链表。

这个结构体的定义允许我们在链表操作中方便地创建和操作节点,以实现链表的常见操作,如插入、删除和遍历。

2. 移除链表元素

2.1 以案例来学习链表,普通的删除方法

复习指针

1. delete ptr不是删除指针, 而是释放指针指向的内存

这里因为tmp和head都指向了同一内存空间,所以delete tmp就是释放之前的head, 因为我们要对head进行一个变动

        while (head != NULL && head->val == val) { // 注意这里不是ifListNode* tmp = head;head = head->next;delete tmp;}

2. 比较容易的错误写法

在删除非头结点的时候容易犯这个错误, 因为是链表,写的时候就很容易参照链表的delete函数的写法,就是直接给一个tmp, 然后cur->next, 然后就直接删除,其实这样是不对的,因为链表删掉了中间节点还要考虑前后的加在一起

        // 删除非头结点的节点ListNode *cur = head; // 当前指针是headwhile (cur != NULL){if (cur->val == val){ListNode* tmp = cur; // tmp, head指向同一内存空间cur = cur->next; // 头结点指针指向原来链表的第二个节点delete tmp;}
#include <iostream>struct ListNode {int val;ListNode* next;ListNode() : val(0), next(nullptr) {}ListNode(int x) : val(x), next(nullptr) {}ListNode(int x, ListNode* next) : val(x), next(next) {}
};class Solution {
public:ListNode* removeElements(ListNode* head, int val) {// 删除头节点,同时也判断新的头结点是否需要删除while (head != NULL && head->val == val){ListNode *tmp = head;   // 这里让tmp指向需要被移除头结点同一地址head = head->next;      // 这里头结点的指针指向下一个节点delete tmp;             // 移除tmp, 同样也释放掉需要操作头结点的指针}// 删除非头结点的节点,这里不只是简单的删除,还要考虑把前一个后一个链接起来/*ex: 1->2->3->2->5, 处理2, 因为这里处理的是非头节点,在1位置链接3处理2,在3链接5处理2如果遍历的时候在2处理2,还要写一个prev指针如果5要处理,就在前一个地方(2)处理,5的next是nullptr, (2)链接5就行了*/ListNode *cur = head; // 这里已经考虑完了头结点, 所以这个头结点不用操作while (cur != NULL && cur->next != NULL){ // 遍历链表的基本手法if (cur->next->val == val){    ListNode *tmp = cur->next;cur->next = cur->next->next;delete tmp;}else {cur = cur->next;}}return head;}
};// 辅助函数:打印链表
void printList(ListNode* head) {ListNode* current = head;while (current != nullptr) {std::cout << current->val << " ";current = current->next;}std::cout << std::endl;
}// 这个地方跟上面删除非头结点最大的区别就是不用维护这个代码
void deleteList(ListNode* head) {ListNode* current = head;while (current != nullptr) {ListNode* tmp = current;current = current->next;delete tmp;}
}int main() {Solution solution;// 示例 1ListNode* head1 = new ListNode(1);head1->next = new ListNode(2);head1->next->next = new ListNode(6);head1->next->next->next = new ListNode(3);head1->next->next->next->next = new ListNode(4);head1->next->next->next->next->next = new ListNode(5);head1->next->next->next->next->next->next = new ListNode(6);int val1 = 6;ListNode* newHead1 = solution.removeElements(head1, val1);std::cout << "Case 1: ";printList(newHead1);// 示例 2ListNode* head2 = nullptr;int val2 = 1;ListNode* newHead2 = solution.removeElements(head2, val2);std::cout << "Case 2: ";printList(newHead2);// 示例 3ListNode* head3 = new ListNode(7);ListNode* current3 = head3;for (int val : {7, 7, 7}){current3->next = new ListNode(val);current3 = current3->next;}std::cout << "Oringin case 3: ";printList(head3);int val3 = 7;ListNode* newHead3 = solution.removeElements(head3, val3);std::cout << "Case 3: ";printList(newHead3);// 释放节点内存deleteList(newHead1);deleteList(newHead2);deleteList(newHead3);return 0;
}

2.2 虚拟头结点写法

这样就只用考虑处理非头结点的情况就好了, 注意这里实例化了之后要用引用,这里也是复习一下实例化和在堆上生成内存返回指针,下面有两种写法

栈上的内存(Stack):当你创建一个局部变量或对象(例如ListNode dummy(0);)时,这个对象是在栈上分配内存的。这些对象的生命周期是确定的,当它们所在的作用域结束时,它们会自动被销毁,内存会被释放。

堆上的内存(Heap):当你使用new关键字(例如ListNode* dummy = new ListNode(0);)时,对象是在堆上分配内存的。这些对象的生命周期是不确定的,你需要显式地使用delete来释放内存。

使用指针的一个主要优点是,它允许你在运行时动态地创建和销毁对象。这给了你更大的灵活性,但代价是你需要更仔细地管理内存。

实例化虚拟头节点

class Solution {
public:ListNode* removeElements(ListNode* head, int val) {ListNode dummy(0);dummy.next = head;// 处理非头节点ListNode *cur = &dummy; // 这里已经考虑完了头结点, 所以这个头结点不用操作while (cur != NULL && cur->next != NULL){ // 遍历链表的基本手法if (cur->next->val == val){    ListNode *tmp = cur->next;cur->next = cur->next->next;delete tmp;}else {cur = cur->next;}}return dummy.next;}
};

指针虚拟头结点

class Solution {
public:ListNode* removeElements(ListNode* head, int val) {ListNode *dummy = new ListNode(999);// 处理非头节点ListNode *cur = dummy; // 这里已经考虑完了头结点, 所以这个头结点不用操作while (cur != NULL && cur->next != NULL){ // 遍历链表的基本手法if (cur->next->val == val){    ListNode *tmp = cur->next;cur->next = cur->next->next;delete tmp;}else {cur = cur->next;}}return dummy->next;}
};

3. 设计一个链表

通过完整的设计一个链表理解链表是怎么遍历的,新的节点怎么在各个地方链接起来

#include <iostream>class MyLinkedList {
public:struct ListNode   // 先创建节点的结构体, val, next, 以及ListNode的构造函数{int val;ListNode *next;ListNode(int x) : val(x), next(nullptr) {}};private:ListNode *head;    // 指向头结点的指针int size;          // 链表的尺寸public:MyLinkedList() {head = NULL;size = 0;}int get(int index) {// 这个就是取出索引的值if (index < 0 || index >= size)return -1;ListNode *cur = head;for (int i = 0; i < index; i++){cur = cur->next;}return cur->val;}void addAtHead(int val) {/*1 -> 2 -> 3 -> 4   添加valval -> 1 -> 2 -> 3 -> 4  */ListNode *new_node = new ListNode(val); // 定义一个新的节点new_node->next = head;  // val链接头结点head = new_node;        // 更新头结点size++;                 }void addAtTail(int val) {/*1 -> 2 -> 3 -> 4   添加val1 -> 2 -> 3 -> 4 -> val*/// 如果刚初始化也可以使用这个接口if (size == 0){addAtHead(val);return;}ListNode *cur = head; // 当前指针位置,从head开始while (cur->next != NULL){ // 这里遍历到最后一个节点cur = cur->next;}ListNode *new_node = new ListNode(val); // 定义指向新的节点的指针cur->next = new_node;                   // 链接起来size++; }void addAtIndex(int index, int val) {if (index < 0 || index > size)return;if (index == 0) {addAtHead(val);return;}ListNode *cur = head;for (int i = 0; i < index - 1; i++){cur = cur->next;}// 现在来到要加的前一个ListNode *new_node = new ListNode(val);new_node->next = cur->next;cur->next = new_node;size++;}void deleteAtIndex(int index) {if (index < 0 || index >= size) return;ListNode *cur = head;if (index == 0) {head = head->next;delete cur;--size;return;}for (int i = 0; i < index - 1; i++){cur = cur->next;}// 现在来到要删除的前一个ListNode *tmp = cur->next;cur->next = cur->next->next;delete tmp;size--;}
};int main() {MyLinkedList *myLinkedList = new MyLinkedList();myLinkedList->addAtHead(1);myLinkedList->addAtTail(3);myLinkedList->addAtIndex(1, 2);std::cout << myLinkedList->get(1) << std::endl;myLinkedList->deleteAtIndex(1);std::cout << myLinkedList->get(1) << std::endl;delete myLinkedList;return 0;
}

4. 反转链表

4.1 双指针法

class Solution {
public:ListNode* reverseList(ListNode* head) {ListNode *cur = head; // 定义当前位置指针ListNode *prev = NULL; // 定义当前位置指针while (cur != NULL){ListNode *next_node = cur->next;cur->next = prev;     // 这里链接起来           prev = cur;           // prev上一位cur = next_node;      // cur上一位}return prev;}
};

递归的写法,复习一下递归

class Solution {
public:ListNode* reverse(ListNode* prev, ListNode* cur){if (cur == NULL)return prev;ListNode *next_node = cur->next;cur->next = prev;return reverse(cur, next_node);}public:ListNode* reverseList(ListNode* head) {return reverse(NULL, head);}
};

完整的代码

#include <iostream>struct ListNode {int val;ListNode *next;ListNode() : val(0), next(nullptr) {}ListNode(int x) : val(x), next(nullptr) {}ListNode(int x, ListNode *next) : val(x), next(next) {}
};class Solution {
public:ListNode* reverse(ListNode* prev, ListNode* cur){if (cur == NULL)return prev;ListNode *next_node = cur->next;cur->next = prev;return reverse(cur, next_node);}public:ListNode* reverseList(ListNode* head) {return reverse(NULL, head);}
};// 辅助函数:打印链表
void printList(ListNode* head) {ListNode* current = head;while (current != nullptr) {std::cout << current->val << " ";current = current->next;}std::cout << std::endl;
}int main() {Solution solution;// 示例ListNode* head = new ListNode(1);head->next = new ListNode(2);head->next->next = new ListNode(3);head->next->next->next = new ListNode(4);std::cout << "Original List: ";printList(head);ListNode* newHead = solution.reverseList(head);std::cout << "Reversed List: ";printList(newHead);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/63897.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Docker】02-安装mysql

参考教程&#xff1a; https://www.bilibili.com/video/BV1Qa4y1t7YH/?p5&spm_id_frompageDriver&vd_source4964ba5015a16eb57d0ac13401b0fe77 docker安装Mysql 1、拉取最新版本的镜像 docker pull mysq:latestl 2、运行mysql服务 docker run --name mysql -e MYSQL_…

2018ECCV Can 3D Pose be Learned from2D Projections Alone?

摘要 在计算机视觉中&#xff0c;从单个图像的三维姿态估计是一个具有挑战性的任务。我们提出了一种弱监督的方法来估计3D姿态点&#xff0c;仅给出2D姿态地标。我们的方法不需要2D和3D点之间的对应关系来建立明确的3D先验。我们利用一个对抗性的框架&#xff0c;强加在3D结构…

【设计模式】Head First 设计模式——构建器模式 C++实现

设计模式最大的作用就是在变化和稳定中间寻找隔离点&#xff0c;然后分离它们&#xff0c;从而管理变化。将变化像小兔子一样关到笼子里&#xff0c;让它在笼子里随便跳&#xff0c;而不至于跳出来把你整个房间给污染掉。 设计思想 ​ 将一个复杂对象的构建与其表示相分离&…

ArcGIS Maps SDK for JS(一):概述与使用

文章目录 1 概述2 如何使用ArcGIS Maps SDK for JavaScript2.1 AMD 模块与 ES 模块2.2 AMD 模块和 ES 模块比较 3 几种安装方式3.1 通过 ArcGIS CDN 获取 AMD 模块3.2 通过 NPM 运行 ES 模块3.3 通过 CDN 获取 ES 模块3.4 本地构建 ES3.5 本地构建 AMD 3 VSCode下载与安装2.1 下…

Redis之分布式锁

背景 分布式应用中&#xff0c;经常会遇到并发问题。熟悉的朋友都知道这个时候就需要加锁。只有原子操作才能保证数据不会混乱。&#xff08;原子操作是不会被线程调度机制所打断的的操作&#xff0c;一旦开始就会执行到最后&#xff0c;要么做要么不做&#xff0c;不会被打断…

#systemverilog# 之 event region 和 timeslot 仿真调度(六)疑惑寄存器采样吗

一 象征性啰嗦 想必大家在刚开始尝试写Verilig HDL代码的时候,都是参考一些列参考代码,有些来自于参考书,有些来自于网上大牛的笔记,甚至有写来自于某宝FPGA开发板的授权代码。我还记得自己当时第一次写代码,参考的是一款Altera 芯片,结合Quartus 开发软件, 在上面练习…

常用框架分析(7)-Flutter

框架分析&#xff08;7&#xff09;-Flutter 专栏介绍Flutter核心思想Flutter的特点快速开发跨平台高性能美观的用户界面 Flutter的架构框架层引擎层平台层 开发过程使用Dart语言编写代码编译成原生代码热重载工具和插件 优缺点优点跨平台开发高性能美观的用户界面热重载强大的…

linux如何抓包数据

分析&回答 (1) 想要截获所有210.27.48.1 的主机收到的和发出的所有的分组&#xff1a; #tcpdump host 210.27.48.1 复制代码 (2) 想要截获主机210.27.48.1 和主机210.27.48.2或210.27.48.3的通信&#xff0c;使用命令(注意&#xff1a;括号前的反斜杠是必须的)&#xff…

225. 用队列实现栈

225. 用队列实现栈 \请你仅使用两个队列实现一个后入先出&#xff08;LIFO&#xff09;的栈&#xff0c;并支持普通栈的全部四种操作&#xff08;push、top、pop 和 empty&#xff09;。 实现 MyStack 类&#xff1a; void push(int x) 将元素 x 压入栈顶。 int pop() 移除并…

Redis 7 第四讲 数据持久化

总体 RDB 介绍 RDB 持久化以指定的时间间隔执行数据集的时间点快照 。 把某一时刻的数据和状态以文件的形式写到磁盘上,即使出现故障宕机,快照文件也不会丢失,数据的可靠性得到保证。快照文件就是RDB(Redis DataBase)文件(dump.rdb) 作用 在指定的时间间隔内将内存中的数…

五子棋游戏禁手算法的改进

五子棋游戏禁手算法的改进 五子棋最新的禁手规则&#xff1a; 1&#xff0e;黑棋禁手判负、白棋无禁手。黑棋禁手有“三三”&#xff08;包括“四三三”&#xff09;、“四四”&#xff08;包括“四四三”&#xff09;和“长连”。黑棋只能以“四三”取胜。 2&#xff0e;黑方…

创新科技改变城市:智慧城市建设全景展望

在当今科技飞速发展的时代&#xff0c;智慧城市的概念已经成为城市发展的新趋势&#xff0c;为人们的生活带来了前所未有的便利和改变。智慧城市&#xff0c;顾名思义&#xff0c;是以先进的信息技术为基础&#xff0c;通过数字化、互联网化和智能化手段&#xff0c;实现城市基…

合宙Air724UG LuatOS-Air LVGL API控件--图表 (Chart)

图表 (Chart) 一幅图胜过一千个字&#xff0c;通过图表展示出的数据内容能让用户更快速有效的了解数据特征。 代码示例 – 创建图表 chart lvgl.chart_create(lvgl.scr_act(), nil) lvgl.obj_set_size(chart, 200, 150) lvgl.obj_align(chart, nil, lvgl.ALIGN_CENTER, 0, …

Node 执行命令时传参 process.argv

process 对象是一个全局变量&#xff0c;提供当前 Node.js 进程的有关信息&#xff0c;以及控制当前 Node.js 进程。 因为是全局变量&#xff0c;所以无需使用 require()。 process.argv 属性返回一个数组&#xff0c;这个数组包含了启动Node.js进程时的命令行参数&#xff0c…

java-thread-affinity线程绑核

通过将线程绑定到指定的cpu上&#xff0c;可以提高执行效率。因为每次都是相同的cpu,可以充分利用高速缓存&#xff0c;在java中可以使用以下依赖来使用。 <dependency><groupId>net.openhft</groupId><artifactId>affinity</artifactId><ver…

2023新,centos7安装mysql8.0.25

2023新&#xff0c;centos7安装mysql8.0.25 目录 2023新&#xff0c;centos7安装mysql8.0.251、下载rpm文件2、安装3、配置my.cnf4、启动查看重启服务5、登入mysql并修改密码6、修改可以远程登录 1、下载rpm文件 进入到你想要的文件地址下 wget https://repo.mysql.com//mysq…

Unity RenderStreaming 云渲染-黑屏

&#x1f96a;云渲染-黑屏 网页加载出来了&#xff0c;点击播放黑屏 &#xff0c;关闭防火墙即可&#xff01;&#xff01;&#xff01;&#xff01;

springboot添加SSL证书,支持https与http

文章目录 一、添加ssl证书二、配置文件三、配置同时支持HTTPS与HTTP四、启动 一、添加ssl证书 将证书文件放在/resource目录下 二、配置文件 修改配置文件 server:ssl:# 指定保存SSL证书的秘钥存储的路径key-store: classpath:dev.cobona.cn.pfx# 访问秘钥存储的密码key-store-…

SKU助手

属性SKU助手可以帮你快速选中目标商品属性 下载安装与运行 下载、安装与运行 语雀 如何使用 下面以1688批量自动下单为例&#xff0c;演示用法&#xff0c;同样适用于淘宝天猫拼多多批量自动下单 功能说明 SKU助手弹出的时机 同时满足如下两个条件 Excel提供的SKU与真实…

CAD图纸加密软件——公司核心文件数据防泄密「天锐绿盾」

PC访问地址&#xff1a; isite.baidu.com/site/wjz012xr/2eae091d-1b97-4276-90bc-6757c5dfedee 数据安全保护系统 数据安全保护系统以全面数据文件安全策略、加解密技术与强制访问控制有机结合为设计思想&#xff0c;对信息媒介上的各种数据资产&#xff0c;实施不同安全等级…