(五)循环链表、双向链表

循环链表

介绍

在单选链表基础上,下一个节点指向前一个节点,最后一个节点指向起点

封装循环链表

为了让循环链表可以继承自单向链表,对其进行重构

给其增加一个tail属性(尾节点),对各方法进行重写整理

import ILinkedList from "./ILinkedList";
// 1.创建Node节点类
class Node<T> {value:T;next:Node<T> | null = null;constructor(value:T) {this.value = value}
}// 2.创建LinkedList类
class LinkedList<T> implements ILinkedList<T> {protected  head:Node<T> | null = null;protected size:number = 0;// 新增属性:尾结点protected tail:Node<T> | null = null;get length() {return this.size}// 封装私有方法// 根据position获取当前的节点protected getNode(position:number): Node<T> | null{let current = this.headlet index = 0while(index++ < position && current) {current = current.next}return current}// 判断是否为最后一个节点private isTail(node:Node<T>): boolean {return node === this.tail}// 1.追加节点append(value:T) {// 1.根据value创建一个新节点const newNode = new Node(value)// 2.判断this.head是否为nulif(this.head === null) {this.head = newNode}else {this.tail!.next = newNode}this.tail = newNodethis.size++}// 2.遍历链表traverse() {const values:T[] = []let current = this.headwhile(current) {values.push(current.value)if(this.isTail(current)){//已经遍历到最后一个节点current = null}else{current = current.next}}if(this.head && this.tail?.next === this.head) {values.push(this.head.value)}console.log(values.join('->'));//aaa->bbb->ccc->ddd}// 3.插入方法insert(value: T, position: number): boolean {// 1. 边界条件判断if (position < 0 || position > this.size) return false;// 2. 根据 value 创建新的节点const newNode = new Node(value);// 3. 判断是否需要插入头部if (position === 0) {newNode.next = this.head;this.head = newNode;} else {// 4.找到插入位置的前一个节点/* let current = this.head;let previous: Node<T> | null = null;let index = 0;while(current && index < position ){previous = currentcurrent = current.next;index++}previous!.next = newNode;newNode.next = current; */// 重构代码const previous = this.getNode(position - 1);newNode.next = previous?.next ?? null;previous!.next = newNode;if(position === this.length) {this.tail = newNode;}}// 6. 更新链表大小this.size++;return true;}// 4.删除方法removeAt(position:number): T | null {// 1.越界判断if(position < 0 || position >= this.size) return null;// 2.判断是否删除第一个节点let current = this.head;if(position === 0) {this.head = current?.next ?? null;if(this.length === 1){this.tail = null;}}else{/* let previous: Node<T> | null = null;let index = 0;while(index++ < position && current) {previous = current;current = current.next}// 找到需要的节点previous!.next = current?.next ?? null; */// 重构为如下代码const previous = this.getNode(position - 1)previous!.next = previous?.next?.next ?? null;current = previous!.next;if(position === this.length - 1) {this.tail = previous;}}this.size--;return current?.value  ?? null;}// 5.获取方法get(position:number): T | null {// 5.1越界命题if(position < 0 || position >= this.size) return null;// 5.2查找元素return this.getNode(position)?.value  ?? null;}// 7.更新方法update(value:T,position:number): boolean {if(position < 0 || position >= this.size) return false;const currentNode = this.getNode(position)currentNode!.value = value;return true;}// 8.根据值获取对应位置的索引indexOf(value:T): number {// 从第一个节点开始,向后遍历let current = this.head;let index = 0;while(current) {if(current.value === value) {return index}if(this.isTail(current)){current = null}else{index++current = current.next}}return -1;}// 9.根据value删除节点remove(value:T): T | null {const index = this.indexOf(value)return this.removeAt(index)}// 10.判断链表是否为空isEmpty(): boolean {return this.size === 0}
}const linkedList = new LinkedList<string>()export default LinkedList

封装CircularLinkedList

import LinkedList from "./01-实现单选LinkedList"class CircularLinkedList<T> extends LinkedList<T> {// 重新实现的方法: append方法append(value:T) {super.append(value)this.tail!.next = this.head}// traverse方法// insert方法insert(value: T, position: number): boolean {const isSuccess = super.insert(value, position)if(isSuccess && (position === this.length || position === 0)){this.tail!.next = this.head}return isSuccess}// removeAt方法removeAt(position: number): T | null {const value = super.removeAt(position)if(value && this.tail &&(position === 0 || position === this.length-1)){this.tail.next = this.head}return value}// indexOf方法
}const cLinkedList = new CircularLinkedList<string>()
cLinkedList.append('aaa')
cLinkedList.append('bbb')
cLinkedList.append('ccc')
cLinkedList.append('ddd')
cLinkedList.insert('eee', 0)
cLinkedList.insert('fff', 5)cLinkedList.removeAt(5)
cLinkedList.traverse()console.log('--------测试get----------');
console.log(cLinkedList.get(0));
console.log(cLinkedList.get(1));
console.log(cLinkedList.get(4));console.log('--------测试update----------');
cLinkedList.update('hhh', 0)
cLinkedList.update('iii', 2)
cLinkedList.traverse()

双向链表

介绍

既可以从头遍历到尾,也可以从尾遍历到头

封装双向链表

import LinkedList from "./01-实现单选LinkedList"; class DoublyNode<T> extends LinkedList<T> {
}

2.append方法

情况一:当添加的是第一个节点时→让head和tail都指向新节点

情况二:当添加的是其它节点时,需改变相关节点的指引关系

代码实现:

  protected head: DoublyNode<T> | null = null;protected tail: DoublyNode<T> | null = null;
// append方法append(value: T): void {const newNode = new DoublyNode(value)// 情况一:链表为空if(!this.head) {this.head = newNodethis.tail = newNode}else{// 情况二:链表不为空this.tail!.next = newNode// 不能将父类的类型赋值给子类;可以将子类的类型赋值给父类newNode.prev = this.tailthis.tail = newNode}this.size++}

测试代码:

dLinkedList.append("aaa")
dLinkedList.append("bbb")
dLinkedList.append("ccc")
dLinkedList.append("ddd")
dLinkedList.traverse()

3.prepend方法-从头部插入

情况一:链表本身为空

情况二:链表有值

prepend(value:T):void {const newNode = new DoublyNode(value)if(!this.head) {this.head = newNodethis.tail = newNode}else{newNode.next = this.headthis.head.prev = newNodethis.head = newNode}this.size++}

4.反向遍历-从尾到头

postTraverse() {const values:T[] = []let current = this.tailwhile(current) {values.push(current.value)current = current.prev}console.log(values.join('->'));//ddd->ccc->bbb->aaa->zzz
}

5.insert插入节点

情况一:插入位置为0时

情况二:插入位置为末尾时

情况三:链表中间位置时

insert(value: T, position: number): boolean {if(position < 0 || position > this.length) return false;if(position === 0) {this.prepend(value)}else if(position === this.length){this.append(value)}else{const newNode = new DoublyNode(value)const current = this.getNode(position)! as DoublyNode<T>current!.prev!.next = newNodenewNode.next = currentnewNode.prev = current!.prevcurrent!.prev = newNodethis.size++}return true}

6.removeAt(position)

情况一:当删除0位置时:

1.当链表长度为1时:

2.链表长度不为1时:

情况二:删除末尾位置:

情况三:删除中间位置:

// 索引删除removeAt(position: number): T | null {if(position < 0 || position >= this.length) return nulllet current = this.headif(position === 0) {if(this.length === 1){this.head = nullthis.tail = null}else {this.head = this.head!.nextthis.head!.prev = null}}else if(position === this.length - 1){current = this.tailthis.tail= this.tail!.prevthis.tail!.next = null}else {current = this.getNode(position)! as DoublyNode<T>current.prev!.next = current.nextcurrent.next!.prev = current.prev}this.size--;return current?.value?? null;}

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

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

相关文章

仙剑奇侠传98柔情版游戏秘籍

战斗秘技&#xff1a;在战斗中输入 “cheat”&#xff0c;然后输入 “v” 直接取胜&#xff1b;输入 “y” 敌人不攻击。另外&#xff0c;在战斗中按 “XJPXZ123” 加 “shift” 键&#xff0c;攻击力增加 1000&#xff05;。等级提升秘籍&#xff1a;当李逍遥等级到达 99 级时…

常见的归一化(Normalization)方法

本文详解深度学习中常见的归一化方法。 【归一化是将数据按比例缩放&#xff0c;使之落入一个特定的区间】目录 1. 批量归一化&#xff08;Batch Normalization&#xff0c;BN&#xff09;1.1 数学原理1.2 代码示例 2. 层归一化&#xff08;Layer Normalization&#xff0c;LN&…

行星际激波在日球层中的传播:Propagation of Interplanetary Shocks in the Heliosphere (参考文献部分)

行星际激波在日球层中的传播&#xff1a;Propagation of Interplanetary Shocks in the Heliosphere &#xff08;第一部分&#xff09;-CSDN博客 行星际激波在日球层中的传播&#xff1a;Propagation of Interplanetary Shocks in the Heliosphere &#xff08;第二部分&…

大模型可视化应用敏捷开发方案:Dify+Echarts

大模型相关目录 大模型&#xff0c;包括部署微调prompt/Agent应用开发、知识库增强、数据库增强、知识图谱增强、自然语言处理、多模态等大模型应用开发内容 从0起步&#xff0c;扬帆起航。 Moe模式&#xff1a;或将是最好的大模型应用开发路径一文带你了解大模型RAG详细记录…

23种GoF设计模式

GoF&#xff08;Gang of Four&#xff09;设计模式是由四位计算机科学家 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 合著的书籍《Design Patterns: Elements of Reusable Object-Oriented Software》中提出的设计模式 目录 一、创建型模式&#xff08;Cre…

Losson 4 NFS(network file system(网络文件系统))

网络文件系统&#xff1a;在互联网中共享服务器中文件资源。 使用nfs服务需要安装:nfs-utils 以及 rpcbind nfs-utils : 提供nfs服务的程序 rpcbind &#xff1a;管理nfs所有进程端口号的程序 nfs的部署 1.客户端和服务端都安装nfs-utils和rpcbind #安装nfs的软件rpcbind和…

C++ 入门六:多态 —— 同一接口的多种实现之道

在面向对象编程中&#xff0c;多态是最具魅力的特性之一。它允许我们通过统一的接口处理不同类型的对象&#xff0c;实现 “一个接口&#xff0c;多种实现”。本章将从基础概念到实战案例&#xff0c;逐步解析多态的核心原理与应用场景&#xff0c;帮助新手掌握这一关键技术。 …

关于C使用Windows API获取系统管理员权限和对文本属性的操作,以及windows API的核心操作

关于windows系统的操作程序开发&#xff0c;本文介绍一部分重要的文本属性操作&#xff0c;和运行计次器。 获取系统管理员权限 #include <windows.h> VOID ManagerRun(LPCSTR exe, LPCSTR param, INT nShow) { //注意&#xff1a;会跳出提示。SHELLEXECUTEINFO ShExec…

Web 项目实战:构建属于自己的博客系统

目录 项目效果演示 代码 Gitee 地址 1. 准备工作 1.1 建表 1.2 引入 MyBatis-plus 依赖 1.3 配置数据库连接 1.4 项目架构 2. 实体类准备 - pojo 包 2.1 dataobject 包 2.2 request 包 2.3 response 包 2.3.1 统一响应结果类 - Result 2.3.2 用户登录响应类 2.3.3…

从“被动跳闸”到“主动预警”:智慧用电系统守护老旧小区安全

安科瑞顾强 近年来&#xff0c;老旧小区电气火灾事故频发&#xff0c;成为威胁居民生命财产安全的重要隐患。据统计&#xff0c;我国居住场所火灾伤亡人数远超其他场所&#xff0c;仅今年一季度就发生8.3万起住宅火灾&#xff0c;造成503人遇难。这些建筑多建于上世纪&#x…

【深入浅出 Git】:从入门到精通

这篇文章介绍下版本控制器。 【深入浅出 Git】&#xff1a;从入门到精通 Git是什么Git的安装Git的基本操作建立本地仓库配置本地仓库认识工作区、暂存区、版本库的概念添加文件添加文件到暂存区提交文件到版本库提交文件演示 理解.git目录中的文件HEAD指针与暂存区objects对象 …

Mybatis的简单介绍

文章目录 MyBatis 简介 1. MyBatis 核心特点2. MyBatis 核心组件3. MyBatis 基本使用示例(1) 依赖引入&#xff08;Maven&#xff09;(2) 定义 Mapper 接口(3) 定义实体类(4) 在 Service 层调用 4. MyBatis 与 JPA/Hibernate 对比 MyBatis 简介 MyBatis 是一款优秀的 持久层框…

Android Studio 在 Windows 上的完整安装与使用指南

Android Studio 在 Windows 上的完整安装与使用指南—目录 一、Android Studio 简介二、下载与安装1. 下载 Android Studio2. 安装前的依赖准备3. 安装步骤 三、基础使用指南1. 首次启动配置2. 创建第一个项目3. 运行应用4. 核心功能 四、进阶功能配置1. 配置 SDK 和工具2. 自定…

WPF 绑定方式举例

WPF 绑定方式举例 一、如果ItemsControl 控件的ItemsSource要绑定到List类型&#xff0c;可以如下&#xff1a; List<string> Names new List<string>(); Names.Add("aaa"); Names.Add("bbb");<ItemsControl ItemsSource"{Binding …

LangSmith 设置指南

什么是 LangSmith&#xff1f; LangSmith 是 LangChain 团队开发的一个统一开发者平台&#xff0c;用于构建、测试、评估和监控基于大型语言模型&#xff08;LLM&#xff09;的应用程序。它提供了一套工具&#xff0c;帮助开发者更好地理解、调试和改进他们的 LLM 应用。 注册…

手撕TCP内网穿透及配置树莓派

注意&#xff1a; 本文内容于 2025-04-13 15:09:48 创建&#xff0c;可能不会在此平台上进行更新。如果您希望查看最新版本或更多相关内容&#xff0c;请访问原文地址&#xff1a;手撕TCP内网穿透及配置树莓派。感谢您的关注与支持&#xff01; 之前入手了树莓派5&#xff0c;…

Java从入门到“放弃”(精通)之旅——程序逻辑控制④

Java从入门到“放弃”&#xff08;精通&#xff09;之旅&#x1f680;&#xff1a;程序逻辑的完美理解 一、开篇&#xff1a;程序员的"人生选择" 曾经的我&#xff0c;生活就像一段顺序执行的代码&#xff1a; System.out.println("早上8:00起床"); Syste…

学习笔记九——Rust所有权机制

&#x1f980; Rust 所有权机制 &#x1f4da; 目录 什么是值类型和引用类型&#xff1f;值语义和引用语义&#xff1f;什么是所有权&#xff1f;为什么 Rust 需要它&#xff1f;所有权的三大原则&#xff08;修正版&#xff09;移动语义 vs 复制语义&#xff1a;变量赋值到底…

Cocos Creator Shader入门实战(八):Shader实现圆形、椭圆、菱形等头像

引擎&#xff1a;3.8.5 您好&#xff0c;我是鹤九日&#xff01; 回顾 Shader的学习是一条漫长的道路。 理论知识的枯燥无味&#xff0c;让很多人选择了放弃。然而不得不说&#xff1a;任何新知识、新领域的学习&#xff0c;本身面临的都是问题&#xff01; 互联网和AI给了我…

深入理解计算机操作系统(持续更新中...)

文章目录 一、计算机系统漫游1.1信息就是位上下文 一、计算机系统漫游 1.1信息就是位上下文 源程序实际上就是一个由值0和1组成的位&#xff08;又称为比特&#xff09;&#xff0c;八个位被组织成一组&#xff0c;称为字节。每个字节表示程序中的某些文本字符 大部分现代计…