认识单双链表

认识单双链表

前置知识:无,会的可以直接跳过。

基本概念

内存空间是所有程序的公共资源,在一个复杂的系统运行环境下,空闲的内存空间可能散落在内存各处。我们知道,存储数组的内存空间必须是连续的,而当数组非常大时,内存可能无法提供如此大的连续空间。此时链表的灵活性优势就体现出来了。

链表(linked list)是一种线性数据结构,其中的每个元素都是一个节点对象,各个节点通过“引用”相连接。引用记录了下一个节点的内存地址,通过它可以从当前节点访问到下一个节点。

链表的设计使得各个节点可以分散存储在内存各处,它们的内存地址无须连续。

在这里插入图片描述

观察上图,链表的组成单位是节点(node)对象。每个节点都包含两项数据:节点的“值”和指向下一节点的“引用”:

  • 链表的首个节点被称为“头节点”,最后一个节点被称为“尾节点”。
  • 尾节点指向的是“空”,它在 Java、C++ 和 Python 中分别被记为 nullnullptrNone
  • 在 C、C++、Go 和 Rust 等支持指针的语言中,上述“引用”应被替换为“指针”。

如下面代码所示,链表节点 ListNode 除了包含值,还需额外保存一个引用(指针)。因此在相同数据量下,链表比数组占用更多的内存空间

/* 链表节点结构体 */
struct ListNode {int val;         // 节点值ListNode *next;  // 指向下一节点的指针ListNode(int x) : val(x), next(nullptr) {}  // 构造函数
};

双链表是在上述单链表的基础上,再加一个指向前一个节点的指针,插入和删除操作需要额外再对指向前一个节点的指针进行操作,双链表的数据结构如下所示:

/* 双向链表节点结构体 */
struct DoubleListNode {int val;         // 节点值DoubleListNode *next;  // 指向后继节点的指针DoubleListNode *prev;  // 指向前驱节点的指针DoubleListNode(int x) : val(x), next(nullptr), prev(nullptr) {}  // 构造函数
};

链表操作

链表的主要操作就两个:插入节点和删除节点。

插入节点

在链表中插入节点非常容易。如下图所示,假设我们想在相邻的两个节点 n0n1 之间插入一个新节点 P则只需改变两个节点引用(指针)即可,时间复杂度为 O ( 1 ) O(1) O(1) 。相比之下,在数组中插入元素的时间复杂度为 O ( n ) O(n) O(n) ,在大数据量下的效率较低。

在这里插入图片描述

插入节点的代码示例如下:

/* 在链表的节点 n0 之后插入节点 P */
void insert(ListNode *n0, ListNode *P) {ListNode *n1 = n0->next;P->next = n1;n0->next = P;
}

删除节点

如下图所示,在链表中删除节点也非常方便,只需改变一个节点的引用(指针)即可。请注意,尽管在删除操作完成后节点 P 仍然指向 n1 ,但实际上遍历此链表已经无法访问到 P ,这意味着 P 已经不再属于该链表了。

在这里插入图片描述

删除节点的代码示例如下:

/* 删除链表的节点 n0 之后的首个节点 */
void remove(ListNode *n0) {if (n0->next == nullptr)return;// n0 -> P -> n1ListNode *P = n0->next;ListNode *n1 = P->next;n0->next = n1;// 释放内存delete P;
}

链表的具体实现

通过前面的概念理解了链表的基本模型以及链表的操作方式,现在可以实现一个链表。我们可以按照 leetcode707 设计链表进行实现并测试。

单链表的实现

链表节点是默认给出的,与上面描述的是一样的。

class MyLinkedList {
public:MyLinkedList() : size(0) {dummy = new ListNode(-1);tail = dummy;}int get(int index) {if (index < 0 || index >= size)return -1;ListNode *cur_node = dummy->next;int i = 0;for (int i = 0; i < index; ++i) {cur_node = cur_node->next;}return cur_node->val;}void addAtHead(int val) {ListNode *new_node = new ListNode(val);new_node->next = dummy->next;dummy->next = new_node;++size;if (tail == dummy)tail = new_node;}void addAtTail(int val) {ListNode *new_node = new ListNode(val);new_node->next = tail->next;tail->next = new_node;tail = new_node;++size;}void addAtIndex(int index, int val) {if (index < 0 || index > size)return;if (index == size) {addAtTail(val);return;}ListNode *new_node = new ListNode(val);ListNode *prev_node = dummy;ListNode *cur_node = dummy->next;for (int i = 0; i < index; ++i) {prev_node =prev_node->next;cur_node = cur_node->next;}new_node->next = cur_node;prev_node->next = new_node;++size;}void deleteAtIndex(int index) {if (index < 0 || index >= size) return;ListNode *prev_node = dummy;ListNode *cur_node = dummy->next;for (int i = 0; i < index; ++i) {prev_node = prev_node->next;cur_node = cur_node->next;}if (cur_node == tail) tail = prev_node;prev_node->next = cur_node->next;delete cur_node;cur_node = nullptr;--size;}private:ListNode *dummy;ListNode *tail;int size;
};

双链表的实现

双链表的实现无非在单链表的基础上增加一个指针的处理

class MyLinkedList {
public:MyLinkedList() : size(0) {dummy = new ListNode(-1);tail = dummy;}int get(int index) {if (index< 0 || index >= size)return -1;ListNode *cur_node = dummy->next;int i = 0;for (int i = 0; i < index; ++i) {cur_node = cur_node->next;}return cur_node->val;}void addAtHead(int val) {ListNode *new_node = new ListNode(val);new_node->next = dummy->next;new_node->prev = dummy;dummy->next = new_node;++size;if (tail == dummy)tail = new_node;}void addAtTail(int val) {ListNode *new_node = new ListNode(val);new_node->next = tail->next;new_node->prev = tail;tail->next = new_node;tail = new_node;++size;}void addAtIndex(int index, int val) {if (index < 0 || index > size)return;if (index == size) {addAtTail(val);return;}ListNode *new_node = new ListNode(val);ListNode *prev_node = dummy;ListNode *cur_node = dummy->next;for (int i = 0; i < index; ++i) {prev_node = prev_node->next;cur_node = cur_node->next;}new_node->next = cur_node;new_node->prev = prev_node;prev_node->next = new_node;++size;}void deleteAtIndex(int index) {if (index < 0 || index >= size) return;ListNode *prev_node = dummy;ListNode *cur_node = dummy->next;for (int i = 0; i < index; ++i) {prev_node = prev_node->next;cur_node = cur_node->next;}if (cur_node == tail) tail = prev_node;prev_node->next = cur_node->next;cur_node->next->prev = prev_node;delete cur_node;cur_node = nullptr;--size;}private:ListNode *dummy;ListNode *tail;int size;
};

链表入门题

反转链表

题目描述如下图所示:

在这里插入图片描述

思路:反转链表需要修改当前节点的 next 指针的指向为前一个节点,因此需要记录前一个节点和当前节点,当修改完以后还要往后遍历修改下一个节点,而前面修改当前节点的 next 指针的指向已经让当前节点与整个链表断开联系,所以需要提前记录当前节点的下一个节点。故分别使用 prevcurnext 表示,其中 prevnext 一开始为空指针,cur 为参数节点。循环判断当前节点是否为空,若不为空表示需要反转,用 next 保存下一个节点的地址,然后将当前节点的指针指向 prev,再更新 prev 为当前指针表示此节点已完成反转,最后更新 cur 为下一个节点,执行下一个循环,直达循环结束,返回 prev 节点即为整个反转后链表的头结点。

代码示例如下:

class Solution {
public:ListNode* reverseList(ListNode* head) {ListNode *prev = nullptr;ListNode *cur = nullptr;ListNode *next = nullptr;cur = head;while (cur != nullptr) {next = cur->next;cur->next = prev;prev = cur;cur = next;}return prev;}
};

测试地址:leetcode206 反转链表

合并两个有序链表

题目描述如下图所示:

在这里插入图片描述

思路:可以直接在原链表上操作,也可以创建新链表操作,下面使用第二种方式。首先创建一个新链表,然后分别遍历两个旧链表,直到其中一个链表遍历结束则停止遍历,每次遍历都要比较两个链表当前节点的值,将较小的节点添加到新链表中,最后将还有剩余的链表全部添加到新链表中。

代码示例如下:

class Solution {
public:ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {if (list1 == nullptr || list2 == nullptr)return list1 == nullptr ? list2 : list1;ListNode *dummy = new ListNode(-1);ListNode *cur = dummy;ListNode *l1 = list1;ListNode *l2 = list2;while (l1 != nullptr && l2 != nullptr) {if (l1->val <= l2->val) {cur->next = l1;l1 = l1->next;} else {cur->next = l2;l2 = l2->next;}cur = cur->next;}if (l1 != nullptr)cur->next = l1;if (l2 != nullptr)cur->next = l2;return dummy->next;}
};

测试地址:leetcode21 合并两个有序链表

两数相加

题目描述如下图所示:

在这里插入图片描述

思路:可以复用老链表,不过下面的实现没有这么做,都是生成的新节点(为了好理解)。创建一个新链表,因为是两数相加,所以需要用一个变量保存进位信息(默认为0),用一个变量保存求和信息。同时从头结点开始遍历两个链表,只要结点不为空,就将结点的值加到和上,在加上进位信息的值,通过最后的和获取进位信息和值信息,将值信息放到新链表节点中。循环执行,每次都需要将和结果置为 0,最后判断进位信息是否为 1,如果是 1 在新链表中再添加一个值为 1 的节点。

代码示例如下:

class Solution {
public:ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {ListNode *cur1 = l1;ListNode *cur2 = l2;int sum = 0;int carry = 0;ListNode *dummy = new ListNode(-1);ListNode *cur = dummy;while (cur1 != nullptr || cur2 != nullptr) {sum = 0;sum += carry;if (cur1 != nullptr) {sum += cur1->val;cur1 = cur1->next;}if (cur2 != nullptr) {sum += cur2->val;cur2 = cur2->next;}carry = sum / 10;int val = sum % 10;ListNode *new_node = new ListNode(val);cur->next = new_node;cur = cur->next;}if (carry != 0) {ListNode *new_node = new ListNode(carry);cur->next = new_node;cur = cur->next;}return dummy->next;}
};

测试地址:leetcode2 两数相加

划分链表

题目描述如下图所示:

在这里插入图片描述

思路:创建两新链表,分别保存小于目标值的节点和大于等于目标值的节点,不打乱顺序,最后将大于等于的那些节点追加到小于部分的链表后面。由于是直接将整个链表添加到新链表中,因此最后的结果可能会是一个环,为了解决这个问题需要在添加到新链表时,将当前节点与原链表解除关系。

代码描述如下:

class Solution {
public:ListNode* partition(ListNode* head, int x) {if (head == nullptr)return nullptr;// 使用两个链表分别保存所有小于和大于等于的的节点ListNode *small = new ListNode(-1);ListNode *large = new ListNode(-1);ListNode *p1 = small;ListNode *p2 = large;while (head != nullptr) {if (head->val < x) {p1->next = head;p1 = p1->next;} else {p2->next = head;p2 = p2->next;}// 因为在符合条件时,是直接将整个链表添加上去,所以在最后两个链表是尾尾相连的// 为了防止这种相连的过程可以在每次添加时断开该节点与原节点的联系// ListNode *temp = head->next;// head->next = nullptr;// head = temp;head = head->next;}// 除了上面的方法外,还有一种方法// 因为最后肯定会修改小于部分链表的最后一个结点指向,所以即使是小于部分的尾指向大于等于部分的尾也没有关系// 但是如果是大于部分的尾,直接修改小于部分尾的指向是无效的,此时只需要将大于等于部分的尾指向空即可p2->next = nullptr;// 最后将大于等于的链表直接加到小于链表后p1->next = large->next;return small->next;}
};

测试地址:leetcode86 划分链表

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

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

相关文章

木马病毒相关知识

1、 木马的定义 相当于一个远控程序&#xff08;一个控制端[hack]、一个被控端[受害端]&#xff09; 在计算机系统中&#xff0c;“特洛伊木马”指系统中被植入的、人为设计的程序&#xff0c;目的包括通过网终远程控制其他用户的计算机系统&#xff0c;窃取信息资料&#xff0…

Oracle简介、环境搭建和基础DML语句

第一章 ORACLE 简介 1.1 什么是 ORACLE ORACLE数据库系统是美国ORACLE 公司&#xff08;甲骨文&#xff09;提供的以分布式数据库为核心的一组软件产品&#xff0c;是目前最流行的客户/服务器体系结构的数据库之一。 英文官网&#xff1a;Database | Oracle 中文官网&#xff…

【React】初学React

A. react中如何创建元素呢&#xff1f; 说明一点&#xff1a; 属性都改为驼峰形式&#xff08;无障碍属性aria-*除外&#xff09;&#xff0c; class改成className 创建元素 B. 变量或表达式如何表示呢&#xff1f;大括号{ }包起来 变量值用大括号包裹 C. 元素和组件的区别 元素…

Memento 备忘录模式

备忘录模式 意图结构适用性实例Java Web开发中的简单示例Originator 类Memento 类Caretaker 类 文本编辑器示例1. Originator (发起人) - TextEditor2. Memento (备忘录) - TextMemento3. Caretaker (负责人) - History4. 使用示例输出 备忘录模式&#xff08;Memento Pattern&…

导入项目时微信开发者工具如何自动识别项目APPID

一、需求 当我们在公司拉取小程序项目的时候&#xff0c;经常会在微信开发者工具中导入项目&#xff0c;需要我们手动输入自己的appid非常麻烦&#xff0c;我们可以用在导入项目的时候自动识别公司的appid 二、步骤 2.1 使用Hbuilder工具编译项目 编译成功后会有一个unpacka…

小语言模型介绍与LLM的比较

小模型介绍 小语言模型&#xff08;SLM&#xff09;与大语言模型&#xff08;LLM&#xff09;相比&#xff0c;具有不同的特点和应用场景。大语言模型通常拥有大量的参数&#xff08;如 GPT-3 拥有 1750 亿个参数&#xff09;&#xff0c;能够处理复杂的自然语言任务&#xff…

17.快递物流仓库管理系统(基于springboot和vue)

目录 1.系统的受众说明 2.系统详细设计 2.1 需求分析 2.2 系统功能设计 2.3 开发环境分析 ​​​​​​​2.4 E-R图设计 2.5 数据库设计 3. 系统实现 3.1 环境搭建 ​​​​​​​3.2 员工信息管理模块 3.3 销售订单信息管理模块 ​​​​​​​3.4 图表分析模块…

Shortcut Learning in In-Context Learning: A Survey

为我们的综述打一打广告&#xff0c;目前是初级版本&#xff0c;欢迎各位批评指正&#xff01;后续的论文列表、测评基准会在Github更新[/(ㄒoㄒ)/~~最近比较忙容许我拖一拖] 这里是arxiv链接&#xff1a;Linking!!! Abstract&#xff1a;捷径学习是指模型在实际任务中使用简单…

第三十四章 Vue路由进阶之声明式导航(导航高亮)

目录 一、导航高亮 1.1. 基于语法 1.2. 主要代码 二、声明式导航的两个类名 2.1. 声明式导航类名匹配方式 2.2. 声明式导航类名样式自定义 ​2.3. 核心代码 一、导航高亮 1.1. 基于语法 在Vue中通过VueRouter插件&#xff0c;我们可以非常简单的实现实现导航高亮效果…

第七部分:1. STM32之ADC实验--单通道实验

主要利用一个模拟量的电位器来实时改变电压值&#xff0c;通过STM32自带的ADC通道来采集这个数据&#xff0c;并打印出来&#xff01; 一句话&#xff0c;学完STM32&#xff0c;我就往南走&#xff0c;我的工资只有5000.~~~~Whappy

Ubuntu20.04两种安装及配置中文界面、输入法、换源、共享文件夹实现,及注意事项

虚拟机安装法 1、新建虚拟机&#xff0c;自定义下一步 任意指定路径 提高处理器数量能加快系统响应 完成以后不要运行&#xff0c;添加镜像文件 导入镜像文件&#xff0c;点击浏览 选择后打开->确认->运行虚拟机 出现这种情况就需要检查虚拟机的配置&#xff0c;操作系统…

记录解决vscode 登录leetcode中遇到的问题

1. 安装完 leetcode 点击sign in to leetcode 点击打开网站登录leetcode&#xff0c;发现网页无法打开。 解决办法&#xff1a;将leetcode.cn.js文件中的leetcode-cn.com路径都改成leetcode.cn 2. 继续点击 sign in to leetcode &#xff0c;选择使用账号登录&#xff0c;始…

docker镜像仓库实战

docker镜像仓库实战 搭建一个nginx服务基础知识(Web服务器)查找nginx镜像拉取镜像启动nginx镜像 搭建一个nginx服务 基础知识(Web服务器) Web 服务器&#xff0c;一般是指“网站服务器”&#xff0c;是指驻留于互联网上某种类型计算机的程序。Web 服务器可以向 Web 浏览器等客…

zabbix安装配置与使用

zabbix Zabbix的工作原理如下: 监控部分: Zabbix Agent安装在各个需要监控的主机上,它以主配置的时间间隔(默认60s)收集主机各项指标数据,如CPU占用率、内存使用情况等。 通讯部分: Agent会把收集的数据通过安全通道(默认10051端口)发送到Zabbix Server。Server会存储这些数…

CSS的三个重点

目录 1.盒模型 (Box Model)2.位置 (position)3.布局 (Layout)4.低代码中的这些概念 在学习CSS时&#xff0c;有三个概念需要重点理解&#xff0c;分别是盒模型、定位、布局 1.盒模型 (Box Model) 定义&#xff1a; CSS 盒模型是指每个 HTML 元素在页面上被视为一个矩形盒子。…

关于LLC知识23(频率越大变压器体积越小?)

为什么频率越高&#xff0c;同样的磁芯就可以用的更小&#xff1f; 变压器他负责的功能是 1、隔离 2、能量传递 这里主要是与能量传递有关 我们首先要知道&#xff0c;次级的输出功率一定的情况下&#xff0c;那么在一定的时段内消耗的能量就是一定的&#xff0c;比如1000W…

UE5.4 PCG Layered Biomes插件

B站学习链接 官方文档 一、PCGSpawn Preset&#xff1a;负责管理PCG要用到的植被资产有哪些 二、BiomesSettings&#xff1a;设置要使用的植被资产Layer、Spawn参数 1.高度Layer参数&#xff1a; 2.地形Layer&#xff1a;我这里用地形样条线绘制了一块地形Layer 绘制点和…

数字后端零基础入门系列 | Innovus零基础LAB学习Day8

###LAB15 Detail Routing for Signal Integrity, Timing, Power and Design for Yield 这个章节虽然标题有点长&#xff0c;但不要被它吓到&#xff0c;其实这个章节就是Innovus工具的绕线Routing。只不过这个阶段做Route不是仅仅是把所有的逻辑连接&#xff0c;用实际的金属层…

量化交易 股市技术指标

股市数据分类 股票数据根据信息来源和分析方法的不同&#xff0c;可以分为技术面数据和基本面数据。 技术面数据和基本面数据都是股票分析中重要的工具&#xff0c;它们提供了不同的视角和方法来评估股票的投资价值。投资者可以综合运用这两类数据&#xff0c;从技术面和基本…

【从零开始的LeetCode-算法】3222. 求出硬币游戏的赢家

给你两个 正 整数 x 和 y &#xff0c;分别表示价值为 75 和 10 的硬币的数目。 Alice 和 Bob 正在玩一个游戏。每一轮中&#xff0c;Alice 先进行操作&#xff0c;Bob 后操作。每次操作中&#xff0c;玩家需要拿出价值 总和 为 115 的硬币。如果一名玩家无法执行此操作&#…