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

【TypeScript】常见数据结构与算法(二):链表

文章目录

    • 链表结构(LinkedList)
      • 链表以及数组的缺点
        • 数组
        • 链表的优势
      • 什么是链表?
      • 封装链表相关方法
      • 源码
        • 链表常见面试题
          • 237-删除链表中的节点
          • 206 - 反转链表
      • 数组和链表的复杂度对比

链表结构(LinkedList)

链表以及数组的缺点

  • 链表和数组一样,可以用于存储一系列的元素,但是链表和数组的实现机制完全不同。
  • 这一章中,我们就来学习一下另外一种非常常见的用于存储数据的线性结构:链表。
数组
  • 要存储多个元素,数组(或称为链表)可能是最常用的数据结构。
  • 我们之前说过,几乎每一种编程语言都有默认实现数组结构。

但是数组也有很多缺点

  • 数组的创建通常需要申请一段连续的内存空间(一整块的内存),并且大小是固定的(大多数编程语言数组都是固定的),所以当当前数组不能满足容量需求时,需要扩容。(一般情况下是申请一个更大的数组,比如2倍。然后将原数组中的元素复制过去
  • 而且在数组开头或中间位置插入数据的成本很高,需要进行大量元素的位移。
  • 尽管JavaScript的Array底层可以帮我们做这些事,但背后的原理依然是这样。
链表的优势

要存储多个元素,另外一个选择就是链表

但不同于数组,链表中的元素在内存中不必是连续的空间。

  • 链表的每个元素有一个存储元素本身的节点和一个指向下一个元素的引用(有些语言称为指针或者链接)组成。

相对于数组,链表的优势:

  • 内存空间不是必须连续的。

    √可以充分利用计算机的内存,实现灵活的内存动态管理。

  • 链表不必再创建时就确定大小,并且大小可以无限的延伸下去。

  • 链表在插入和删除数据时,时间复杂度可以达到O(1)

    √相对数组效率高很多

相对数组,链表的缺点:

  • 链表访问任何一个位置的元素时,都要从头开始访问。(无法跳过第一个元素访问任何一个元素)。
  • 无法通过下标直接访问元素,需要从头一个个访问,直到找到对应元素。

什么是链表?

  • 其实上面我们已经简单的提过了链表的结构,我们这里更加详细的分析一下。
  • 链表类似于火车:有一个火车头,火车头会连接一个节点,节点上有乘客(类似于数据),并且这个节点会连接下一个节点,以此类推。

image.png

image.png

封装链表相关方法

我们先来认识一下,链表中应该有哪些常见的操作
append(element):向链表尾部添加一个新的项
insert(position,element):向链表的特定位置插入一个新的项。
image.png
get(position):获取对应位置的元素
image.png
indexOf(element):返回元素在链表中的索引。如果链表中没有该元素则返回-1。
update(position,element):修改某个位置的元素
removeAt(position):从链表的特定位置移除一项。
image.png
remove(element):从链表中移除一项。
isEmpty():如果链表中不包含任何元素,返回true,如果链表长度大于0则返回false。
size():返回链表包含的元素个数。与数组的length属性类似。

源码

// 1.创建Node节点类
class Node<T> {value: T;next: Node<T> | null = null;constructor(value: T) {this.value = value;}
}// 2.创建LinkedList的类
class LinkedList<T> {private head: Node<T> | null = null;private size: number = 0;get length() {return this.size;}// 封装私有方法// 根绝positon获取当前的节点(不是节点的value,而是获取节点)private getNode(position: number): Node<T> | null {let index = 0;let current = this.head;while (index++ < position && current) {current = current.next;}return current;}// 追加节点append(value: T) {// 1.根据value新建一个Node节点const newNode = new Node(value);// 2.if (!this.head) {this.head = newNode;} else {let current = this.head;while (current.next) {current = current.next;}// current肯定指向最后一个节点current.next = newNode;}// 3.size++this.size++;}// 遍历链表的方法traverse() {const values: T[] = [];let current = this.head;while (current) {values.push(current.value);current = current.next;}console.log(values.join(" -> "));}//链表插入元素的方法insert(value: T, position: number): boolean {// 1.越界判断if (position < 0 || position > this.size) return false;// 2.根据value创建新的节点const newNode = new Node(value);/* 3.判断* 是否插入头部* 否则就找到需要插入的位置,然后记录前一个节点和当前节点,在前一个节点的next等于newNode,newNode的next等于后一个节点*/if (position === 0) {newNode.next = this.head;this.head = newNode;} else {const previous = this.getNode(position - 1);newNode.next = previous!.next;previous!.next = newNode;}this.size++;return true;}//链表插入元素的方法removeAt(position: number): T | null {// 1.越界判断if (position < 0 || position >= this.size) return null;let current = this.head;if (position === 0) {this.head = current?.next ?? null;} else {const previous = this.getNode(position - 1);previous!.next = previous?.next?.next ?? null;}this.size--;return current?.value ?? null;}// 获取方法get(position: number): T | null {if (position < 0 || position >= this.size) return null;return this.getNode(position)?.value ?? null;}// 更新方法update(value: T, position: number): boolean {if (position < 0 || position >= this.size) return false;const currentNode = this.getNode(position);currentNode!.value = value;return true;}// 根据值获取该值位置索引indexOf(value: T): number {let current = this.head;let index = 0;while (current) {if (current.value === value) {return index;}index++;current = current.next;}return -1;}// 删除方法,根据value删除remove(value: T): T | null {const index = this.indexOf(value);return this.removeAt(index);}// 判断单链表是否为空的方法isEmpty() {return this.size === 0;}
}export {};
链表常见面试题
237-删除链表中的节点
  • https://leetcode.cn/problems/delete-node-in-a-linked-list/

image.png
解题

// Definition for singly-linked list.
class ListNode {val: number;next: ListNode | null;constructor(val?: number, next?: ListNode | null) {this.val = val === undefined ? 0 : val;this.next = next === undefined ? null : next;}
}/**Do not return anything, modify it in-place instead.*/
function deleteNode(node: ListNode | null): void {node!.val = node!.next!.valnode!.next = node!.next!.next
}
206 - 反转链表
  • https://leetcode.cn/problems/reverse-linked-list/

image.png

  • 用栈结构解决
function reverseList(head: ListNode | null): ListNode | null {if(head === null) return nullif(head.next === null) return headconst stack:ListNode[] = []let current:ListNode | null = headwhile(current) {stack.push(current)current = current.next}const newHead:ListNode = stack.pop()!let newHeadCurrent = newHeadwhile(stack.length) {const node = stack.pop()!newHeadCurrent.next = nodenewHeadCurrent = newHeadCurrent.next}newHeadCurrent.next = nullreturn newHead
};
  • 用非递归解题
function reverseList(head: ListNode | null): ListNode | null {if (head === null || head.next === null) return head;let newHead: ListNode | null = null;// 1.next = 2, 2.next = 3, 3.next = 4while (head) {// 让current指向下一个节点// 目的:保留下个节点的引用,可以拿到,并且不会销毁(current = 2)const current= head.next;// 改变head当前指向的节点,指向newHead// 这里反转链表对于第一节点来说,指向newHead就是null(1.next = null)head.next = newHead;// 让newhead指向head节点// 这里开始准备反转新的节点,目的是下一次遍历时,可以让下一个节点指向第一个节点(newHead = 1)newHead = head;// 让head指向下个节点也就是current(head = 2)head = current;}return newHead;
}

image.png

  • 用递归方案解题
function reverseList(head: ListNode | null): ListNode | null {// 递归停止条件,当递归到最后一个节点时停止if (head === null || head.next === null) return head;// 一直递归循环直到符合head === null 时停止递归// 那么拿到的就是链表倒数第二个节点const newHead = reverseList(head.next ?? null)// 反转链表,让最后一个节点指向head开始正式反转head.next.next = head// 让倒数第二个节点的next指向nullhead.next = null// 最后递归完了就是反转后的链表了return newHead
}

数组和链表的复杂度对比

接下来,我们使用大O表示法来对比一下数组和链表的时间复杂度:

image.png

  • 数组是一种连续的存储结构,通过下标可以直接访问数组中的任意元素。

  • 时间复杂度:对于数组,随机访问时间复杂度为o(1),插入和删除操作时间复杂度为o(n)。

  • 空间复杂度:数组需要连续的存储空间,空间复杂度为o(n)。

  • 链表是一种链式存储结构,通过指针链接起来的节点组成,访问链表中元素需要从头结点开始遍历。

  • 时间复杂度:对于链表,随机访问时间复杂度为o(n),插入和删除操作时间复杂度为o(1)。

  • 空间复杂度:链表需要为每个节点分配存储空间,空间复杂度为O(n)。

  • 在实际开发中,选择使用数组还是链表需要根据具体应用场景来决定。

  • 如果数据量不大,且需要频繁随机访问元素,使用数组可能会更好。

  • 如果数据量大,或者需要频繁插入和删除元素,使用链表可能会更好。

相关文章:

【TypeScript】常见数据结构与算法(二):链表

文章目录 链表结构&#xff08;LinkedList&#xff09;链表以及数组的缺点数组链表的优势 什么是链表?封装链表相关方法源码链表常见面试题237-删除链表中的节点206 - 反转链表 数组和链表的复杂度对比 链表结构&#xff08;LinkedList&#xff09; 链表以及数组的缺点 链表…...

原型模式 (Prototype Pattern)

定义&#xff1a; 原型模式&#xff08;Prototype Pattern&#xff09;是一种创建型设计模式&#xff0c;它用于创建重复的对象&#xff0c;同时保持性能。这种模式的核心思想是通过复制一个已存在的实例来创建新的实例&#xff0c;而不是新建实例并对其进行初始化。原型模式适…...

项目总结报告(案例模板)

软件项目总结报告模板套用&#xff1a; 项目概要项目工作分析经验与教训改进建议可纳入的项目过程资产 --------进主页获取更多资料-------...

C++ Qt QByteArray用法介绍

作者:令狐掌门 技术交流QQ群:675120140 csdn博客:https://mingshiqiang.blog.csdn.net/ 文章目录 一、QByteArray的基本用法1、初始化和赋值2、访问和修改元素3、 常用方法4、数据转换二、QByteArray与文件操作三、QByteArray与网络编程四、QByteArray数据编码1、Base64 编解…...

蓝桥杯物联网竞赛_STM32L071_3_Oled显示

地位&#xff1a; 对于任何一门编程语言的学习&#xff0c;print函数毫无疑问是一种最好的调试手段&#xff0c;调试者不仅能通过它获取程序变量的运行状态而且通过对其合理使用获取程序的运行流程&#xff0c;更能通过关键变量的输出帮你验证推理的正确与否&#xff0c;朴素的…...

python-opencv轮廓检测(外轮廓检测和全部轮廓检测,计算轮廓面积和周长)

python-opencv轮廓检测&#xff08;外轮廓检测和全部轮廓检测&#xff0c;计算轮廓面积和周长&#xff09; 通过cv2.findContours&#xff0c;我们可以进行轮廓检测&#xff0c;当然也有很多检测模式&#xff0c;我们可以通过选择检测模式&#xff0c;进行外轮廓检测&#xff…...

LeetCode [简单] 1. 两数之和

给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能重复出现。 你可以按任意顺序返回…...

C++设计模式之工厂模式(下)——抽象工厂模式

抽象工厂模式 介绍示例示例使用运行结果抽象工厂模式的优缺点优点缺点 总结 介绍 抽象工厂模式是一种创建型设计模式&#xff0c;它提供了一种封装一组相关或相互依赖对象的方式&#xff0c;而无需指定它们具体的类。它允许客户端使用抽象接口来创建一系列相关的对象&#xff…...

2023亚太杯数学建模A题思路分析 - 采果机器人的图像识别技术

1 赛题 问题A 采果机器人的图像识别技术 中国是世界上最大的苹果生产国&#xff0c;年产量约为3500万吨。与此同时&#xff0c;中国也是世 界上最大的苹果出口国&#xff0c;全球每两个苹果中就有一个&#xff0c;全球超过六分之一的苹果出口 自中国。中国提出了一带一路倡议…...

关于Flink的旁路缓存与异步操作

1. 旁路缓存 1. 什么是旁路缓存? 将数据库中的数据,比较经常访问的数据,保存起来,以减少和硬盘数据库的交互 比如: 我们使用mysql时 经常查询一个表 , 而这个表又一般不会变化,就可以放在内存中,查找时直接对内存进行查找,而不需要再和mysql交互 2. 旁路缓存例子使用 dim层…...

MyBatis-Plus的分页插件和乐观锁插件

MyBatis-Plus: 探索分页查询和乐观锁插件 在现代的Web应用开发中&#xff0c;高效的数据处理是不可或缺的一部分。MyBatis-Plus&#xff0c;作为MyBatis的增强版&#xff0c;提供了多种插件来简化和优化数据库操作。在这篇博客中&#xff0c;我们将重点介绍两个非常实用的插件…...

批量将本地N个英文Html文档进行中文翻译-操作篇

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列ChatGPT和AIGC &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分…...

解决cad找不到vcruntime140.dll的方法,实测有效的5个的方法

最近&#xff0c;我在使用CAD软件时遇到了一个困扰我已久的问题&#xff1a;由于找不到vcruntime140.dll文件而导致CAD无法正常运行。经过一番努力和尝试&#xff0c;我终于找到了解决这个问题的方法。那么&#xff0c;如何解决vcruntime140.dll丢失的问题呢&#xff1f;本文将…...

2023亚太杯数学建模C题:我国新能源电动汽车的发展趋势,思路模型代码

问题C 我国新能源电动汽车的发展趋势 赛题思路&#xff1a;获取思路见文末名片&#xff0c;第一时间更新 新能源汽车是指以先进技术原理、新技术、新结构的非常规汽车燃料为动力来源( 非常规汽车燃料指汽油、柴油以外的燃料&#xff09;&#xff0c;将先进技术进行汽车动力控制…...

英语学习-爆破音

英文爆破音有&#xff1a;[p],[b],[t],[d],[k],[g]。 同时爆破音的发音会根据前后音的不同&#xff0c;发音不同&#xff0c;具体如下&#xff1a; ⒈ [p],[b],[t],[d],[k],[g] 中的任何两个音素相邻时&#xff0c;前面的发不完全爆破音&#xff0c;后面的就要完全地爆破。如…...

【Vue】图片切换

上一篇&#xff1a; vue的指令 https://blog.csdn.net/m0_67930426/article/details/134599378?spm1001.2014.3001.5502 本篇所需要的指令有&#xff1a; v-on v-bind v-show <!DOCTYPE html> <html lang"en"> <head><meta charset"…...

C++模拟如何实现vector的方法

任意位置插入&#xff0c;insert的返回值为新插入的第一个元素位置的迭代器&#xff1b;因为插入可能会进行扩容&#xff0c;导致start的值改变&#xff0c;所以先定义一个变量保存pos与start的相对位置&#xff1b;判断是否需要扩容&#xff1b;从插入位置开始&#xff0c;将所…...

芯知识 | 混音播报语音芯片的优势:革新音频应用的新力量

随着科技的进步&#xff0c;语音芯片在各个领域的应用越来越广泛。而在众多语音芯片中&#xff0c;混音播报语音芯片以其独特的优势&#xff0c;正逐渐成为音频应用领域的翘楚。本文将重点探讨混音播报语音芯片的优势及其在现代科技应用中的价值。 一、混音播报语音芯片概述 …...

Arduino驱动PT100数字K型高温传感器(温湿度传感器)

目录 1、传感器特性 2、控制器和传感器连线图 3、硬件原理图 4、驱动程序 PT100适用于大部分400℃以下高温的测量,但是通常家用天然气灶焰芯温度可达800℃以上,烧制陶瓷的窖子或者大功率电炉温度更可超过1000℃,在这些超高温度的场景下就需要用到K型热电偶。...

【C/PTA —— 11.函数2(课外实践)】

C/PTA —— 11.函数2&#xff08;课外实践&#xff09; 一.函数题6-1 计算A[n]1/(1 A[n-1])6-2 递归实现顺序输出整数6-3 自然数的位数(递归版)6-4 分治法求解金块问题6-5 汉诺塔6-6 重复显示字符(递归版)6-7 显示平行四边形(右)(递归版) 二.编程题7-2 N阶楼梯上楼问题 一.函数…...

Python|GIF 解析与构建(5):手搓截屏和帧率控制

目录 Python&#xff5c;GIF 解析与构建&#xff08;5&#xff09;&#xff1a;手搓截屏和帧率控制 一、引言 二、技术实现&#xff1a;手搓截屏模块 2.1 核心原理 2.2 代码解析&#xff1a;ScreenshotData类 2.2.1 截图函数&#xff1a;capture_screen 三、技术实现&…...

云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?

大家好&#xff0c;欢迎来到《云原生核心技术》系列的第七篇&#xff01; 在上一篇&#xff0c;我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在&#xff0c;我们就像一个拥有了一块崭新数字土地的农场主&#xff0c;是时…...

简易版抽奖活动的设计技术方案

1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

CMake基础:构建流程详解

目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...

Springboot社区养老保险系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;社区养老保险系统小程序被用户普遍使用&#xff0c;为方…...

虚拟电厂发展三大趋势:市场化、技术主导、车网互联

市场化&#xff1a;从政策驱动到多元盈利 政策全面赋能 2025年4月&#xff0c;国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》&#xff0c;首次明确虚拟电厂为“独立市场主体”&#xff0c;提出硬性目标&#xff1a;2027年全国调节能力≥2000万千瓦&#xff0…...

安卓基础(Java 和 Gradle 版本)

1. 设置项目的 JDK 版本 方法1&#xff1a;通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分&#xff0c;设置 Gradle JDK 方法2&#xff1a;通过 Settings File → Settings... (或 CtrlAltS)…...

ArcPy扩展模块的使用(3)

管理工程项目 arcpy.mp模块允许用户管理布局、地图、报表、文件夹连接、视图等工程项目。例如&#xff0c;可以更新、修复或替换图层数据源&#xff0c;修改图层的符号系统&#xff0c;甚至自动在线执行共享要托管在组织中的工程项。 以下代码展示了如何更新图层的数据源&…...

GeoServer发布PostgreSQL图层后WFS查询无主键字段

在使用 GeoServer&#xff08;版本 2.22.2&#xff09; 发布 PostgreSQL&#xff08;PostGIS&#xff09;中的表为地图服务时&#xff0c;常常会遇到一个小问题&#xff1a; WFS 查询中&#xff0c;主键字段&#xff08;如 id&#xff09;莫名其妙地消失了&#xff01; 即使你在…...

本地部署drawDB结合内网穿透技术实现数据库远程管控方案

文章目录 前言1. Windows本地部署DrawDB2. 安装Cpolar内网穿透3. 实现公网访问DrawDB4. 固定DrawDB公网地址 前言 在数字化浪潮席卷全球的背景下&#xff0c;数据治理能力正日益成为构建现代企业核心竞争力的关键因素。无论是全球500强企业的数据中枢系统&#xff0c;还是初创…...