JavaDS —— 单链表 与 LinkedList

顺序表和链表区别

ArrayList :
底层使用连续的空间,可以随机访问某下标的元素,时间复杂度为O(1)
但是在插入和删除操作的时候,需要将该位置的后序元素整体往前或者向后移动,时间复杂度为O(N)
增容需要申请新空间,有时候需要拷贝数据释放旧空间,这会有不小的消耗
顺序表的增容一般是2倍增加的,势必会有一定的kong’jian浪费,例如当前容量为100时,需要扩容的话,就是将容量增加到200,如果只是再插入几个数据,就一定会浪费九十几的空间。

既然如此,我们就会思考如何减少空间的浪费,这时候链表就登场了,下面是单链表的示意图:
在这里插入图片描述
链表由两个部分组成,一个是数据域,一个是指针域,数据域是用来存放数据的,指针域是用来存放下一个或者前一个的引用的,这样就把数据给串联起来了,大家也就不难发现,链表的优点就是用多少空间就申请多少空间,做到空间不浪费,并且在下面的内容,你还会感受到链表的插入删除操作效率很高。

链表的分类

链表有8大类,带头和不带头,单向还是双向,循环还是不循环,2^3 = 8种

带头和不带头是指链表有没有一个哨兵节点,就是只是充当头结点的作用,不存放任何有效的数据。
上面的图片就是不带头的,下面的是带头的:
在这里插入图片描述

单向和双向是指:链表的节点是只指向后一个节点的话就是单向的,如果链表的节点即指向前一个结点又指向后一个节点的话就是双向的。
在这里插入图片描述

循环和不循环是指链表是否头尾相连,如果头尾相连就是循环的,否则就是不循环的:
在这里插入图片描述
在这里插入图片描述

实现单链表

下面是自己写的IList接口,会被单链表拓展:

public interface IList {//头插法public void addFirst(int data);//尾插法public void addLast(int data);//任意位置插入,第一个数据节点为0号下标public void addIndex(int index,int data);//查找是否包含关键字key是否在单链表当中public boolean contains(int key);//删除第一次出现关键字为key的节点public void remove(int key);//删除所有值为key的节点public void removeAllKey(int key);//得到单链表的长度public int size();//清空链表public void clear();//打印链表public void display();
}

单链表的节点需要一个数据域和一个指针域,我们先来写一个静态内部类来构造节点类:

    static class ListNode {public int val;public ListNode next;public ListNode(int val) {this.val = val;}}

除此之外,我们还需要一个头指针来指向第一个节点:

    public ListNode head;

打印

循环遍历链表,打印每一个节点的数据,这个方法有利于我们的测试:

    @Overridepublic void display() {ListNode cur = head;while(cur != null) {System.out.print(cur.val + " ");cur = cur.next;}System.out.println();}

头插

在单链表的头部插入一个数据,我们需要将新头节点的next指向原先头部的节点,然后改变head的指向。

    @Overridepublic void addFirst(int data) {ListNode node = new ListNode(data);node.next = head;head = node;}

尾插

循环遍历单链表找到尾节点,然后改变尾节点的指向即可。

这里要注意如果head为空的时候,直接赋值就可以了,不能直接使用null,会报空指针异常,所以在循环前面加多一个判断条件即可。

    @Overridepublic void addLast(int data) {ListNode node = new ListNode(data);if(head == null) {head = node;return;}ListNode cur = head;while(cur.next != null) {cur = cur.next;}cur.next = node;}

求节点总个数

这个很简单,直接循环遍历即可。

    @Overridepublic int size() {ListNode cur = head;int count = 0;while(cur != null) {cur = cur.next;count++;}return count;}

指定位置插入

先判断指定的位置有没有越界,和之前的顺序表是一样的,这里不赘述:

public class IndexException extends RuntimeException{public IndexException(String message) {super(message);}
}
    private void checkIndexInAdd(int index) throws IndexException {if(index < 0 || index > size()) {throw new IndexException("下标范围不合法!");}}

我们要先找到index前一个结点,因为这个插入操作是对三个节点进行操作的,首先先把index的引用放入新结节点的next中,然后再把index前一个结点的next改成新结点的引用,这是一般情况,如果index == 0的话就是头插操作,为什么要做一个判断,因为我们得出的一般规律最后是cur.next = node,这是建立在新结点前面一定有结点的情况下,但是如果是头插的话就不符合了,所以头插需要单独说明。

    @Overridepublic void addIndex(int index, int data) {try{checkIndexInAdd(index);if(index == 0) {addFirst(data);return;}//找到index前一个的节点ListNode cur = head;for (int i = 0; i < index - 1; i++) {cur = cur.next;}ListNode node = new ListNode(data);node.next = cur.next;cur.next = node;} catch (IndexException e) {System.out.println("index 不合法!");e.printStackTrace();}}

对于插入操作,我们要先处理后面的结点,避免后面的结点丢失。

contains

是否包含某个元素,直接遍历循环即可:

    @Overridepublic boolean contains(int key) {ListNode cur = head;while(cur != null) {if(cur.val == key) {return true;}cur = cur.next;}return false;}

删除第一次出现的key

删除某个结点的时候,由于这是单链表,所以我们最好事先拿到删除节点的前一个结点,然后我们要考虑一些特殊的情况,如果这个链表为空就不需要删除,如果要删除的结点就是头结点,那么我们就需要改变头指针的指向,最后就是一般情况下,我们直接修改删除结点的前一个结点的 next 域 就可以了。

    private ListNode findFrontNodeOfKey(int key) {ListNode cur = head;while(cur != null) {if(cur.next.val == key) {return cur;}cur = cur.next;}return null;}@Overridepublic void remove(int key) {//空链表if(head == null) {return;}//头删if(head.val == key) {head = head.next;return;}ListNode prev = findFrontNodeOfKey(key);if(prev == null) {return;//不存在key}ListNode del = prev.next;prev.next = del.next;}

删除所有出现的key

我们使用两个指针,一个从头结点开始,另一个从头结点的下一个结点开始遍历链表,当第二个指针遇到要删除的结点时,配合第一个指针完成此工作,然后prev不变,cur继续移动,如果没有遇到删除的结点,两个指针是一起继续向后运动。

要注意如果链表为空的话就直接return ,避免发生空指针异常

这时候大家一定知道还差一个结点没有判断,就是第一个结点,所以我们最后还有判断一下头结点。

    @Overridepublic void removeAllKey(int key) {if(head == null) {return;}ListNode prev = head;ListNode cur = head.next;while(cur != null) {if(cur.val == key) {prev.next = cur.next;} else {prev = cur;}cur = cur.next;}if(head.val == key) {head = head.next;}}

clear

清空链表,你可以直接把头指针赋值为null,由于链表没有被引用,会被JVM自动回收,

    @Overridepublic void clear() {ListNode cur = head;while(cur != null) {ListNode tmp = cur.next;cur.next = null;cur = tmp;}head = null;}

模拟实现LinkedList

LinkedList 是不带头,双向的,循环的链表

构建节点

双向的意味着有两个节点,一个指向前一个结点,一个指向后一个结点,还有一个头指针指向头节点,一个尾指针指向尾节点。

    static class ListNode {public int val;public ListNode prev;public ListNode next;public ListNode(int val) {this.val = val;}}public ListNode head;public ListNode last;

打印

    public void display() {ListNode cur = head;while(cur != null) {System.out.print(cur.val + " ");cur = cur.next;}System.out.println();}

头插

要注意如果头指针为null,意味着链表为空,尾指针自然也是null,链表为空的话,插入新数据要改变头尾指针的指向。
正常情况下是链表至少有一个结点,改变原先头节点的prev指向,新结点的next也要改变。

    public void addFirst(int data) {ListNode node = new ListNode(data);if(head == null) {head = last = node;return;}head.prev = node;node.next = head;head = node;}

尾插

注意如果尾指针为null时,说明链表为空。和上面的头插一样,要单独讨论说明。

    public void addLast(int data) {ListNode node = new ListNode(data);if(last == null) {head = last = node;}last.next = node;node.prev = last;last = node;}

求结点个数

    public int size() {ListNode cur = head;int count = 0;while(cur != null) {count++;cur = cur.next;}return count;}

指定位置插入

先判断index是否合法,不合法还是和之前一样抛异常。

public class IndexOutOfBoundException extends RuntimeException {public IndexOutOfBoundException() {super();}public IndexOutOfBoundException(String message) {super(message);}
}
    private void checkIndexInAdd(int index) throws IndexOutOfBoundException{if(index < 0 || index > size()) {throw new IndexOutOfBoundException("下标越界!!!");}}

我们先讨论一般情况,如果待插入的结点正好前后都是由结点的,那么我们需要修改三个结点的指针:
cur.prev.next = node;
node.prev = cur.prev;
node.next = cur;
cur.prev = node;
在这里插入图片描述
现在来注意特殊情况,如果index == 0时,就是头插,不管怎么样,头插就一定要改变头指针,所以要单独讨论。换一种思路,如果是头插的话,cur.prev = null ,所以 cur.prev.next 一定会报空指针异常。所以头插还是要单独讨论。
那如果是尾插呢?尾插意味着 cur == null ,还是和头插思考方式一样,尾节点一定要改变所以要单独讨论,还有cur.prev 一定会报空指针异常。

    public void addIndex(int index,int data) {try {checkIndexInAdd(index);if(index == 0) {addFirst(data);return;}ListNode node = new ListNode(data);ListNode cur = head;for (int i = 0; i < index; i++) {cur = cur.next;}if(cur == null) {addLast(data);return;}cur.prev.next = node;node.prev = cur.prev;node.next = cur;cur.prev = node;} catch (IndexOutOfBoundException e) {e.printStackTrace();}}

remove

删除第一次出现关键字为key的节点

如果链表为空不能继续删除操作
如果删除头节点,就必须改变头指针,所以要单独说明
一般情况下,需要变动cur前后结点,自然会想到:cur.prev.next = cur.next; cur.next.prev = cur.prev;
那如果是尾删呢?上面两行代码只有前面一行还能继续用,由于是尾删,尾节点就要发生改变,所以last = cur.prev;

public void remove(int key) {if(head == null) {return;}if(head.val == key) {head = head.next;head.prev = null;return;}ListNode cur = head.next;while(cur != null) {if(cur.val == key) {cur.prev.next = cur.next;if(cur.next == null) {last = cur.prev;} else {cur.next.prev = cur.prev;}return;}cur = cur.next;}}

removeAllKey

删除所有值为key的节点

删除所有的key,上面我们写了删除第一次出现key的结点,这里把代码直接帮过来,删掉return就可以继续用,但是一定是对的吗?
前面的链表判空直接返回没有问题,但是头删的话就有问题了,假设头节点是你要删除的结点就意味着头指针要发生改变,那如果新的头节点又要发生改变呢?这里我们选择尽量不改变我们的祖传代码,把头删放在最后面去做即可。

    public void removeAllKey(int key) {if(head == null) {return;}ListNode cur = head.next;while(cur != null) {if(cur.val == key) {cur.prev.next = cur.next;if(cur.next == null) {last = cur.prev;} else {cur.next.prev = cur.prev;}}cur = cur.next;}if(head.val == key) {head = head.next;if(head == null) {return;}head.prev = null;}}

contains

是否包含key这个元素

    public boolean contains(int key) {ListNode cur = head;while(cur != null) {if(cur.val == key) {return true;}cur = cur.next;}return false;}

clear

你可以直接将head 和last都置为null,这样链表就会被JVM自动回收。
这里模仿源码的写法,源码是一个一个结点都置为null,最后头尾指针再置为null

    public void clear() {ListNode cur = head;while(cur != null) {ListNode tmp = cur.next;cur.prev = null;cur.next = null;cur = tmp;}head = last = null;}

LinkedList 使用

Java集合类中给我们提供了LinkedList,这是一个无头双向循环链表,我们来看一下它里面的方法,方法名字和上面我们模拟实现的差不多。
在这里插入图片描述

LinkedList 的构造方法

在这里插入图片描述
第二个构造方法是可以传入一个对象,和之前ArrayList表示二维数组是一个意思。

LinkedList 的方法

在这里插入图片描述

要注意LinkedList和ArrayList 的subList是一样的原理,截取的list还是原来的对象list,只是范围不同,并没有创建新的对象。

add(默认尾插)

注意LinkedList的add方默认是尾插

    public static void main(String[] args) {LinkedList<Integer> list = new LinkedList<>();list.add(1);list.add(2);list.add(3);System.out.println(list);}

在这里插入图片描述

addAll

尾插一个对象

    public static void main(String[] args) {LinkedList<Integer> list = new LinkedList<>();list.add(1);list.add(2);list.add(3);System.out.println(list);ArrayList<Integer> list1 = new ArrayList<>();list1.add(10);list1.add(20);list.addAll(list1);System.out.println(list);}

在这里插入图片描述

遍历链表

直接打印

LinkedList也是重写了toString 方法

    public static void main(String[] args) {LinkedList<Integer> list = new LinkedList<>();list.add(1);list.add(2);list.add(3);System.out.println(list);}

在这里插入图片描述

for 循环

    public static void main(String[] args) {LinkedList<Integer> list = new LinkedList<>();list.add(1);list.add(2);list.add(3);int size = list.size();for (int i = 0; i < size; i++) {System.out.print(list.get(i) + " ");}System.out.println();}

在这里插入图片描述

for each

    public static void main(String[] args) {LinkedList<Integer> list = new LinkedList<>();list.add(1);list.add(2);list.add(3);for(int x : list) {System.out.print(x + " ");}System.out.println();}

在这里插入图片描述

迭代器

在这里插入图片描述
ListIterator 是继承 Iterator 的,这两个都可以来遍历链表打印数据。

迭代器的使用可以类似下面的图:
在这里插入图片描述

while(it.hasNext())hasNext表示是否由下一个数据,通过next()方法打印下一个数据,之后 it 一直向后移动。

Iterator
    public static void main(String[] args) {LinkedList<Integer> list = new LinkedList<>();list.add(1);list.add(2);list.add(3);System.out.println("===== Iterator ====");Iterator<Integer> it = list.iterator();while (it.hasNext()) {System.out.print(it.next()+" ");}System.out.println();}

在这里插入图片描述

ListIterator
    public static void main(String[] args) {LinkedList<Integer> list = new LinkedList<>();list.add(1);list.add(2);list.add(3);ListIterator<Integer> lit =  list.listIterator();while (lit.hasNext()) {System.out.print(lit.next()+" ");}System.out.println();}

在这里插入图片描述

ListIterator(逆向遍历)
    public static void main(String[] args) {LinkedList<Integer> list = new LinkedList<>();list.add(1);list.add(2);list.add(3);System.out.println("===== ListIterator ====");ListIterator<Integer> lit2 =  list.listIterator(list.size());while (lit2.hasPrevious()) {System.out.print(lit2.previous()+" ");}System.out.println();}

在这里插入图片描述

listIterator(int n) ,可以指定从哪个下标开始遍历链表

ArrrayList 和 LinkedLisrt 的总结

在这里插入图片描述

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

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

相关文章

什么是智能制造?

科技的每一次飞跃都深刻改变着我们的生产生活方式。其中&#xff0c;智能制造作为工业4.0的核心概念&#xff0c;正引领着全球制造业向更加高效、灵活、智能的方向迈进。那么&#xff0c;究竟什么是智能制造&#xff1f;它如何重塑我们的工业版图&#xff0c;又将对未来社会产生…

TTT架构超越Transformer,ML模型替代RNN隐藏状态!

目录 01 算法原理 02 骨干架构 03 实验结果 一种崭新的大语言模型&#xff08;LLM&#xff09;架构有望取代当前主导 AI 领域的 Transformer&#xff0c;并在性能上超越 Mamba。 论文地址&#xff1a;https://arxiv.org/abs/2407.04620 本周一&#xff0c;关于 Test-Time Tr…

修复 Ubuntu 24.04 Dock 丢失应用程序图标

找出应用程序窗口的类名 首先&#xff0c;您需要启动应用程序窗口。然后&#xff0c;按 Alt F2 启动“运行 Command”对话框。当对话框打开时&#xff0c;输入 lg 并按 Enter 键。 在该窗口中&#xff0c;单击Windows按钮&#xff0c;然后找出目标应用程序窗口的类名称。 在/…

Flutter——最详细(Table)网格、表格组件使用教程

背景 用于展示表格组件&#xff0c;可指定线宽、列宽、文字方向等属性 属性作用columnWidths列的宽度defaultVerticalAlignment网格内部组件摆放方向border网格样式修改children表格里面的组件textDirection文本排序方向 import package:flutter/material.dart;class CustomTa…

公众号运营秘籍:8 大策略让你的粉丝翻倍!

在当今信息爆炸的时代&#xff0c;微信公众号的运营者们面临着前所未有的挑战&#xff1a;如何在这个充满竞争的红海中脱颖而出&#xff0c;吸引并留住粉丝&#xff1f;事实上&#xff0c;微信公众号的红利期并未完全过去&#xff0c;关键在于我们如何策略性地运营&#xff0c;…

使用PEFT库进行ChatGLM3-6B模型的QLORA高效微调

PEFT库进行ChatGLM3-6B模型QLORA高效微调 QLORA微调ChatGLM3-6B模型安装相关库使用ChatGLM3-6B模型GPU显存占用准备数据集加载数据集数据处理数据集处理加载量化模型-4bit预处理量化模型配置LoRA适配器训练超参数配置开始训练保存LoRA模型模型推理合并模型使用微调后的模型 QLO…

【Pytorch实用教程】transformer中创建嵌入层的模块nn.Embedding的用法

文章目录 1. nn.Embedding的简单介绍1.1 基本用法1.2 示例代码1.3 注意事项2. 通俗的理解num_embeddings和embedding_dim2.1 num_embeddings2.2 embedding_dim2.3 使用场景举例结合示例1. nn.Embedding的简单介绍 nn.Embedding 是 PyTorch 中的一个模块,用于创建一个嵌入层。…

准大一新生开学千万要带证件照用途大揭秘

1、提前关注好都有哪些考场&#xff0c;以及这些考场大致在网页的哪个位置。比如我选对外经贸大学&#xff0c;我就直接找到第二个点进去。 2、电脑上同时开了谷歌浏览器和IE浏览器&#xff0c;以及手机也登陆了。亲测下来&#xff0c;同一时间刷新&#xff0c;谷歌浏览器能显示…

​cesium、three.js,三维GIS为啥那么热?到底怎么学呢?

​cesium、three.js&#xff0c;三维GIS为啥那么热&#xff1f;他们的应用场景都是什么呢&#xff1f;接下来我们可以一起来看看~ 三维GIS的应用 GIS和3D的应用是趋势&#xff0c;目前已经有很多应用案例&#xff0c;例如BIM&#xff0c;智慧城市&#xff0c;数字孪生等。如下…

汇聚荣拼多多电商实力强吗?

汇聚荣拼多多电商实力强吗?汇聚荣拼多多&#xff0c;作为中国电商领域的后起之秀&#xff0c;已经在市场上占据了一席之地。那么&#xff0c;它的实力究竟如何呢?在回答这个问题之前&#xff0c;我们需要先了解一下拼多多的基本情况。拼多多是一家以社交电商为主要模式的购物…

3个方法教你如果快速绕过Excel工作表保护密码

在日常生活中&#xff0c;我们可能会遇到一些特殊情况&#xff0c;比如不小心忘记了Excel文件中设置的打开密码。别担心&#xff01;这里为您带来一份详细的Excel文件密码移除教程&#xff0c;助您轻松绕过Excel工作表保护。 方法一&#xff1a;使用备份文件 如果您有文件的备…

深入理解 go map

什么是 map 维基百科里这样定义 map: In computer science, an associative array, map, symbol table, or dictionary is an abstract data type composed of a collection of (key, value) pairs, such that each possible key appears at most once in the collection. 简单…

[nicetomeetyou@onionmail.org].Faust勒索病毒科普知识全解析

引言 随着网络技术的飞速发展&#xff0c;勒索病毒已成为威胁全球网络安全的一大隐患。[nicetomeetyouonionmail.org].Faust作为一种新型勒索病毒&#xff0c;通过其复杂的加密技术和隐蔽的传播手段&#xff0c;给个人、企业及政府机构带来了巨大的损失。本文将从多个维度全面解…

C++的入门——(命名空间, 输入输出 ,缺省参数 ,函数重载 ,引用, 内联函数 ,nullptr)

文章目录 引言c兼容C语⾔c的重要性C在⼯作领域中的应⽤ 一、命名空间namespace的价值namespace的定义命名空间使⽤ 二、 C输⼊&输出三、缺省参数四、函数重载1、参数类型不同2、参数个数不同3、参数类型顺序不同 五、引⽤1、引⽤的概念和定义2、引⽤的特性3、引⽤的使⽤4、…

SCSA第四天

ASPF FTP --- 文件传输协议 Tftp --- 简单文件传输协议 FTP协议相较于Tftp协议 ---- 1&#xff0c;需要进行认证 2&#xff0c;拥有一套完整的命令集 用户认证 防火墙管理员认证 ---- 校验登录者身份合法性 用户认证 --- 上网行为管理中的一环 上网用户认证 --- 三层认证…

Java SQL 连接(初级)

实训Day3 记实 实训第三天&#xff0c;今天是头脑风暴的第二天&#xff0c;课程将SQL与Java&#xff08;idea&#xff09;代码结合&#xff0c;这是一项具有挑战性的代码课程。课程将两个应用结合起来&#xff0c;展现了Java代码的跨平台性&#xff0c;展现了Java语言的封装性…

Cesium中实现全球体积云效果的一种方案

原生 Cesium 提供了一种积云的效果&#xff0c;云的物理特征和渲染性能都还不错&#xff0c;这种方案适合表达小范围相对离散的云朵&#xff0c;但是用来实现全球范围下相对连续、柔和渐变的云层比较困难。本文在体渲染的基础上&#xff0c;参考了开源社区中 shadertoy 和 thre…

c#调用c++ dll库报错System.BadImageFormatException

System.BadImageFormatException:“试图加载格式不正确的程序。 (异常来自 HRESULT:0x8007000B)” 1. dll需要选择release模式进行编译 2.选择相同位数&#xff0c;比如x64平台&#xff0c;c#也需要x64 3.不要设置c#不支持的函数供调用 比如&#xff1a; c可以输出到控制台…

Meta发布Llama 2驱动的AI代码生成器:Code Llama,开源来袭!

Meta 刚刚了号称是编程领域 “最先进的大语言模型”—— Code Llama &#xff0c;可根据 代码和自然语言提示 生成代码和有关代码的自然语言&#xff0c;支持多种主流编程语言&#xff0c; 包括 Python、C、Java、PHP、Typescript (Javascript)、C# 和 Bash 。 Code Llama 完全…

拆分盘究竟是什么?一篇文章带你了解!

拆分盘是一种特殊的理财产品或投资模式&#xff0c;它通常被描述为“只涨不跌”的投资方式&#xff0c;多指股票&#xff0c;但实质上与传统股市中的股票有本质区别。以下是对拆分盘的详细解析&#xff1a; 一、拆分盘的定义 拆分盘可以理解为一种只涨不跌的理财股票。其特点在…