Java:链表

news2024/11/27 14:52:23

一、链表简介

1、链表与顺序表的区别

上一篇博客我介绍了顺序表,这次我们来认识认识链表!先来看看二者的区别:

顺序表:由于顺序表实际上是一个数组,因此它在物理上是连续的,逻辑上也是连续的!

链表    :链表实际上是由一块一块的内存组成,因此物理上表示连续的,但是在逻辑上是连续的!

链表的一个生动例子也就是我们日常出行可见的火车高铁!大家想想看:火车是不是有一节一节的车厢连接而成?在就很我们要学习的链表很相似了!链表也是由一块一块的内存连接而成!

如图:

链表是由一个一个的节点组织起来的,整体就叫做链表



2、链表分类:

分类的几种标准:

单向           双向

带头           不带头

循环           非循环 

根据以上标准,链表基本划分为八种:

单向带头循环、单向带头非循环、单向不带头循环、单向不带头非循环

双向带头循环、双向带头非循环、双向不带头循环、双向不带头非循环

全部讲解不太现实,也没有必要,我们只要认识两种比较重要的类型就行了!接下来我们主要认识的是:

单向不带头非循环、双向不带头非循环



3、通过画图认识几种类型:

单向不带头非循环:

如图:

1、每一个节点由两个域构成,分别是valuenext,value存储这个节点的数据,next存储下一个节点的地址!

2、单向的意思是这个链表只有一个方向,即箭头方向

3、不带头表示表示这个链表没有头,而是指这个链表的头不固定!

如这个例子,现在这个头的地址为第一个节点的地址0x12,如果把第一个节点删除,那么这个头的地址也就修改为第二个节点的地址0x33



单向带头非循环: 

 与前面的单向不带头非循环对比,区别是这个类型带了一个固定的头,它永远不会变!

如图:



循环类型: 

循环类型与非循环类型的区别在于,最后一个节点存储的地址是不是为null?

非循环:最后一个节点存储的地址为null

循环   :最后一个节点存储的地址为第一个节点的地址 

如图:这个为循环类型



二、链表的实现

全部代码:

1、链表类代码:

public class MySingleLinkedList {

    //链表是由一个个节点构成,因此我们可以抽象出一个节点类
    class ListNode{
        public int val;
        public ListNode next;

        //创建构造方法
       public  ListNode(int val){
            this.val=val;
        }
    }

    //链表的头节点
    public ListNode head;

    //创建一个小链表的方法:
    public void create(){
        ListNode node1=new ListNode(1);
        ListNode node2=new ListNode(2);
        ListNode node3=new ListNode(3);
        ListNode node4=new ListNode(4);

       //通过地址连接链表
        node1.next=node2;
        node2.next=node3;
        node3.next=node4;
        head=node1;
    }

    //打印链表的方法:
    public void display(){
        ListNode cur=head;
        while(cur!=null){
            System.out.print(cur.val+" ");
            cur=cur.next;
        }
    }

    //求链表长度的方法:
    public int size(){
        ListNode cur=head;
        int count=0;
        while(cur!=null){
            count++;
            cur=cur.next;
        }
        return count;
    }

    //头插的方法:
    public void addFirst(int val){
        ListNode node=new ListNode(val);
        node.next=head;
        head=node;
    }

    //尾插的方法:
    public void addLast(int val){
        ListNode node=new ListNode(val);
        //如果head为空
        if(head==null){
            head=node;
            return ;
        }


        //当head不为空
        ListNode cur=head;
        //找到尾节点
        while(cur.next!=null){
            cur=cur.next;
        }
        cur.next=node;
    }

    //任意位置插入的方法:
    public void addIndex(int index,int val){
        //检查index是否合法
        try{
            checkIndex(index);
        }catch (IndexNotLeaglException e){
            e.printStackTrace();
        }

        //index==0,相当于头插
        if(index==0){
            addFirst(val);
            return;
        }

        //index=size,相当于尾插
        if(index==size()){
            addLast(val);
            return;
        }

        //0<index<size
         if(index>0&&index<size()){
            ListNode node=new ListNode(val);
            ListNode cur=findIndexSubOne(index);
            node.next=cur.next;
            cur.next=node;
        }

        }


    //找到index的前一个位置的方法:
    private ListNode findIndexSubOne(int index){
        ListNode cur=head;
        int count=0;
        while(count!=index-1){
            cur=cur.next;
            count++;
        }
        return cur;
    }


    //检查index是否合法
    private void checkIndex(int index){
        if(index<0||index>size()){
            throw new IndexNotLeaglException("index不合法!");
        }
    }

    //查找是否包含某个关键字key的方法:
    public boolean contains(int key){
        ListNode cur=head;
        while(cur!=null) {
            if(cur.val==key){
                return true;
            }
            cur=cur.next;
        }
        return false;
    }


    //删除链表中第一次出现关键字key的节点方法:
    public void remove(int key){

     //1、删除头节点
        if(head==null){
            return;
        }
        if(head.val==key){
            head=head.next;
            return;
        }

     //2、要删除的元素不在头节点

     //先找到要删除的前一个节点
       ListNode cur=head;
       while(cur.next!=null){
           if(cur.next.val==key){
               break;
           }
           cur=cur.next;
       }
       //删除该节点
        cur.next=cur.next.next;
    }


    //去除链表中所有相同的关键字
    public void removeAllKey(int key){

        if(head==null){
            return;
        }
        //1、先去除头节点以外的与key相同值的节点
        ListNode prev=head;//cur的前驱节点
        ListNode cur=head.next;//寻找要去除的节点

        //判断并且删除节点
        while(cur!=null){
            if(cur.val==key){
                prev.next=cur.next;
                cur=cur.next;
            }else{
                prev=cur;
                cur=cur.next;
            }
        }

        //2、判断头节点存储的内容是否为key
        if(head.val==key){
            head=head.next;
        }
    }
}

2、异常类代码: 

public class IndexNotLeaglException extends RuntimeException{
    IndexNotLeaglException(){

    }
    IndexNotLeaglException(String msg){
        super(msg);
    }
}

全部类文件: 




  代码讲解: 

1、类文件

如图,为了实现链表,我们创建了两个类:MySingleLinkedList类和Test类

MySingleLinkedList类:主要是用来创建链表和实现链表的具体功能!

Test类:主要是用来测试和使用链表



2、创建节点类 

   我们知道,链表是由一个一个的节点构成的,每个节点由value(节点的内容)next(下一个节点的地址)构成,那么在自己实现链表的过程中,我们就可以抽象出来一个节点类,包含两个成员变量val和next。

   此时这个ListNode类定义在MySingleLinkedList类的内部,称为内部类!

   另外,由于链表需要一个头,方便我们遍历链表,因此我们可以定义一个ListNode类型的引用变量head,用来存储第一个节点的地址!

public class MySingleLinkedList {

    //链表是由一个个节点构成,因此我们可以抽象出一个节点类
    class ListNode{
        public int val;
        public ListNode next;

        //创建构造方法
       public ListNode(int val){
            this.val=val;
        }
    }

    //链表的头节点
    public ListNode head;
    
}


3、创建链表方法: 

我们知道,链表的底层不像顺序表,它不是由数组构成,而是由一个一个的节点连接而成!

因此我们在创建链表的时候,需要实例化一个一个的ListNode类!这里我们可以实现一个方法,帮助我们创建一个小链表! 

//创建一个小链表
    public void create(){
        ListNode node1=new ListNode(1);
        ListNode node2=new ListNode(2);
        ListNode node3=new ListNode(3);
        ListNode node4=new ListNode(4);

       //通过地址连接链表
        node1.next=node2;
        node2.next=node3;
        node3.next=node4;
        head=node1;
    }


3、打印链表方法:

为了知道链表中存储了什么内容,我们可以实现一个打印链表的方法!

//打印链表方法:
    public void display(){
        ListNode cur=head;
        while(cur!=null){
            System.out.print(cur.val+" ");
            cur=cur.next;
        }
    }


4、求链表长度方法: 

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


 5、头部插入方法

在第一个节点之前插入一个新的节点:

首先,我们需要新实例化一个ListNode类node作为一个新的节点,此时的node.next

里面存储

插入前:

插入后:

 //头插
    public void addFirst(int val){
        ListNode node=new ListNode(val);
        node.next=head;
        head=node;
    }


6、尾部插入方法: 

尾部插入方法分为两种情况讨论:链表head为空or链表head不为空 

首先实例化一个节点类的对象node

1、当链表的head为空,我们只需要将head=node就行

2、当链表的head不为空,先找到尾节点,再将尾节点的next赋值为node

 //尾插
    public void addLast(int val){
        ListNode node=new ListNode(val);
      //如果head为空
        if(head==null){
            head=node;
            return;
        }


      //当head不为空
        ListNode cur=head;
        //找到尾节点
        while(cur.next!=null){
            cur=cur.next;
        }
        cur.next=node;
    }




7、任意位置插入方法: 

   相对于头部插入尾部插入,这个任意位置插入的方法实现起来比较复杂,原因是,它自己本身也包含了头部插入和尾部插入功能!并且在此之上,还可以插入除了头部和尾部之间的任意位置! 

   首先,我们要弄清楚的一点是,链表本身不像顺序表,它是没有下标这个概念的,但是为了实现这个功能,我们可以人为地认为链表是有下标(index)的!

如图:

那么我们可以将其分为几种情况:

1、index==0,这时相当于头部插入

2、index==size(size表示链表节点的个数),这时相当于尾部插入

3、0<index<size,插入除头部和尾部的任意中间位置

4、index<0或index>size,下标不合法


1、index==0 和  2、index==size

这个时候调用头部插入方法和尾部插入方法即可

        //index==0,相当于头插
        if(index==0){
            addFirst(val);
            return;
        }

        //index=size,相当于尾插
        if(index==size()){
            addLast(val);
            return;
        }

3、0<index<size 

这种情况下,我们分为两步走:

第一步:找到index位置的前一个位置,

先定义一个cur==head,然后移动cur,使cur找到index位置的前一个位置

  //找到index的前一个位置的方法:
    private ListNode findIndexSubOne(int index){
        ListNode cur=head;
        int count=0;
        while(count!=index-1){
            cur=cur.next;
            count++;
        }
        return cur;
    }

第二步:连接节点 

连接节点只需要修改两个东西,以图为例子:

一个是新节点node的next,要存入原下标为2的节点的地址

一个是下标为1的节点的next,要修改为node的地址

修改之后:

 //0<index<size
         if(index>0&&index<size()){
            ListNode node=new ListNode(val);
            ListNode cur=findIndexSubOne(index);
            node.next=cur.next;
            cur.next=node;
        }

 4、index<0或index>size(index不合法)

这种情况下,我们可以在插入前检查下标,如果不合法,那就抛出异常!

首先新建一个类,定义一个异常类:

public class IndexNotLeaglException extends RuntimeException{
    IndexNotLeaglException(){

    }
    IndexNotLeaglException(String msg){
        super(msg);
    }
}

然后在MySingleLinkedList类中实现一个检查下标的方法:

//检查index是否合法
    private void checkIndex(int index){
        if(index<0||index>size()){
            throw new IndexNotLeaglException("index不合法!");
        }
    }

接下来就使用try……catch语句使用该异常类 

 



任意插入方法的全部代码  

//任意位置插入的方法:
    public void add(int index,int val){
        //检查index是否合法
        try{
            checkIndex(index);
        }catch (IndexNotLeaglException e){
            e.printStackTrace();
        }

        //index==0,相当于头插
        if(index==0){
            addFirst(val);
            return;
        }

        //index=size,相当于尾插
        if(index==size()){
            addLast(val);
            return;
        }

        //0<index<size
         if(index>0&&index<size()){
            ListNode node=new ListNode(val);
            ListNode cur=findIndexSubOne(index);
            node.next=cur.next;
            cur.next=node;
        }

        }


    //找到index的前一个位置的方法:
    private ListNode findIndexSubOne(int index){
        ListNode cur=head;
        int count=0;
        while(count!=index-1){
            cur=cur.next;
            count++;
        }
        return cur;
    }


    //检查index是否合法
    private void checkIndex(int index){
        if(index<0||index>size()){
            throw new IndexNotLeaglException("index不合法!");
        }
    }
}

新创建的异常类型: 



8、查找是否包含关键字key的方法:

查找链表中的每一个节点,如果链表中存在key,返回true,不存在key,返回false 

 //查找是否包含某个关键字key的方法:
    public boolean contains(int key){
        ListNode cur=head;
        while(cur!=null) {
            if(cur.val==key){
                return true;
            }
            cur=cur.next;
        }
        return false;
    }

9、删除第一次出现关键字为key的节点 的方法:

   删除指定元素的节点分为2种情况:

1、删除的元素在头节点:

这种情况比较简单:只需要将链表的头修改为下一个链表的地址即可!

另外,如果head==null,不执行任何操作,直接return

 //1、删除头节点
        if(head==null){
            return;
        }
        if(head.val==key){
            head=head.next;
            return;
        }

2、要删除的元素不在头节点: 

如果要删除指定元素所在的节点,我们可以先定义一个cur,让它找到要删除节点的前一个节点

   用cur.next节点的val来判断,cur的下一个节点存储的元素是不是我们要找的元素key,如果是,那么此时的cur正指向要删除节点的前一个节点

   注意,此时while循环的判断条件应该是(cur.next!=null)而不是(cur!=null)!

//先找到要删除的前一个节点
       ListNode cur=head;
       while(cur.next!=null){
           if(cur.next.val==key){
               break;
           }
           cur=cur.next;
       }
       //删除该节点
        cur.next=cur.next.next;
    }

让我们看看二者的区别:

1、cur!=null

当cur指向该链表的最后一个节点,由于cur!=null,因此进入循环。

我们就会发现,在循环体语句cur.next.val==key会报错!因为cur.next为null,因此也不会有cur.next.next(相当于null.next)

2 、cur.next!=null

当cur指向倒数第二个节点,上述的语法不会报错!



10、删除链表中相同的元素的方法:


    //去除链表中所有相同的关键字
    public void removeAllKey(int key){

        if(head==null){
            return;
        }
        //1、先去除头节点以外的与key相同值的节点
        ListNode prev=head;//cur的前驱节点
        ListNode cur=head.next;//寻找要去除的节点

        //判断并且删除节点
        while(cur!=null){
            if(cur.val==key){
                prev.next=cur.next;
                cur=cur.next;
            }else{
                prev=cur;
                cur=cur.next;
            }
        }

        //2、判断头节点存储的内容是否为key
        if(head.val==key){
            head=head.next;
        }
    }

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

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

相关文章

文件上传漏洞-黑名单检测

黑名单检测 一般情况下&#xff0c;代码文件里会有一个数组或者列表&#xff0c;该数组或者列表里会包含一些非法的字符或者字符串&#xff0c;当数据包中含有符合该列表的字符串时&#xff0c;即认定该数据包是非法的。 如下图&#xff0c;定义了一个数组$deny_ext array(.a…

电脑文件msvcp120.dll丢失的解决方法详细分析,找多种靠谱方法修复

遇到msvcp120.dll文件丢失的问题实际上不算罕见&#xff0c;这往往是由于我们频繁使用电脑而导致的意外删除&#xff0c;或者是电脑受到病毒感染。当这类情况发生时&#xff0c;msvcp120.dll文件可能会被错误地移除或损坏&#xff0c;这便需要我们去进行修复。接下来&#xff0…

3D汽车模型线上三维互动展示提供视觉盛宴

VR全景虚拟看车软件正在引领汽车展览行业迈向一个全新的时代&#xff0c;它不仅颠覆了传统展览的局限&#xff0c;还为参展者提供了前所未有的高效、便捷和互动体验。借助于尖端的vr虚拟现实技术、逼真的web3d开发、先进的云计算能力以及强大的大数据处理&#xff0c;这一在线展…

【正点原子Linux连载】第二十三章 Linux PWM驱动实验 摘自【正点原子】ATK-DLRK3568嵌入式Linux驱动开发指南

1&#xff09;实验平台&#xff1a;正点原子ATK-DLRK3568开发板 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id731866264428 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/docs/boards/xiaoxitongban 第二十…

Rust 02.控制、引用、切片Slice、智能指针

1.控制流 //rust通过所有权机制来管理内存&#xff0c;编译器在编译就会根据所有权规则对内存的使用进行 //堆和栈 //编译的时候数据的类型大小是固定的&#xff0c;就是分配在栈上的 //编译的时候数据类型大小不固定&#xff0c;就是分配堆上的 fn main() {let x: i32 1;{le…

AOP的实现方式一(Spring学习笔记十)

1、什么是AOP 全称是 Aspect Oriented Programming 即&#xff1a;面向切面编程。是OOP&#xff08;面向对象编程&#xff09;的延续&#xff0c;也是Spring框架中的一个重要内容&#xff0c;是函数式编程的一种衍生泛型。简单的说他就是把我们程序重复的代码抽取出来&#xf…

【网站项目】泉文化管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

面试题:JVM的垃圾回收

一、GC概念 为了让程序员更专注于代码的实现&#xff0c;而不用过多的考虑内存释放的问题&#xff0c;所以&#xff0c;在Java语言中&#xff0c;有了自动的垃圾回收机制&#xff0c;也就是我们熟悉的GC(Garbage Collection)。 有了垃圾回收机制后&#xff0c;程序员只需要关…

java调用c++,使用clion进行JNI开发,ddl包生成以及so包生成

java调用c&#xff0c;使用clion进行JNI开发&#xff0c;ddl包生成以及so包生成 java基础代码生成C头部文件使用clion写C实现代码cmke打包构建使用java调用C执行linux环境下产生CPP的so包 java基础代码 先写好对应的基础代码&#xff0c;先不管static加载的ddl文件&#xff0c…

【JavaScript】数组 ③ ( JavaScript 数组长度 | 修改数组长度 | 数组案例 )

文章目录 一、JavaScript 数组长度1、数组长度2、修改数组长度 二、数组案例1、求数组元素平均值2、求数组元素最大值 一、JavaScript 数组长度 1、数组长度 在 JavaScript 中 , 数组长度 可以通过 数组变量的 length 属性 获取 , 该属性 返回 数组中的元素数量 , 也就是 数组长…

软件速成书:一夜成神的传说,还是现实的泡影?

嗨&#xff0c;各位小伙伴&#xff01; &#x1f431;‍&#x1f4bb; 我是【行走的程序喵】&#xff01;一个兼具Web前端和Java后端技能的技术宅&#xff01; &#x1f31f; 我的博客上分享最新的Web前端和Java后端技术文章&#xff0c;从基础入门到进阶应用&#xff0c;应有…

Linux系统----------探索mysql数据库MHA高可用

目录 一、MHA概述 1.1 什么是 MHA 1.2MHA 的组成 1.2.1MHA Node&#xff08;数据节点&#xff09; 1.2.2MHA Manager&#xff08;管理节点&#xff09; 1.3MHA 的特点 1.4MHA工作原理 1.5数据同步的方式 1.5.1同步复制 1.5.2异步复制 1.5.3半同步复制 二、搭建 MySQ…

夜神模拟器录制脚本录制键盘操作方法

很多用户们在使用夜神模拟器的时候&#xff0c;有个操作助手的功能非常的好用&#xff0c;用户们在录制自己的操作以后&#xff0c;就能够进行全自动的操作等&#xff0c;使用起来还是非常方便的&#xff0c;想要知道的用户们快来查看详细的教程吧~ 夜神模拟器录制脚本怎样录制…

5-规范设计(下):commit信息风格迥异、难以阅读,如何规范?

我们在做代码开发时&#xff0c;经常需要提交代码&#xff0c;提交代码时需要填写 Commit Message&#xff08;提交说明&#xff09;&#xff0c;否则就不允许提交。 所以在 Go 项目开发时&#xff0c;一个好的 Commit Message 至关重要&#xff1a; 可以使自己或者其他开发人…

3.26学习总结java初步实现学生管理系统

(该项目通过视频讲解过程中完成,其中将一些操作进行了修改和完善,其目的是为了巩固前面学习java的一些用法,熟悉写项目的过程) 一.项目要求 学生类: 属性:id、姓名、年龄、家庭住址 添加功能: 键盘录入每一个学生信息并添加&#xff0c;需要满足以下要求: ID唯一 删除功能…

Qt源码调试步骤记录

1.源码&#xff1a; 两种方式&#xff0c;要么安装qt时选择source&#xff0c;要么从官网下载源码&#xff0c;然后在qt creator中设置路径。二选一即可。我选的第二种。 1.1.第一种&#xff0c;安装时选择source&#xff1a; 1.2.第二种&#xff0c;下载源码设置路径&#x…

Linux中安装JDK17.X

1、总体概述&#xff1f; 该操作方式适合centos或red hat环境 2.1、在线下载JDK安装包&#xff1f; 通过wget命令下载JDK17.X包 wget https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.tar.gz 如果提示&#xff1a;没有wget命令就安装wget yum install w…

Qt 富文本处理 (字体颜色大小加粗等)

Qt中支持HTML的控件有textEdit 、label 、textBrowser 。 接口&#xff1a;setHtml("Qt"); toHtml(). 文本样式设置 : 可分字设置 &#xff0c;主要使用QTextCharFormat类进行文本样式设置。 示例&#xff1a; QTextCharFormat fmt; //粗体 fmt.setFontWeight…

wps没保存关闭了恢复数据教程

有时候我们因为电脑问题会忘记保存就关闭wps导致数据丢失&#xff0c;不知道wps没保存关闭了怎么恢复数据&#xff0c;其实数据是无法恢复的。 wps没保存关闭了怎么恢复数据 1、wps没有数据恢复功能&#xff0c;不过可以开启自动备份。 2、我们可以先点击wps左上角的“文件”…

Vtk裁剪功能之平面裁剪vtkClipClosedSurface(vtk小记)

1.原理分析 对你的三维图形&#xff0c;使用一个平面切下去&#xff0c;然后保留一半。 确定一个平面&#xff1a;使用法向量和一个三维坐标点可以确定一个平面 原始图像 切一刀 切两刀&#xff0c;又一半 切三刀&#xff0c;又一半 源代码 #include <vtkActor.h> #i…