手撕Java集合——链表

链表

  • 一、链表概念特性
  • 二、不带头单向非循环链表实现
    • 🍑1、定义结点
    • 🍑2、打印链表
    • 🍑3、使用递归逆序打印链表
    • 🍑4、头插
    • 🍑5、尾插
    • 🍑6、指定位置插入
    • 🍑7、查找是否包含关键字key是否在单链表当中
    • 🍑8、删除第一次出现关键字为key的节点
    • 🍑9、删除所有值为key的节点
    • 🍑10、得到单链表的长度
    • 🍑11、清空链表
  • 三、无头双向非循环链表
  • 四、Java集合中的 LInkedList
    • 🍑1、LinkedList构造方法
    • 🍑2、LinkedList常用方法
  • 五、线性表总结:LinkedList和ArrayList区别

一、链表概念特性

链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。

特点:

  1. 上图每个数据块是一个结点,这些结点一般是从堆上申请来的。
  2. 链式结构在逻辑上是连续的,但物理上不一定连续。

链表结构由【单向/双向;循环/非循环;带头/不带头】几种形式进行组合,共有2×2×2=8种不同结构,不过我们常用的主要是 单向不带头非循环链表双向不带头非循环链表 。对于前者来说,结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。对于后者,在Java的集合框架库中LinkedList底层实现就是无头双向非循环链表。

接下来我们就以不带头单向非循环链表和不带头双向循环链表进行展开,分别模拟实现它们的主要功能。

二、不带头单向非循环链表实现

首先给出功能接口(实现时元素类型为int):

//1.打印链表
public void display() {}//2.递归逆序打印链表
public void displayReverse(SingleLinkedList.ListNode node) {}//3.头插法
public void addFirst(int data) {}//4.尾插法
public void addLast(int data) {}//5.任意位置插入,第一个数据节点为0号下标
public boolean addIndex(int index, int data) {}//6.查找是否包含关键字key是否在单链表当中
public boolean contains(int key) {}//7.删除第一次出现关键字为key的节点
public void remove(int key) {}//8.删除所有值为key的节点
public void removeAllKey(int key) {}//9.得到单链表的长度
public int size() {}//10.清空链表
public void clear() {}

🍑1、定义结点

这里是将结点看做单链表SingleLinkedList的一部分,所以将结点定义为链表类-SingleLinkedList的内部类。
注意: 这里的head结点并不表示链表带有单独头结点(不管链表是否为空,均含有一个头结点),而是用来表示该链表的第一个结点。

public class SingleLinkedList {static class ListNode {public int val;//存储的数据public ListNode next;//存储下一个节点的地址//构造方法public ListNode(int val) {this.val = val;}}public ListNode head;//表示当前链表的头结点//一些功能接口……
}

🍑2、打印链表

思路:从头结点开始向后变历链表,直到结点为空,打印完毕。

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

🍑3、使用递归逆序打印链表

思路:利用递归思想,先递后归,实现倒序打印链表。

    public void displayRecu(ListNode node) {if (node == null) {return;}if (node.next == null) {System.out.print(node.val+" ");return;}displayRecu(node.next);System.out.print(node.val+" ");}

🍑4、头插

    //头插法【比较简单,整体也没有特殊情况,head=null也没问题!】public void addFirst(int data) {ListNode newNode = new ListNode(data);newNode.next = head;head = newNode;}

🍑5、尾插

    //尾插法【需要考虑头结点为null的情况】public void addLast(int data) {ListNode newNode = new ListNode(data);ListNode cur = head;//如果头结点为空,直接赋值if (head == null) {head = newNode;return;}//找到尾结点while (cur.next != null) {cur = cur.next;}cur.next = newNode;}

🍑6、指定位置插入

	//下标合法性检测private void checkIndex(int index) {//这里的size()是自定义函数(后面讲解)if (index < 0 || index > size()) {throw new IndexOutOfException("下标越界!");}}//找到 index-1位置的节点的地址private ListNode findIndexSubOne(int index) {ListNode cur = head;int count = 0;while (count != index - 1) {cur = cur.next;count++;}return cur;}//指定位置插入public boolean addIndex(int index, int data) throws IndexOutOfException {//判断坐标合法性checkIndex(index);//index=0这种情况需要特殊处理一下【1.head=null,index=0;2.head!=null,index=0】if (index == 0) {addFirst(data);return true;}//尾插不用特殊考虑,以下包含了尾插//中间插入ListNode newNode = new ListNode(data);ListNode cur = findIndexSubOne(index);newNode.next = cur.next;cur.next = newNode;return true;}

🍑7、查找是否包含关键字key是否在单链表当中

思路: 遍历链表,比对value值即可

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

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


    //删除第一次出现关键字为key的节点public void remove(int key) {//链表一个结点都没有if (head == null) {System.out.println("链表为空!");return;}//判断头结点的valif (head.val == key) {head = head.next;return;}//找到key的前驱结点ListNode cur = head;while (cur.next != null) {if (cur.next.val == key) {cur.next = cur.next.next;return;}cur = cur.next;}System.out.println("未找到关键字" + key);}

🍑9、删除所有值为key的节点

思路: 这个算法相比于“删除第一次出现关键字为key的节点”只需略微调整(划去return;单独判断头结点)
注意:这里头结点的判断放在了最后,因为这里是删除所有值为key的节点,由于head结点的特殊性(head结点的value=key),且head后对key值的删除处理具有同一性,所以这里就先一并处理head后结点,最后单独判断head结点。对于“删除第一次出现关键字为key的节点”,由于此时的场景是处理第一次出现的key值,所以就必须先处理head结点。

    //删除所有值为key的节点public void removeAllKey(int key) {//为空情况if (head == null) {return;}//头结点后存在key值的情况ListNode cur = head;while (cur.next != null) {if (cur.next.val == key) {cur.next = cur.next.next;} else {cur = cur.next;}}//头结点单独判断if (head.val == key) {head = head.next;}}

🍑10、得到单链表的长度

思路: 遍历链表,返回count计数值即可。

    //得到单链表的长度public int size() {int count = 0;ListNode cur = head;while (cur != null) {count++;cur = cur.next;}return count;}

🍑11、清空链表

思路: 在Java中,当一个对象没有任何引用指向时,Java自带的垃圾回收机制会自动删除该对象。因此, 只要将链表的头节点赋值为空,链表的所有节点就会被视为不再被引用,可以通过垃圾回收机制来删除它们。

具体来说,当链表的头节点变成了null,链表的所有节点就不再有任何引用指向它们,这意味着这些节点成为Java内存中的"孤儿"节点,也就是不再被外部引用的节点。这时,Java垃圾回收机制会自动触发,将不再被引用的节点删除。

需要注意的是:如果链表中的节点存在其他引用指向它们,那么这些节点就不会被视为垃圾,也不会被自动删除。因此,在清空链表前需要确保没有其它引用指向链表中的任何一个节点,否则这些节点会一直被占用,浪费内存空间。

    //清空链表public void clear() {head = null;}

三、无头双向非循环链表

双向不带头非循环链表,与上述单向不带头非循环链表的主要差别体现在功能接口的实现上,特别是删除和插入操作时,不需要在保存前驱结点,可根据当前结点进行操作。

下面是无头双向非循环链表的代码实现,很多功能接口和上述单链表的实现多有雷同,并且下面代码中给出了详细的注释,大家可以参考理解:

// 2、无头双向链表实现
public class MyLinkedList {static class Node {// 数据public int val;// 前驱结点public Node prev;// 后继结点public Node next;// 结点构造public Node(int val) {this.val = val;}}// 当前头结点public Node head;// 当前尾结点public Node last;//打印双向链表(同单向链表)public void display() {Node cur = head;while (cur != null) {System.out.print(cur.val + " ");cur = cur.next;}System.out.println();}//头插法(思路同单向链表)public void addFirst(int data) {Node newnode = new Node(data);if (head == null) {head = newnode;last = newnode;} else {newnode.next = head;head.prev = newnode;head = newnode;}}//尾插法(思路同单向链表)public void addLast(int data) {Node newnode = new Node(data);if (head == null) {head = newnode;last = newnode;} else {last.next = newnode;newnode.prev = last;last = newnode;}}//得到双向链表的长度(同单链表)public int size() {int count = 0;Node cur = head;while (cur != null) {count++;cur = cur.next;}return count;}//任意位置插入(注:第一个数据节点为0号下标)public void addIndex(int index, int data) {// 判断坐标合法性if (index<0 || index>size()) {throw new IndexOutOfException("坐标非法!");}// 构建新节点Node newnode = new Node(data);// 处理头插尾插特殊情况if (index==0) {addFirst(data);return;}if (index==size()) {addLast(data);return;}// 处理在中间插入情况(这里体现了双向链表的优势)// 将cur结点指向当前坐标Node cur = head;while (index!=0) {cur = cur.next;index--;}// 修改新节点的 next 域newnode.next = cur;// 修改 cur 结点的前驱结点的 next 域cur.prev.next = newnode;// 修改新节点的 prev 域newnode.prev = cur.prev;// 修改 cur 结点的 prev 域cur.prev = newnode;}//查找是否包含关键字key是否在单链表当中(同单链表)public boolean contains(int key) {Node cur = head;while (cur!=null) {if (cur.val==key) {return true;}cur = cur.next;}return false;}//删除第一次出现关键字为key的节点(体现双向链表优势)public void remove(int key) {Node cur = head;while (cur!=null) {if (cur.val==key) {//如果只有一个结点(单链表不存在last所以不存在这一步)if (head == last) {head = null;last = null;return;}//如果cur是头结点if (cur == head) {head = head.next;head.prev = null;return;}//如果cur是尾结点if (cur == last) {last = last.prev;last.next = null;return;}// 如果cur是中间结点cur.prev.next = cur.next;cur.next.prev = cur.prev;return;}cur = cur.next;}}//删除所有值为key的节点[对上面代码进行改进]public void removeAllKey(int key) {Node cur = head;while (cur!=null) {//满足条件开始删除if (cur.val==key) {if (head == last) {// 如果只有1个结点head = null;last = null;return;} else if (cur == head) {// 如果cur是头结点,并且节点数必定大于等于2head = head.next;if (head!=null) {head.prev = null;}} else if (cur == last){// 如果是尾巴结点last = last.prev;last.next = null;} else {// 如果是中间结点cur.prev.next = cur.next;cur.next.prev = cur.prev;}}cur = cur.next;}}//双向链表的清空操作public void clear() {Node cur = head;while (cur!=null) {Node curNext = cur.next;cur.prev=null;cur.next=null;cur = curNext;}head=null;last=null;}
}

四、Java集合中的 LInkedList

java.util包下为我们提供了LinkedList集合类,它的底层就是一个无头双向非循环链表结构。并且这里的 LinkedList是以泛型方式实现的,使用时必须要先实例化,同时,它也实现了 List 接口,可以通过 List 接口接收LinkedList对象,并使用List方法操作LinkedList对象:

🍑1、LinkedList构造方法

方法解释
LinkedList()无参构造
LinkedList(Collection<? extends E> c)使用其他集合容器中元素构造List

🍑2、LinkedList常用方法

方法描述
boolean add(E e)在链表尾部插入元素 e
void add(int index, E element)将元素 element 插入到指定的 index 位置。
void addFirst(E e)在该列表开头插入指定的元素。
boolean addAll(Collection<? extends E> c)将集合 c 中的所有元素尾部插入链表。
E remove(int index)删除指定 index 位置的元素,并返回被删除的元素。
boolean remove(Object o)删除遇到的第一个等于 o 的元素,若删除成功则返回 true
E get(int index)获取指定 index 位置的元素。
E set(int index, E element)将指定 index 位置的元素替换为新的元素 element,并返回原元素。
void clear()清空链表中的所有元素。
boolean contains(Object o)判断链表中是否包含元素 o,如果存在则返回 true
int indexOf(Object o)返回第一个出现的元素 o 的索引,如果不存在则返回 -1
int lastIndexOf(Object o)返回最后一个出现的元素 o 的索引,如果不存在则返回 -1
List subList(int fromIndex, int toIndex)截取链表部分元素,并返回一个新的 List 对象。

五、线性表总结:LinkedList和ArrayList区别

不同点ArrayListLinkedList
存储空间上物理上一定连续逻辑上连续,但物理上不一定连续
随机访问支持 O(1)不支持 O(N)
头插或中间位置插入需要搬移元素,效率低 O(N)只需修改引用的指向,时间复杂度为 O(1)
空间空间不够时需要扩容没有容量的概念
应用场景元素高效存储+频繁访问任意位置插入和删除频繁

说明: ArrayList支持随机访问,LinkedList不支持随机访问"的意思是指在数据结构中,ArrayList可以通过索引直接访问和获取元素,而LinkedList不能通过索引进行直接访问和获取。

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

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

相关文章

C++_模板初阶

在面向对象中&#xff0c;我们可以使用重载来实现多态。 但是问题在于&#xff0c;重载的函数仅仅是类型不同&#xff0c;代码复用率比较低&#xff0c;只要有新的类型出现时&#xff0c;就要增加对应的函数&#xff1b;另一方面它的代码可维护性比较低&#xff0c;一个出错可…

Windows - UWP - 网络不好的情况下安装(微软商店)MicrosoftStore的应用

Windows - UWP - 网络不好的情况下安装&#xff08;微软商店&#xff09;MicrosoftStore的应用 前言 UWP虽然几乎被微软抛弃了&#xff0c;但不得不否认UWP应用给用户带来的体验。沙箱的运行方式加上微软的审核&#xff0c;用户使用起来非常放心&#xff0c;并且完美契合Wind…

最具性价比视频解析卡:Atlas300V,性能和能效业界两倍

华为推出“昇腾伙伴网络”&#xff08;简称APN&#xff09;合作伙伴计划&#xff0c;思腾合力凭借多年在AI行业的积累和自身的实力成为首家签署APN合作协议的公司&#xff0c;共同打造基于昇腾的软硬件AI基础架构解决方案&#xff0c;推进人工智能国产算力的进步和发展。

分支语句与循环语句(2)

3.3 do...while()循环 3.3.1 do语句的语法&#xff1a; do 循环语句; while(表达式); 3.3.2执行流程图&#xff1a; 3.3.3 do语句的特点 循环至少执行一次&#xff0c;使用的场景有限&#xff0c;所以不是经常使用。 打印1-10的整数&#xff1a; #define _CRT_SECURE_NO_WA…

c++ qt(第一部分)

c qt&#xff08;第一部分&#xff09; 一.认识QT 1.什么是QT Qt&#xff08;官方发音 [kju:t]&#xff0c;音同 cute&#xff09;是一个跨平台的 C 开发库&#xff0c;主要用来开发图形用户界面&#xff08;Graphical User Interface&#xff0c;GUI&#xff09;程序&#…

Multi-object navigation in real environments using hybrid policies 论文阅读

论文信息 题目&#xff1a;Multi-object navigation in real environments using hybrid policies 作者&#xff1a;Assem Sadek, Guillaume Bono 来源&#xff1a;CVPR 时间&#xff1a;2023 Abstract 机器人技术中的导航问题通常是通过 SLAM 和规划的结合来解决的。 最近…

[FPGA IP系列] 2分钟了解FPGA中的BRAM

FPGA设计中&#xff0c;BRAM是一项非常关键的内置存储资源&#xff0c;FPGA开发需要熟练使用BRAM&#xff0c;今天再复习一下BRAM的知识&#xff0c;包括BRAM的定义、组成、应用等等。 一、BRAM介绍 1、BRAM的定义 RAM是Random Access Memory&#xff0c;也就是随机访问数据…

natapp内网穿透

下载 参考 https://natapp.cn/article/natapp_newbie 注册免费通道 启动 ./natapp -authtoken9ab6b9040a624f40

带扩散器的超快速控制网

一、说明 自从稳定扩散风靡全球以来&#xff0c;人们一直在寻找更好地控制生成过程结果的方法。ControlNet提供了一个最小的界面&#xff0c;允许用户在很大程度上自定义生成过程。使用 ControlNet&#xff0c;用户可以轻松地使用不同的空间上下文&#xff08;如深度图、分割图…

SD卡相关资料

STM32429I-EVAL1 正点原子 ALIENTEK探索者STM32F4、ALIENTEK战舰STM32F1 V3 野火 野火STM32F103-V2霸道 中科蓝汛 AB5322B QFN32内部软开关蓝牙音箱

OpenCV基本操作——图像的基础操作

目录 图像的IO操作读取图像显示图像保存图像 绘制几何图形绘制直线绘制圆形绘制矩形向图像中添加文字效果展示 获取并修改图像中的像素点获取图像的属性图像通道的拆分与合并色彩空间的改变 图像的IO操作 读取图像 cv2.imread()import numpy as np import cv2 imgcv2.imread(…

四、web应用程序技术——HTTP

文章目录 1 HTTP请求2 HTTP响应3 HTTP方法4 URL5 HTTP消息头5.1 常用消息头5.2 请求消息头5.3 响应消息头 6 cookie7 状态码8 HTTP代理9 HTTP身份验证 HTTP&#xff08;HyperText Transfer Protocol&#xff0c;超文本传输协议&#xff09;是访问万维网使用的核心通信协议&…