算法通关村第一关—青铜挑战—用Java基本实现各种链表操作

2023-11-30 12:44

本文主要是介绍算法通关村第一关—青铜挑战—用Java基本实现各种链表操作,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 第一关—链表【青铜挑战】
    • 1.1 单链表的概念
    • 1.2 链表的相关概念
    • 1.3 创建链表 - Java实现
    • 1.4 链表的增删改查
      • 1.4.1 遍历单链表 - 求单链表长度
      • 1.4.2 链表插入 - 三种位置插入
        • (1)在链表的表头插入
        • (2)在链表的中间插入
        • (3)在链表的结尾插入
        • (4)在链表的所有位置插入[总结]⭐
      • 1.4.3 链表删除 - 三种位置删除
        • (1)删除链表的表头结点
        • (2)删除链表的最后一个结点
        • (3)删除链表的中间结点
        • (4)删除链表的任一位置[总结]⭐

第一关—链表【青铜挑战】


1.1 单链表的概念

  • 单向链表就像一个铁链一样,元素之间互相连接,包含多个结点,每个结点**只有一个**指向后继元素的next指针,并且最后一个元素的next指向null

在这里插入图片描述

小练:以下两张图,是否满足单链表的要求

在这里插入图片描述
在这里插入图片描述
解析:

第一张图满足单链表的要求,第二张图不满足要求,因为c1它有两个后继结点a5和b4,单链表的核心是一个结点只能有一个后继,但是不代表一个结点只能有一个被指向(如c1可以被a2和b3指向)

注意:做题的时候注意比较的是值还是结点,有时可能两个结点的值是相等的,但并不是同一个结点,例如下图,有两个结点的值都是1,但并不是同一个结点

在这里插入图片描述


1.2 链表的相关概念

节点和头节点

在链表中,每个点都由指向下一个节点的地址组成独立的单元,成为一个结点,有时也称为节点,含义都是一样的

对于单链表而言,如果知道了第一个元素,就可以遍历访问整个链表,因此第一个节点最重要,一般称为头节点

虚拟结点

虚拟结点就是一个dummyNode,其next指针指向head头部,也就是dummyNode.next = head

因此,如果我们在算法里使用了虚拟结点,则要注意,如果要获得head结点,或者从方法里返回的时候,则应使用dummyNode.next

另外,dummyNode的val不会被使用,初始化为0或者-1等都是可以的,既然值不会被使用,那么我们就会有疑问?虚拟结点有啥用呢?简单来说,就是为了方便我们处理头结点,否则我们需要在代码里单独处理头结点【首部结点】的问题
在这里插入图片描述


1.3 创建链表 - Java实现

我们首先要理解JVM是怎么构建出链表,JVM里面有栈区和堆区,堆区主要存引用,也就是一个指向实际对象的地址,而堆区存的才是创建的对象

在这里插入图片描述

/*** @Author Zan* @Date 2023/11/29 14:46* @Description : 传入一个数组,将其转换成单链表*/
public class BasicLink {public static void main(String[] args) {int[] a = {1, 2, 3, 4, 5, 6};Node head = initLinkedList(a);System.out.println(head);}private static Node initLinkedList(int[] array) {Node head = null, current = null;for (int i = 0; i < array.length; i++) {Node newNode = new Node(array[i]);if (i == 0) { // 头节点// 由于head = current,因此当current在变化的时候,head也在变化head = newNode;
//                newNode = new Node(array[i]); // 如果在此将newNode重新定义,指向的是不同的堆数据,因此head就只是一个Node普通对象,单节点的链表current = newNode;} else { // 后面的节点current.next = newNode;current = newNode;}}return head;}static class Node {public int x;public Node next;public Node(int x) {this.x = x;next = null;}}
}

在这里插入图片描述

我们可以看到初始化链表的时候,head和current指向的是同一个对象,也就是指向堆中的同一个数据,因此当控制current.next = newNode 的时候,其实就是控制堆中的数据指向谁,next指向下一条数据,而head跟current一样指向的是同一个对象,因此就可以跟随其变化

在这里插入图片描述
最后得到head如下图所示 - 单链表的形式

在这里插入图片描述

1.4 链表的增删改查

  • 对于单链表而言,不管进行什么操作,一定都是从头开始逐个向后开始访问,所以操作之后是否还能够找到表头非常重要

1.4.1 遍历单链表 - 求单链表长度

/*** 遍历链表,获取链表的长度* @param head 头节点* @return*/
public static int getListLength(Node head) { // 传入头节点int length = 0;Node node = head;while (node != null) { // 一个一个节点遍历length++;node = node.next;}return length;
}

1.4.2 链表插入 - 三种位置插入

  • 单链表的插入,和数组的插入一样,过程不复杂。但是单链表的插入操作需要考虑三种情况:首部、中部和尾部

(1)在链表的表头插入
  1. 创建新结点newNode
  2. 新结点的next = head,即newNode.next = head
  3. 头head指向新的链表,即head = newNode

在这里插入图片描述

/*** 在链表的表头插入* @param head 原链表* @param nodeInsert 要插入表头的结点元素* @return*/
public static Node insertNodeByHead(Node head, Node nodeInsert) {nodeInsert.next = head;head = nodeInsert;return head;
}

在这里插入图片描述


(2)在链表的中间插入
  1. 循环找到要插入位置position的前一个结点(位置从1开始)
  2. 将插入结点的next指向前一个结点的next,即nodeInsert.next = newNode.next
  3. 将前一个结点的next指向插入结点,即newNode.next = nodeInsert
  • 注意:我们不能先将前一个结点的next指向插入结点,这是因为每个结点都只有一个next,因此如果先将前一个结点的next指向插入结点,那么15->7这一条线就断掉了,也就导致后面的7、40将会找不到,断开

在这里插入图片描述

/*** 在链表的中间位置插入* @param head 原链表的头结点* @param nodeInsert 要插入的结点* @param position 要插入的位置,从1开始* @return*/
public static Node insertNodeByPosition(Node head, Node nodeInsert, int position) {Node newNode = head; // 不对原链表进行操作,用新链表指向堆中的同一个元素,进行堆中的操作int i = 1;while (i < position - 1) { // 要在中间位置插入,因此要获取插入位置的前一个结点,这样子才能将next连接起来newNode = newNode.next;i++;}nodeInsert.next = newNode.next; // 将要插入的结点的next指向插入位置前一个结点的nextnewNode.next = nodeInsert; // 将插入位置前一个结点的next指向要插入的结点return head;
}

在这里插入图片描述


(3)在链表的结尾插入
  1. 获取原链表总共有多少个元素
  2. 循环遍历找到最后一个结点
  3. 将最后一个结点的next指向新结点

在这里插入图片描述

/*** 在链表的结尾插入* @param head 原链表的头结点* @param nodeInsert 要插入的结点* @return*/
public static Node insertByEnd(Node head, Node nodeInsert) {Node newNode = head;int nodeLength = getListLength(newNode); // 获取到原链表的元素个数int i = 1;while (i < nodeLength) { // 循环遍历找到最后一个结点newNode = newNode.next;i++;}newNode.next = nodeInsert; // 将最后一个结点的next指向新结点return head;
}

在这里插入图片描述


(4)在链表的所有位置插入[总结]⭐
/*** 链表的插入(所有情况,表头、中间、结尾)** @param head 原链表* @param nodeInsert 插入的结点* @param position 插入的位置,从1开始* @return*/
public static Node insertNode(Node head, Node nodeInsert, int position) {// head原链表中没有数据,插入的结点就是链表的头结点if (head == null) {return nodeInsert;}// 获取存放元素个数 - 进行校验(position在[1, size]之间)int size = getListLength(head);if (position > size + 1 || position < 1) {System.out.println("位置参数越界");return head;}// 表头插入if (position == 1) {nodeInsert.next = head;head = nodeInsert;return head;}// 中间插入和结尾插入Node newNode = head;int count = 1;while (count < position - 1) {count++;newNode = newNode.next;}nodeInsert.next = newNode.next;newNode.next = nodeInsert;return head;
}

1.4.3 链表删除 - 三种位置删除

  • 删除同样分为删除头部元素、删除中间元素和删除尾部元素

(1)删除链表的表头结点

将head表头向前移动一次之后,原先的结点就变成了不可达,会被JVM回收掉

在这里插入图片描述

/*** 删除表头结点* @param head 原链表* @return*/
public static Node deleteByHead(Node head) {head = head.next;return head;
}

在这里插入图片描述


(2)删除链表的最后一个结点
  1. 获取该链表的总长度size
  2. 找到倒数第二个结点
  3. 将倒数第二个结点的next指向null,即newNode.next = null

在这里插入图片描述

/*** 删除最后一个结点* @param head 原链表* @return*/
public static Node deleteByEnd(Node head) {Node newNode = head;int size = getListLength(head); // 获取该链表的总长度sizeint i = 1;while (i < size - 1) { // 找到倒数第二个结点i++;newNode = newNode.next;}newNode.next = null; // 将倒数第二个结点的next指向nullreturn head;
}

在这里插入图片描述


(3)删除链表的中间结点
  1. 找到要删除结点的前一个结点
  2. 将前一个结点的next指向下下个结点,即newNode.next = newNode.next.next

在这里插入图片描述

/*** 删除中间结点* @param head 原链表* @return*/
public static Node deleteByPosition(Node head, int position) {Node newNode = head;int i = 1;while (i < position - 1) {i++;newNode = newNode.next;}newNode.next = newNode.next.next;return head;
}

在这里插入图片描述


(4)删除链表的任一位置[总结]⭐
/*** 删除结点(三种情况,表头、中间、最后一位结点)* @param head 原链表* @return*/
public static Node deleteNode(Node head, int position) {// 如果没有结点,说明无法删除,直接返回null即可if (head == null) {return null;}//校验int size = getListLength(head);if (position > size || position < 1) { // 这里不是size+1,而插入是size+1,因为插入可以插入到最后一位(未知的最后一位),而删除必须要是已知的,不能是未知的越界System.out.println("输入的参数有误");return head;}if (position == 1) { // 删除头节点return head.next;} else { // 删除中间结点或者最后一个结点Node newNode = head;int count = 1;while (count < position - 1) {count++;newNode = newNode.next;}newNode.next = newNode.next.next;return head;}
}

这篇关于算法通关村第一关—青铜挑战—用Java基本实现各种链表操作的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/437079

相关文章

Python位移操作和位运算的实现示例

《Python位移操作和位运算的实现示例》本文主要介绍了Python位移操作和位运算的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录1. 位移操作1.1 左移操作 (<<)1.2 右移操作 (>>)注意事项:2. 位运算2.1

如何在 Spring Boot 中实现 FreeMarker 模板

《如何在SpringBoot中实现FreeMarker模板》FreeMarker是一种功能强大、轻量级的模板引擎,用于在Java应用中生成动态文本输出(如HTML、XML、邮件内容等),本文... 目录什么是 FreeMarker 模板?在 Spring Boot 中实现 FreeMarker 模板1. 环

Qt实现网络数据解析的方法总结

《Qt实现网络数据解析的方法总结》在Qt中解析网络数据通常涉及接收原始字节流,并将其转换为有意义的应用层数据,这篇文章为大家介绍了详细步骤和示例,感兴趣的小伙伴可以了解下... 目录1. 网络数据接收2. 缓冲区管理(处理粘包/拆包)3. 常见数据格式解析3.1 jsON解析3.2 XML解析3.3 自定义

SpringMVC 通过ajax 前后端数据交互的实现方法

《SpringMVC通过ajax前后端数据交互的实现方法》:本文主要介绍SpringMVC通过ajax前后端数据交互的实现方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价... 在前端的开发过程中,经常在html页面通过AJAX进行前后端数据的交互,SpringMVC的controll

Java中的工具类命名方法

《Java中的工具类命名方法》:本文主要介绍Java中的工具类究竟如何命名,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录Java中的工具类究竟如何命名?先来几个例子几种命名方式的比较到底如何命名 ?总结Java中的工具类究竟如何命名?先来几个例子JD

Java Stream流使用案例深入详解

《JavaStream流使用案例深入详解》:本文主要介绍JavaStream流使用案例详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录前言1. Lambda1.1 语法1.2 没参数只有一条语句或者多条语句1.3 一个参数只有一条语句或者多

Spring Security自定义身份认证的实现方法

《SpringSecurity自定义身份认证的实现方法》:本文主要介绍SpringSecurity自定义身份认证的实现方法,下面对SpringSecurity的这三种自定义身份认证进行详细讲解,... 目录1.内存身份认证(1)创建配置类(2)验证内存身份认证2.JDBC身份认证(1)数据准备 (2)配置依

利用python实现对excel文件进行加密

《利用python实现对excel文件进行加密》由于文件内容的私密性,需要对Excel文件进行加密,保护文件以免给第三方看到,本文将以Python语言为例,和大家讲讲如何对Excel文件进行加密,感兴... 目录前言方法一:使用pywin32库(仅限Windows)方法二:使用msoffcrypto-too

SpringBoot整合OpenFeign的完整指南

《SpringBoot整合OpenFeign的完整指南》OpenFeign是由Netflix开发的一个声明式Web服务客户端,它使得编写HTTP客户端变得更加简单,本文为大家介绍了SpringBoot... 目录什么是OpenFeign环境准备创建 Spring Boot 项目添加依赖启用 OpenFeig

Java Spring 中 @PostConstruct 注解使用原理及常见场景

《JavaSpring中@PostConstruct注解使用原理及常见场景》在JavaSpring中,@PostConstruct注解是一个非常实用的功能,它允许开发者在Spring容器完全初... 目录一、@PostConstruct 注解概述二、@PostConstruct 注解的基本使用2.1 基本代