【数据结构】链表(一)

链表(一)

文章目录

  • 链表(一)
    • 01 引入
    • 02 概念及结构
    • 03 单向不带头不循环链表实现
      • 3.1 创建节点类型
      • 3.2 简易创建一个链表
      • 3.3 遍历链表每个节点
      • 3.4 获取链表长度
      • 3.5 查找是否包含关键字key是否在单链表当中
      • 3.6 头插法
      • 3.7 尾插法
      • 3.8 任意位置插入
      • 3.9 删除第一次出现关键字为key的节点
      • 3.10 删除所有值为key的节点
      • 3.11 清空

01 引入

接上文顺序表,我们可以知道ArrayList的缺陷。当在ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后搬移,时间复杂度为O(n),效率比较低,因此ArrayList不适合做任意位置插入和删除比较多的场景。因此:java集合中又引入了LinkedList,即链表结构。

那么下面,让我们来了解了解链表。

02 概念及结构

链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。其实就是节点组成,一个节点类似于火车一个车厢那样:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nxFKWdPq-1691313293619)(C:\Users\86159\AppData\Roaming\Typora\typora-user-images\image-20230802182622237.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NmTz2djR-1691313293621)(E:\Typora_note\数据结构笔记\Java\链表.assets\image-20230805165401086.png)]

下面是一个单向带头非循环链表:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cIeoB8dK-1691313293621)(C:\Users\86159\AppData\Roaming\Typora\typora-user-images\image-20230803142840530.png)]

下面是链表的种类:

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kxeLdsML-1691313293622)(https://gitee.com/liuhb-clanguage/picture/raw/master/png/image-20230803144159308.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yb7FYu2f-1691313293622)(https://gitee.com/liuhb-clanguage/picture/raw/master/png/image-20230803144412289.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PCab9kv4-1691313293623)(C:\Users\86159\AppData\Roaming\Typora\typora-user-images\image-20230803144625610.png)]

通过彼此间的排列组合,一共可以分为:8种,如下:

  1. 单向 不带头 非循环
  2. 单向 不带头 循环
  3. 单向 带头 非循环
  4. 单向 带头 循环
  5. 双向 不带头 非循环
  6. 双向 不带头 循环
  7. 双向 带头 非循环
  8. 双向 带头 循环

这里着重介绍单向 不带头 非循环 和 双向 不带头 非循环。

  • 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。
  • 无头双向链表:在Java的集合框架库中LinkedList底层实现就是无头双向循环链表。

03 单向不带头不循环链表实现

3.1 创建节点类型

链表是由一个一个节点组成的,每一个节点对应每一个对象,如果我们去抽象他,他其实有两个域值,所以我们可以把节点定义成内部类。

创建一个ListNode类来作为节点类型,包含两个成员变量:val域用来储存数据(数据域),next用来存储下一个节点的地址(指针域)。
再创建一个带参的构造方法来实例化对象,同时给val赋值,next的默认值是null。接下来我们用代码来实现一下:

//静态内部类
    static class ListNode{
        public int val; //节点的值域
        public ListNode next; //下一个节点的地址

        //实例化节点对象
        public ListNode(int val){
            this.val = val;
        }
    }

3.2 简易创建一个链表

public ListNode head;//表示当前链表的头节点

    //以穷举的方式创建一个链表
    public void createList(){
        ListNode node1 = new ListNode(12);
        ListNode node2 = new ListNode(23);
        ListNode node3 = new ListNode(34);
        ListNode node4 = new ListNode(45);
        ListNode node5 = new ListNode(56);
    }

此时创建的链表如图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a22rDdR2-1691313293623)(E:\Typora_note\数据结构笔记\Java\链表.assets\image-20230803161957003.png)]

目前各节点间不存在关系。

那么接下来的操作就是要让node1->node2->node3->node4->node5

//以穷举的方式创建一个链表
    public void createList(){
        ListNode node1 = new ListNode(12);
        ListNode node2 = new ListNode(23);
        ListNode node3 = new ListNode(34);
        ListNode node4 = new ListNode(45);
        ListNode node5 = new ListNode(56);
        
        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        node4.next = node5;
        
        this.head = node1;
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KSoJaQPS-1691313293623)(E:\Typora_note\数据结构笔记\Java\链表.assets\image-20230803163008316.png)]

这里我们不妨在编译器里debug来看看:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0gyacBuy-1691313293624)(E:\Typora_note\数据结构笔记\Java\链表.assets\image-20230803221846684.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r9st8GkK-1691313293624)(E:\Typora_note\数据结构笔记\Java\链表.assets\image-20230803221913786.png)]

3.3 遍历链表每个节点

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

这里我们可以在测试类中debug一下看看:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mvZIpdf9-1691313293624)(E:\Typora_note\数据结构笔记\Java\链表.assets\image-20230803225415004.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zQCGDYFR-1691313293625)(E:\Typora_note\数据结构笔记\Java\链表.assets\image-20230803225425896.png)]

虽然这里的确是将链表遍历完全,但是,这里的头节点head = null
在这里插入图片描述

那么这样就会造成一个后果,鬼都不知道头节点head死哪里去了,那咋办?

这个后果虽然很严重,但是解决办法其实也很容易,既然head它不能乱来,那它可以叫个分身cur代替它去呀。

public void display() {
        ListNode cur = head;
        while (cur != null){
            //如果cur == null,说明把链表遍历完成
            System.out.println(cur.val+" ");
            cur = cur.next;
            //cur每次向后走一步
        }
        System.out.println();
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4tyC9HTA-1691313293625)(E:\Typora_note\数据结构笔记\Java\链表.assets\image-20230803232650482.png)]

3.4 获取链表长度

这个其实也就是基于3.3 遍历链表得来的。

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

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

这个其实也一样是基于3.3 遍历链表得来的。

//查找是否包含关键字key是否在单链表当中
    public boolean contains(int key){
        ListNode cur = head;
            while(cur != null){
                if (key == cur.val){
                    return true;
                }
                  cur = cur.next;
            }
        return false;
    }

3.6 头插法

头插法指的是在链表的头节点位置插入一个新的节点,定义一个node表示插入的新节点,然后将node.next = head,head = node,即可。

形象一点来看,如图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wq1UPcRp-1691313293625)(E:\Typora_note\数据结构笔记\Java\链表.assets\image-20230804140014328.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uiECf3X7-1691313293626)(E:\Typora_note\数据结构笔记\Java\链表.assets\image-20230804140805784.png)]

//头插法
    public void addFirst(int data){
        ListNode node = new ListNode(data);

        node.next = head;
        head = node;
    }

3.7 尾插法

尾插法指的是:在链表的尾巴节点位置插入一个新节点,定义一个node表示新节点,如同头插法那样,对原尾巴节点的next进行赋值。下面是尾插链表出现的情况:

  • 当链表不为空的时候,定义一个cur来代替head(这里其实和遍历节点的道理一致),直到cur.next == null 的时候就跳出遍历,cur.next == node,这样即可完成尾插。

  • 当链表为空的时候,不论我们定义什么去代替head,都是竹篮打水一场空,都无法进入遍历,同时也会报空指针异常,解决方法其实也很简单,直接让head = node即可。

具体分析见以下:

如图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cQZ0sLBB-1691313293626)(https://gitee.com/liuhb-clanguage/picture/raw/master/png/image-20230804142311057.png)]

其实这其中也有遍历链表那味了,其思想就是遍历到链表最后一个节点,然后再进行尾插就行了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UcGHqGsq-1691313293626)(https://gitee.com/liuhb-clanguage/picture/raw/master/png/image-20230804143200359.png)]

//尾插法
    public void addLast(int data){
        ListNode node = new ListNode(data);

        ListNode cur = head;
        while (cur.next != null){
            cur = cur.next;
        }
        cur.next = node;
    }

但是这个代码是具有一定的问题的,情景如下图:

在这里插入图片描述

这个问题其实就是,此时head是空的,同时我们定义一个cur= head,那么cur也是空,那么就无法进入到while循环中。修正如下:

//尾插法
    public void addLast(int data){
        ListNode node = new ListNode(data);

        ListNode cur = head;
        if (cur == null){
            head = node;
            return;
        }
        //找到链表的尾巴,注意是cur.next 不是cur
        while (cur.next != null){
            cur = cur.next;
        }
        cur.next = node;
    }

3.8 任意位置插入

思路:

  1. 定义curindex-1步,找到要插入位置的前一个节点
  2. 进行插入

给一个情景,定义cur = index - 1,在1号、2号间插入node.

关键就在于我们要将0x66 -> 0x777,null -> 0x32,即node.next = cur.next,cur.next = node.

需要将head先移至2号位置(注意:用cur代替head,防止head丢失),然后

node.next = cur.next使该节点的next域改为下一节点的地址,再cur.next = node.next使前一节点的next域改为该节点的地址。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ugPmgfIm-1691313293627)(https://gitee.com/liuhb-clanguage/picture/raw/master/png/image-20230804161544727.png)]

//任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index,int data){
        if (index < 0 || index > size()){
            System.out.println("index不合法");
            return;
        }
        if (index == 0){
            addFirst(data);
            return;
        }
        if (index == size()){
            addLast(data);
            return;
        }

        /*ListNode node = new ListNode(data);
        ListNode cur = head;

        int tmp = index - 1;
        while (tmp != 0){
            cur = cur.next;
            tmp--;
        }
        node.next = cur.next;
        cur.next = node;
         */
        //将上面这一坨封装
        ListNode cur = findIndexSubOne(index);
        ListNode node = new ListNode(data);
        node.next = cur.next;
        cur.next = node;
    }

    /**
     * 找到要删除节点位置的前一个节点
     * @param index
     * @return
     */
    private ListNode findIndexSubOne(int index){
        ListNode cur = head;
        int tmp = index - 1;
        while (tmp != 0){
            cur = cur.next;
            tmp--;
        }
        return cur;
    }

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

效果图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XNjLHKsz-1691313293628)(E:\Typora_note\数据结构笔记\Java\链表.assets\image-20230804163610766.png)]

  • 对于删除第一次出现的key值的节点,若不是头节点,我们只需将key值对应的节点的前一节点的next的域改为key值对应的节点的next域即可。

  • 对于头节点,直接head = head.next即可。

  1. 找到要删除节点的前一个节点
  2. 此时要删除的节点del = cur.next;
  3. 进行删除cur.next = del.next

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Z4twZk9-1691313293628)(https://gitee.com/liuhb-clanguage/picture/raw/master/png/image-20230804165759876.png)]

//删除第一次出现关键字为key的节点
    public void remove(int key){
        if (head == null){
            return;
        }
        //单独删除头节点
        if (head.val == key){
            head = head.next;
            return;
        }
        
        ListNode cur = searchPrev(key);
        if (cur == null){
            System.out.println("没有你要删除的数字!");
            return;
        }
        ListNode del = cur.next;
        cur.next = del.next;
        
    }

    /**
     * 找到关键字key的前驱
     * @param key
     * @return
     */
    private ListNode searchPrev(int key){
        ListNode cur = head;
        while (cur.next != null){
            if (cur.next.val == key){
                return cur;
            }
            cur = cur.next;
        }
        return null;
    }

3.10 删除所有值为key的节点

情景如下:

将值为23的删除

有种暴力的方法,我们只需要多次调用3.9种的remove函数即可,但是这并不是我们真正想要的,要求:遍历一遍就要删除完。

如图分析:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XnRqeEfk-1691313293628)(https://gitee.com/liuhb-clanguage/picture/raw/master/png/image-20230806160715303.png)]

//删除所有值为key的节点
    public void removeAllKey(int key){
        ListNode prev = head;
        ListNode cur = head.next;

        if (head == null){
            return;
        }

        while (cur != null){
            if(cur.val == key){
                prev = cur.next;
                cur = cur.next;
            }else {
                prev = cur;
                cur = cur.next;
            }
        }
        if (head.val == key){
            head = head.next;
        }
    }

3.11 清空

简单粗暴:

public void clear() {
        this.head = null;
    }

温柔:

    public void clear(){
        while(this.head != null){
            ListNode curNext = this.head.next;
            this.head.next = null;
            this.head = cur.next;
        }
    }

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

国产数据库排行

目录 一、理论 1.国产数据库排行 2.数据 一、理论 1.国产数据库排行 &#xff08;1&#xff09;墨天轮榜单 墨天轮国产数据库流行度排行于2019年6月推出&#xff0c;通过近50个维度的数据来考察近300个国产数据库的流行度排行&#xff0c;每月1日更新排行数据&#xff0c…

layui的基本使用-日期控件的业务场景使用入门实战案例一

效果镇楼&#xff1b; 1 前端UI层面&#xff1b; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport&…

虚幻引擎游戏开发过程中,游戏鼠标如何双击判定?

UE虚幻引擎对于游戏开发者来说都不陌生&#xff0c;市面上有47%主机游戏使用虚幻引擎开发游戏。作为是一款游戏的核心动力&#xff0c;它的功能十分完善&#xff0c;囊括了场景制作、灯光渲染、动作镜头、粒子特效、材质蓝图等。本文介绍了虚幻引擎游戏开发过程中游戏鼠标双击判…

培训报名小程序报名功能开发

目录 1 创建页面2 新建URL参数3 课程详细信息4 报名数据源创建5 报名信息功能开发6 设置页面跳转7 最终实现的效果总结 在培训报名小程序中&#xff0c;我们已经开发了首页和列表页。在列表页点击报名时就跳转到报名页面&#xff0c;先看我们的原型 报名页分为两个部分&#x…

多货币多汇率跨境电子商城建设(仓储管理、网络安全)

多货币多汇率跨境电子商城建设需要考虑到多个方面&#xff0c;包括仓储管理、网络安全、货币兑换、物流配送等。以下是具体的介绍&#xff1a; 一、仓储管理 仓储管理是跨境电子商城的重要组成部分&#xff0c;需要考虑到商品的存储、管理和分拣等环节。以下是需要注意的几个…

C# 使用FFmpeg.Autogen对byte[]进行编解码

C# 使用FFmpeg.Autogen对byte[]进行编解码&#xff0c;参考&#xff1a;https://github.com/vanjoge/CSharpVideoDemo 入口调用类&#xff1a; using System; using System.IO; using System.Drawing; using System.Runtime.InteropServices; using FFmpeg.AutoGen;namespace F…

LinuxC编程——线程

目录 一、概念二、进程与线程的区别⭐⭐⭐三、线程资源四、函数接口4.1 线程创建4.2 线程退出4.3 线程回收4.3.1 阻塞回收4.3.2 非阻塞回收 4.4 pthread_create之传参4.5 练习 一、概念 是一个轻量级的进程&#xff0c;为了提高系统的性能引入线程。 进程与线程都参与cpu的统一…

内核裁剪与驱动编译

linux设备驱动以内核模块的形式出现&#xff0c;编写linux内核模块编程是学习linux设备驱动的先决条件。 在编译linux内核之前要先配置linux内核。每个板子都有其对应的默认配置文件&#xff0c;这些默认配置文件保存在arch/arm/configs 目录中。比如xilinx_zynq_defconfig作为…

暗黑版GPT流窜暗网 降低犯罪门槛

随着AIGC应用的普及&#xff0c;不法分子利用AI技术犯罪的手段越来越高明&#xff0c;欺骗、敲诈、勒索也开始与人工智能沾边。 近期&#xff0c;专为网络犯罪设计的“暗黑版GPT”持续浮出水面&#xff0c;它们不仅没有任何道德界限&#xff0c;更没有使用门槛&#xff0c;没有…

英特尔发布雷电3接口:竟和USB Type-C统一了 - 全文

在过去的一年里&#xff0c;外部连接通信线的世界里发生了很多时。在这段时间&#xff0c;USB先后发布了10Gbps “超高速”USB3.1以及新的USB Type-C连接器&#xff0c;这是一种新式的可正反插的接口&#xff0c;将成为未来十年乃至更长时间上的行业标准。同时随着USB备用模式功…

centos自动同步北京时间

1、安装ntpdate服务 yum -y install ntpdate 2、加入自动任务计划 查找ntpdate的路径&#xff1a; which ntpdate 复制这个路径。 编辑自动任务计划并加入ntpdate&#xff1a; crontab -e # 每小时第30分钟同步AD域控时间 30 * * * * /usr/sbin/ntpdate -u 192.168.2.8 > …

qt在vs中编译出现link2001时,不会生成moc文件了

现象&#xff1a; 解决方法&#xff1a; 在对应头文件-属性-配置属性-常规-项类型-改为Qt Meta-Object Compiler (moc) 即可。 有时候不知道啥原因头文件类型变成普通C头文件

游戏行业实战案例 4 :在线时长分析

【面试题】某游戏数据后台设有「登录日志」和「登出日志」两张表。 「登录日志」记录各玩家的登录时间和登录时的角色等级。 「登出日志」记录各玩家的登出时间和登出时的角色等级。 其中&#xff0c;「角色id」字段唯一识别玩家。 游戏开服前两天&#xff08; 2022-08-13 至 …

享元模式 Flyweight Pattern 《游戏编程模式》学习笔记

如果我们要存储一个树一样的数据结构&#xff0c;直觉来说我们会这么写 但是实际上我们会发现&#xff0c;哪怕森林里有千千万万的树&#xff0c;它们大多数长得一模一样。 它们使用了相同的网格和纹理。 这意味着这些树的实例的大部分字段是一样的。 那么我们就可以将树共…

一、Kubernetes介绍与集群架构

Kubernetes介绍与集群架构 一、认识容器编排工具 docker machine 主要用于准备docker host现已弃用建议使用docker desktop docker compose Compose 是一个用于定义和运行多容器 Docker 应用程序的工具。使用 Compose&#xff0c;您可以使用 YAML 文件来配置应用程序的服务。…

Java List(列表)

List 是一个有序、可重复的集合&#xff0c;集合中每个元素都有其对应的顺序索引。List 集合允许使用重复元素&#xff0c;可以通过索引来访问指定位置的集合元素。List 集合默认按元素的添加顺序设置元素的索引&#xff0c;第一个添加到 List 集合中的元素的索引为 0&#xff…

Centos7单机部署ElasticSearch

Centos7单机部署ElasticSearch 引言 Elasticsearch是一种广泛使用的开源搜索引擎&#xff0c;专门为分布式环境设计&#xff0c;但也可以在单机上运行。它使存储、搜索和分析大量数据变得更加容易和高效。此教程将引导你通过在Centos7上单机部署Elasticsearch&#xff0c;涵盖…

Android google admob Timeout for show call succeed 问题解决

项目场景&#xff1a; 项目中需要接入 google admob sdk 实现广告商业化 问题描述 在接入Institial ad 时&#xff0c;onAdLoaded 成功回调&#xff0c;但是onAdFailedToShowFullScreenContent 也回调了错误信息 “Timeout for show call succeed.” InterstitialAd.load(act…

Vue数组变更方法和替换方法

一、可以引起UI界面变化 Vue 将被侦听的数组的变更方法进行了包裹&#xff0c;所以它们也将会触发视图更新。这些被包裹过的方法包括&#xff1a; push()pop()shift()unshift()splice()sort()reverse() 以上七个数组都会改变原数组&#xff0c;下面来分别讲解它们的区别&…

【2023年11月第四版教材】《第2章-信息技术发展(合集篇)》

《第2章-信息技术发展&#xff08;第一部分&#xff09;》 章节说明1 计算机软硬件2 计算机网络2.1 网络的作用范围2.2 OSI模型2.3 广域网协议2.4 网络协议2.5 TCP/IP2.6 软件定义网络&#xff08;SDN&#xff09;2.7 第五代移动通信技术 3 存储和数据库3.1 存储系统架构3.2 存…