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

数据结构与算法【链表:一】Java实现

目录

链表

单向链表

哨兵链表

双向链表

环形链表


链表

链表是数据元素的线性集合,其每个元素都指向下一个元素,元素存储上并不连续。

随机访问性能

根据 index 查找,时间复杂度 O(n)

插入或删除性能

  • 起始位置:O(1)
  • 结束位置:如果已知 tail 尾节点是 O(1)[双向链表],不知道 tail 尾节点是 O(n)
  • 中间位置:根据 index 查找时间 + O(1)

单向链表

单向链表中每个元素只知道下一个节点位置

单向链表的简单实现

public class SinglyLinkList implements Iterable<Integer> {private Node head = null;//节点类private static class Node {int value;Node next;public Node(int value, Node next) {this.value = value;this.next = next;}}//提供链表方法public void addFirst(int value) {head = new Node(value, head);}//向链表尾部添加节点public void addLast(int value) {Node last = findLast();if (last == null) {addFirst(value);}last.next = new Node(value, null);}private Node findLast() {if (head.next == null) {return null;}Node p = head;while (p.next != null) {p = p.next;}return p;}//遍历链表public void loop(Consumer<Integer> consumer) {Node pointer = head;while (pointer != null) {consumer.accept(pointer.value);pointer = pointer.next;}}@Overridepublic Iterator<Integer> iterator() {return new Iterator<Integer>() {Node pointer = head;@Overridepublic boolean hasNext() {return pointer != null;}@Overridepublic Integer next() {int value = pointer.value;pointer = pointer.next;return value;}};}// 根据索引查找节点值private Node findByIndex(int index) {int i = 0;for (Node p = head; p != null; p = p.next, i++) {if (index == i) {return p;}}return null;}public int get(int index) {Node node = findByIndex(index);if (node == null) {throw new IllegalArgumentException("索引超出范围");}return node.value;}//插入元素public void insert(int index, int value) {if (index == 0) {addFirst(value);return;}//查找索引的上一个节点Node node = findByIndex(index - 1);if (node == null) {throw new IllegalArgumentException("索引超出范围");}node.next = new Node(value, node.next);}//移除首节点public void removeFirst() {if (head==null){throw new RuntimeException("链表为空。无法移除");}head = head.next;}//删除指定节点public void remove(int index){if (index==0){removeFirst();}//找到要删除的节点的前驱节点Node node = findByIndex(index - 1);Node remove = node.next;if (remove==null){//要删除的节点位置为空throw new IllegalArgumentException("索引越界");}node.next = remove.next;}
}

这种实现方法有一个弊端,那就是每次添加或是删除元素时都需要进行判断链表是否为空。因此提出了哨兵链表的概念

哨兵链表

所谓哨兵列表,就是头节点不存储数据,只为简化边缘判断使用。

具体代码如下

public class SinglyLinkListSentinel implements Iterable<Integer> {//头节点指向哨兵节点。值可以随便给private Node head = new Node(723468, null);//节点类private static class Node {int value;Node next;public Node(int value, Node next) {this.value = value;this.next = next;}}public void addFirst(int value) {//在链表头部添加节点时,应该在哨兵之后。insert(0, value);}//向链表尾部添加节点public void addLast(int value) {Node last = findLast();last.next = new Node(value, null);}private Node findLast() {Node p = head;while (p.next != null) {p = p.next;}return p;}//遍历链表public void loop(Consumer<Integer> consumer) {//从哨兵节点之后的节点开始遍历Node pointer = head.next;while (pointer != null) {consumer.accept(pointer.value);pointer = pointer.next;}}@Overridepublic Iterator<Integer> iterator() {return new Iterator<Integer>() {Node pointer = head.next;@Overridepublic boolean hasNext() {return pointer != null;}@Overridepublic Integer next() {int value = pointer.value;pointer = pointer.next;return value;}};}// 根据索引查找节点值private Node findByIndex(int index) {//这里设置为-1是排除哨兵节点带来的影响int i = -1;for (Node p = head; p != null; p = p.next, i++) {if (index == i) {return p;}}return null;}public int get(int index) {Node node = findByIndex(index);if (node == null) {throw new IllegalArgumentException("索引超出范围");}return node.value;}//插入元素public void insert(int index, int value) {//查找索引的上一个节点Node node = findByIndex(index - 1);if (node == null) {throw new IllegalArgumentException("索引超出范围");}node.next = new Node(value, node.next);}//移除首节点public void removeFirst() {remove(0);}//删除指定节点public void remove(int index) {//找到要删除的节点的前驱节点Node node = findByIndex(index - 1);Node remove = node.next;if (remove == null) {//要删除的节点位置为空throw new IllegalArgumentException("索引越界");}node.next = remove.next;}
}

双向链表

每个元素知道上一个元素与下一个元素的地址

简单实现如下

public class DoublyLinkedListSentinel implements Iterable<Integer>{//头哨兵private Node head = new Node(null, null, 0);//尾哨兵private Node tail = new Node(null, null, 0);public DoublyLinkedListSentinel() {//初始化时,对头尾哨兵进行指定head.next = tail;tail.prev = head;}private static class Node {Node prev;Node next;int value;public Node(Node prev, Node next, int value) {this.prev = prev;this.next = next;this.value = value;}}//提供双向链表方法public Node findByIndex(int index) {int i = -1;for (Node p = head; p != tail; p = p.next, i++) {if (index == i) {return p;}}return null;}//插入首元素public void addFirst(int value) {insert(0, value);}//插入元素public void insert(int index, int value) {Node prev = findByIndex(index - 1);if (prev == null) {throw new RuntimeException("下标越界");}Node next = prev.next;Node node = new Node(prev, next, value);prev.next = node;next.prev = node;}//向尾节点添加public void addLast(int value){Node prev = tail.prev;Node addNode = new Node(prev, tail, value);prev.next = addNode;tail.prev = addNode;}//删除首节点public void removeFirst(){if (head.next==tail){throw new RuntimeException("链表为空");}Node remove = head.next;Node next = remove.next;head.next = next;next.prev = head;}//删除元素public void remove(int index) {Node prev = findByIndex(index - 1);if (prev == null) {throw new RuntimeException("下标越界");}Node remove = prev.next;Node next = remove.next;if (remove==tail){throw new RuntimeException("下标越界");}prev.next = next;next.prev = prev;}//获取元素public int get(int index){Node node = findByIndex(index);return node.value;}//遍历元素public void loop(Consumer<Integer> consumer) {Node pointer = head.next;while (pointer != tail) {consumer.accept(pointer.value);pointer = pointer.next;}}//迭代器实现@Overridepublic Iterator<Integer> iterator() {return new Iterator<Integer>() {Node p =head.next;@Overridepublic boolean hasNext() {return p!=tail;}@Overridepublic Integer next() {int value = p.value;p = p.next;return value;}};}
}

环形链表

环形链表的尾节点指向的是头节点head

环形链表我们也可以引入哨兵,空链表时,哨兵既做头节点也做尾节点。

简单实现代码如下

public class DoublyLinkedListSentinelTo implements Iterable<Integer> {private Node sentinel = new Node(null, null, -1);public DoublyLinkedListSentinelTo() {sentinel.prev = sentinel;sentinel.next = sentinel;}@Overridepublic Iterator<Integer> iterator() {return new Iterator<Integer>() {Node p = sentinel.next;@Overridepublic boolean hasNext() {return p != sentinel;}@Overridepublic Integer next() {int value = p.value;p = p.next;return value;}};}private static class Node {Node prev;Node next;int value;public Node(Node prev, Node next, int value) {this.prev = prev;this.next = next;this.value = value;}}//提供循环链表的方法public Node findByIndex(int index) {int i = 0;for (Node p = sentinel.next; p != sentinel; i++, p = p.next) {if (index == i) {return p;}}return null;}public void addFirst(int value) {Node next = sentinel.next;Node addNode = new Node(sentinel, next, value);sentinel.next = addNode;next.prev = addNode;}public void insert(int index, int value) {if (index == 0) {addFirst(value);}Node prev = findByIndex(index - 1);if (prev == null) {throw new RuntimeException("下标越界");}Node next = prev.next;Node insertNode = new Node(prev, next, value);next.prev = insertNode;prev.next = insertNode;}public void addLast(int value) {Node prev = sentinel.prev;Node addNode = new Node(prev, sentinel, value);prev.next = addNode;sentinel.prev = addNode;}//删除第一个节点public void removeFirst() {Node remove = sentinel.next;if (remove == sentinel) {throw new RuntimeException("下标越界");}Node next = remove.next;sentinel.next = next;next.prev = sentinel;}//删除最后一个节点public void removeLast() {Node remove = sentinel.prev;if (remove == sentinel) {throw new RuntimeException("下标越界");}Node prev = remove.prev;prev.next = sentinel;sentinel.prev = prev;}//删除指定删除节点public void remove(int index) {if (index == 0) {removeFirst();}Node prev = findByIndex(index - 1);Node remove = prev.next;if (remove == sentinel) {throw new RuntimeException("下标越界");}Node next = remove.next;prev.next = next;next.prev = prev;}//遍历节点public void loop(Consumer<Integer> consumer) {for (Node p = sentinel.next; p != sentinel; p = p.next) {consumer.accept(p.value);}}
}

相关文章:

数据结构与算法【链表:一】Java实现

目录 链表 单向链表 哨兵链表 双向链表 环形链表 链表 链表是数据元素的线性集合&#xff0c;其每个元素都指向下一个元素&#xff0c;元素存储上并不连续。 随机访问性能 根据 index 查找&#xff0c;时间复杂度 O(n) 插入或删除性能 起始位置&#xff1a;O(1)结束位…...

数据结构 | 队列的实现

数据结构 | 队列的实现 文章目录 数据结构 | 队列的实现队列的概念及结构队列的实现队列的实现头文件&#xff0c;需要实现的接口 Queue.h初始化队列队尾入队列【重点】队头出队列【重点】获取队列头部元素获取队列队尾元素获取队列中有效元素个数检测队列是否为空销毁队列 Que…...

flutter 集成 高德地图,退出界面闪退

android:allowNativeHeapPointerTagging"false"应用尝试释放系统堆分配器未分配的指针。 应用中的某个部分修改了指针的顶部字节。不能修改指针的顶部字节&#xff0c;您需要更改代码来修复此问题。 指针的顶部字节被错误使用或修改的示例包括&#xff1a; 指向特定…...

数据结构----链式栈的操作

链式栈的定义其实和链表的定义是一样的&#xff0c;只不过在进行链式栈的操作时要遵循栈的规则----即“先进后出”。 1.链式栈的定义 typedef struct StackNode {SElemType data;struct StackNode *next; }StackNode,*LinkStack; 2.链式栈的初始化 Status InitStack(LinkSta…...

识别伪装IP的网络攻击方法

识别伪装IP的网络攻击可以通过以下几种方法&#xff1a; 观察IP地址的异常现象。攻击者在使用伪装IP地址进行攻击时&#xff0c;往往会存在一些异常现象&#xff0c;如突然出现的未知IP地址、异常的流量等。这些现象可能是攻击的痕迹&#xff0c;需要对此加以留意。 检查网络通…...

C 语言指针

C 语言指针 在本教程中&#xff0c;您将学习指针。什么是指针&#xff0c;如何使用它们以及在示例的帮助下使用它们时可能遇到的常见错误。 指针是 C和C 编程的强大功能。在学习指针之前&#xff0c;让我们学习一下C语言编程中的地址。 C 语言地址 如果程序中有变量var&am…...

学【Java多态】-- 写高质量代码

多态的实现条件 在java中要实现&#xff0c;必须要满足如下几个条件&#xff0c;缺一不可。 1.必须在继承体系下2.子类必须要对父类中的方法进行重写3.通过父类的引用调用冲写的方法。 想要真正的学好多态需要去学习一些前置知识&#xff0c;那我们直接开始吧&#xff01; …...

【汇编】内存的读写与地址空间、寄存器及数据存储

文章目录 前言一、CPU对存储器的读写1.1 cpu对存储器的读写如何进行&#xff1f;1.2 演示 二、内存地址空间三、将各类存储器看作一个逻辑存储器——统一编址内存地址空间的分配方案 三、CPU的组成寄存器是CPU内部的信息存储单元通用寄存器--AX为例“横看成岭侧成峰“ 四、“字…...

DSP生成hex方法

以下使用两种方法生成的HEX文件&#xff0c;亲测可用 &#xff08;1&#xff09;万能法 不管.out文件是哪个版本CCS编译器生成的&#xff0c;只要用HEX2000.exe软件&#xff0c;翻译都可以使用。方法&#xff1a; hex2000 -romwidth 16 -memwidth 16 -i -o 20170817chuankou…...

GZ038 物联网应用开发赛题第7套

2023年全国职业院校技能大赛 高职组 物联网应用开发 任 务 书 &#xff08;第7套卷&#xff09; 工位号&#xff1a;______________ 第一部分 竞赛须知 一、竞赛要求 1、正确使用工具&#xff0c;操作安全规范&#xff1b; 2、竞赛过程中如有异议&#xff0c;可向现场考评…...

ELK之Logstash解析时间相差8h的问题

一、问题描述 服务器当前时间为&#xff1a;2022年 06月 28日 星期二 11:24:22 CST 而logstash解析的时间为2022-06-28T03:15:25.545Z与实际时间相差8h 一、解决办法&#xff1a; 需改logstash的配置文件&#xff1a; 原理就是&#xff1a;定义一个中间变量timestamp&…...

uniapp+vite+vue3开发跨平台app,运行到安卓模拟器调试方法

因为没有使用hbuilder开发uniapp&#xff0c;而是使用了vscode和vite来开发的&#xff0c;所以怎么将这个程序运行到安卓模拟器调试开发呢&#xff1f;其实方法很简单&#xff0c;使用android studio创建一个模拟器或者其他mumu模拟器&#xff0c;然后将项目使用hbuilder打开&a…...

Ubuntu诞生已经19年了

导读2004 年 10 月 20 日&#xff0c;Ubuntu 4.10 正式发布&#xff0c;代号‘Warty Warthog’。 2004 年 10 月 20 日&#xff0c;Ubuntu 4.10 正式发布&#xff0c;代号‘Warty Warthog’。 ▲ Ubuntu 4.10 与最新版 Ubuntu 23.10 的对比 作为 Ubuntu 第一个版本&#xff0…...

跟着基金买,别墅靠大海?买基金重仓股票,会破产吗?| 附最新选股结果

2020年A股经历了一波结构性牛市。 抱团核心资产的公募基金历史性大赚2万亿&#xff0c;一跃成为全市场顶流。不仅常年霸榜热搜&#xff0c;甚至连游戏直播的弹幕都在讨论基金。 很多年轻人也纷纷跑步入场&#xff0c;毕竟支付宝买基金贼方便。 可惜好景不长&#xff0c;大盘急…...

【教3妹学编辑-mysql】mybatis查询条件遇到的坑及解决方案

2哥 :3妹&#xff0c;今天怎么下班这么晚啊。 3妹&#xff1a;嗨&#xff0c;别提了&#xff0c;今天线上出bug了&#xff0c; 排查了好久。 2哥&#xff1a;啊&#xff0c;什么问题呀&#xff1f; 3妹&#xff1a;我们内部的一个管理系统报错了&#xff0c; 最近排查下来是myb…...

032-从零搭建微服务-定时服务(一)

写在最前 如果这个项目让你有所收获&#xff0c;记得 Star 关注哦&#xff0c;这对我是非常不错的鼓励与支持。 源码地址&#xff08;后端&#xff09;&#xff1a;mingyue: &#x1f389; 基于 Spring Boot、Spring Cloud & Alibaba 的分布式微服务架构基础服务中心 源…...

精通Nginx(11)-缓存

缓存能够存储请求的响应结果,以供未来再次使用,进而加速内容的提供。内容缓存可以缓存完整的响应,减少上游服务器的负载,避免了每次都为相同的请求重新运行计算和查询的麻烦。缓存可以提高性能并减少负载,这意味着可以用更少的资源更快地提供服务。NGINX 允许在NGINX 服务…...

用excel计算矩阵的乘积

例如&#xff0c;我们要计算两个矩阵的乘积&#xff0c; 第一个矩阵是2*2的&#xff1a; 1234 第2个矩阵是2*3的&#xff1a; 5697810 在excel中鼠标点到其它空白的地方&#xff0c;用来存放矩阵相乘的结果&#xff1a; 选择插入-》函数&#xff1a; 选中MMULT&#xff0c;…...

【微软技术栈】C#.NET 中使用依赖注入

本文内容 先决条件创建新的控制台应用程序添加接口添加默认实现添加需要 DI 的服务为 DI 注册服务结束语 本文介绍如何在 .NET 中使用依赖注入 (DI)。 借助 Microsoft 扩展&#xff0c;可通过添加服务并在 IServiceCollection 中配置这些服务来管理 DI。 IHost 接口会公开 IS…...

开启学历新征程,电大搜题助您轻松获取知识

作为一名电大学者&#xff0c;有肩负着传递真实信息、宣传正面价值的使命&#xff0c;而今天我要向您介绍的是一款非常实用的学习工具——电大搜题微信公众号。通过该平台&#xff0c;您可以获得更多关于浙江开放大学和广播电视大学的学习资源&#xff0c;助您在学习和工作上取…...

C++:std::is_convertible

C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

java 实现excel文件转pdf | 无水印 | 无限制

文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...

数据链路层的主要功能是什么

数据链路层&#xff08;OSI模型第2层&#xff09;的核心功能是在相邻网络节点&#xff08;如交换机、主机&#xff09;间提供可靠的数据帧传输服务&#xff0c;主要职责包括&#xff1a; &#x1f511; 核心功能详解&#xff1a; 帧封装与解封装 封装&#xff1a; 将网络层下发…...

CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云

目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...

自然语言处理——循环神经网络

自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元&#xff08;GRU&#xff09;长短期记忆神经网络&#xff08;LSTM&#xff09…...

JS手写代码篇----使用Promise封装AJAX请求

15、使用Promise封装AJAX请求 promise就有reject和resolve了&#xff0c;就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...

【Linux】自动化构建-Make/Makefile

前言 上文我们讲到了Linux中的编译器gcc/g 【Linux】编译器gcc/g及其库的详细介绍-CSDN博客 本来我们将一个对于编译来说很重要的工具&#xff1a;make/makfile 1.背景 在一个工程中源文件不计其数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;mak…...

Qt的学习(一)

1.什么是Qt Qt特指用来进行桌面应用开发&#xff08;电脑上写的程序&#xff09;涉及到的一套技术Qt无法开发网页前端&#xff0c;也不能开发移动应用。 客户端开发的重要任务&#xff1a;编写和用户交互的界面。一般来说和用户交互的界面&#xff0c;有两种典型风格&…...

CTF show 数学不及格

拿到题目先查一下壳&#xff0c;看一下信息 发现是一个ELF文件&#xff0c;64位的 ​ 用IDA Pro 64 打开这个文件 ​ 然后点击F5进行伪代码转换 可以看到有五个if判断&#xff0c;第一个argc ! 5这个判断并没有起太大作用&#xff0c;主要是下面四个if判断 ​ 根据题目…...

李沐--动手学深度学习--GRU

1.GRU从零开始实现 #9.1.2GRU从零开始实现 import torch from torch import nn from d2l import torch as d2l#首先读取 8.5节中使用的时间机器数据集 batch_size,num_steps 32,35 train_iter,vocab d2l.load_data_time_machine(batch_size,num_steps) #初始化模型参数 def …...