数据结构-3.链表

news2024/11/14 15:21:09

前言

本篇博客给大家带来的是链表的知识点, 其中包括面试经常会提问的真题 ArrayList 和 LinkedList 的区别 .

文章专栏: Java-数据结构

若有问题 评论区见

欢迎大家点赞 评论 收藏 分享

如果你不知道分享给谁,那就分享给薯条, 如果分享不成功, 那我就会回你一下,那样你就分享成功啦.

你们的支持是我不断创作的动力 .

1.ArrayList的存在的问题

1.ArrayList底层使用连续的空间, 任意位置插入或者删除元素时, 需要将该位置后面的元素整体往前或往后移动,故时间复杂度为 O(N) .

2.增容需要申请新的空间, 拷贝数据, 释放旧空间 会有不小的消耗 .

3.增容一般是呈1.5倍或2倍增长,势必会有一定的空间浪费. 例如当前容量为100时, 假设呈二倍增长,满了以后增容到200, 我们再继续插入5个数据, 后面没有数据插入的话就浪费了95个数据空间.

如果我们想要达到那种随用随取, 有几个数据我就增几个空间,那我们该怎么办呢? - 就是今天要介绍的链表了

2.链表

2.1链表的概念及结构

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

注意: 

1. 从上图可看出, 链式结构再逻辑上是连续的的,但是在物理上不一定连续.

2. 现实中的结点一般都是从堆上申请出来的

3. 从堆上申请的空间, 是按照一定的策略来分配的, 两次申请的空间可能连续, 也可能不连续

实际中链表的结构非常多样, 一下情况组合起来就有8种链表结构 :

1. 单向或者双向

2.带头或者不带头

3.循环或者非循环

虽然有8种链表的结构, 但是我们重点掌握两种: 

1.无头单向非循环链表: 结构简单, 一般不会单独用来存数据. 实际种更多的是作为其他数据结构的子结构, 如: 哈希桶, 图的邻接表等等. 另外这种结构在笔试 面试中出现很多.

2.无头双向链表: 在Java的集合框架库中LinkedList 底层实现 就是无头双向循环链表.

2.2链表的实现(无头单向非循环链表)

写到这里, 我想起上篇 顺序表的实现 , 我直接把代码一丢 没做任何解释, 会让初学者看得很难受, 所以这次我得改正这个错误, 尽量让大家都能看懂并且会自己写. 其实解释也是很耗费时间的, 作为一名大二的学生😭, 上专业课和看网课就让我的时间所剩无几, 完后还要写blog, 我真是累成狗了🐶. 但是我还是很开心的,  一方面能够给大家带来知识, 另一方面也是在巩固我的知识. 

第一步创建一个类 MySingleList 

 我们都知道节点是链表里面的 , 所以节点就是链表的成员,这没有问题,

那问题来了, 节点中的数据data呢? 它可以定义为链表的成员吗? 

显然是不行的, 因为data是节点里面的, 只需要,在MySingleList类当中再定义一个内部类ListNode就可以解决了.

public class MySingleList {

    //因为节点是链表的一部分,所以可以将其定义为内部类
    static class ListNode {
        public int val;//节点的值域
        public ListNode next;//下一个节点的地址

        public ListNode(int val) {
            this.val = val;
        }
    }
public ListNode head;//表示当前链表的头节点
}

所以有了上述代码. 那么接着我想要实现链表的头插法, 尾插法. 我发现没有链表,

那么就自己手动实现一个简单点的,

// 头插法
public void addFirst ( int data ){
}
// 尾插法
public void addLast ( int data ){
}
public class MySingleList {

    //因为节点是链表的一部分,所以可以将其定义为内部类
    static class ListNode {
        public int val;//节点的值域
        public ListNode next;//下一个节点的地址

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

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);

        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        node4.next = node5;

        this.head = node1;
    }
}
好,  到这里 我们就可以来实现链表的基本操作: 

第二步实现链表的基本操作

遍历单链表:

//遍历单链表
    public void display() {
        //指向头节点的引用不能动,所以利用cur代替
        ListNode cur = this.head;
        while(cur != null) {
            System.out.print(cur.val+" ");
            cur = cur.next;
        }
        System.out.println();
    }

求链表的长度:

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

查找关键字key是否包含在链表中:

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

头插法:

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

尾插法:

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

任意位置插入,第一个数据节点为0号下标: 

//任意位置插入,第一个数据节点为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;
        }
        int count = 0;
        ListNode node = new ListNode(data);
        ListNode cur = this.head;
        while(count != index-1) {
            cur = cur.next;
            count++;
        }
        node.next = cur.next;
        cur.next = node;
    }

清除链表, 直接把head赋值为空即可:

//清除单链表
    public void clear() {
        this.head = null;
    }

删除第一个值为key的节点:

//删除第一个关键字为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("没有找到key");
            return;
        }
        ListNode del = cur.next;
        cur.next = del.next;
    }

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

删除值为key的所有节点:

 //删除所有关键为key的节点
    public void removeAll(int key){
        if(head == null) {
            return;
        }

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

下面是全部代码:

//因为节点是链表的一部分,所以可以将其定义为内部类
    static class ListNode {
        public int val;//节点的值域
        public ListNode next;//下一个节点的地址

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

    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);

        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        node4.next = node5;

        this.head = node1;
    }

    //遍历单链表
    public void display() {
        //指向头节点的引用不能动,所以利用cur代替
        ListNode cur = this.head;
        while(cur != null) {
            System.out.print(cur.val+" ");
            cur = cur.next;
        }
        System.out.println();
    }

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

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

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

        //尾插法
    public void addLast(int data) {
        ListNode node = new ListNode(data);
        ListNode  cur = this.head;
        if(cur == null) {
            head = node;
            return;
        }
        while(cur.next != null) {
            cur = cur.next;
        }
        cur.next = node;
        node.next = null;
    }

        //任意位置插入,第一个数据节点为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;
        }
        int count = 0;
        ListNode node = new ListNode(data);
        ListNode cur = this.head;
        while(count != index-1) {
            cur = cur.next;
            count++;
        }
        node.next = cur.next;
        cur.next = node;
    }

        //删除第一个关键字为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("没有找到key");
            return;
        }
        ListNode del = cur.next;
        cur.next = del.next;
    }

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

    //删除所有关键为key的节点
    public void removeAll(int key){
        if(head == null) {
            return;
        }

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

    //清除单链表
    public void clear() {
        this.head = null;
    }

3.LinkedList的模拟实现(无头双向链表)

无头双向链表跟上述的单向链表的实现大致相同,不过是多了一个前驱,故不做解释

static class ListNode {
        private int val;//数据域
        private ListNode prev;//前驱
        private ListNode next;//后继

        public ListNode(int val) {
            this.val = val;
        }
    }
    public ListNode head;//双向链表的头节点.
    public ListNode last;//双向链表的尾节点.

    //头插法
    public void addFirst(int data){
        ListNode node = new ListNode(data);
        if(head == null) {
            head = node;
            last = node;
        }else{
            head.prev = node;
            node.next = head;
            head = node;
        }
    }

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

    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index,int data){
        ListNode node = new ListNode(data);
        ListNode cur = head;
        if(index == 0) {
            addFirst(data);
            return;
        }
        if(index == size()) {
            addLast(data);
            return;
        }
        if(index != 0 && index != size()) {
            for (int i = 0; i < index-1; i++) {
                cur = cur.next;
            }
            node.next = cur.next;
            cur.next.prev = node;
            cur.next = node;
            node.prev = cur;
        }

    }
//判断Index是否合法
    private void checkIndex(int index) {
        if(index < 0 || index > size()) {
            throw new IndexOutOfException("index 不合法" + 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){
        ListNode cur = head;
        if(head.val == key) {
            head = head.next;
            if(head != null) {
                //考虑只有一个节点的情况下
                head.prev = null;
            }else{
                last = null;
            }
            return;
        }
        if(last.val == key) {
            last = last.prev;
            last.next = null;
            return;
        }
        while(cur != null) {
            if(cur.val == key) {
                cur.prev.next = cur.next;
                cur.next.prev = cur.prev;
                return;
            }
            cur = cur.next;
        }
    }

    //删除所有值为key的节点
    public void removeAllKey(int key){
        ListNode cur = head;
        while(cur != null) {
            if(cur.val == key) {
                if(cur == head) {
                    head = head.next;
                    head.prev = null;
                    cur = cur.next;
                    continue;
                }
                if(cur == last) {
                    last = last.prev;
                    last.next = null;
                    cur = cur.next;
                    continue;
                }
                cur.prev.next = cur.next;
                cur.next.prev = cur.prev;
                cur = cur.next;
            }
            cur = cur.next;
        }
    }

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

    //遍历链表
    public void display(){
        ListNode cur = head;
        for (int i = 0; i < size(); i++) {
            System.out.print(cur.val + " ");
            cur = cur.next;
        }
        System.out.println();
    }

    //清除链表
    public void clear(){
        /*ListNode cur = head;
        while(cur.next != null) {
            cur = cur.next;
            cur.prev.next = null;
            cur.prev = null;
        }
        //while循环走出来有两种情况:
        //1.head.next = null;
        //2.cur走到了尾节点.
        if(head.next == null) {
            head = head.next;
        }else {
            cur.prev = null;
        }
        head = null;
        last = null;*/

        //gaobo写法:
        ListNode cur = head;
        while(cur != null) {
            ListNode curNext = cur.next;
            cur.prev = null;
            cur.next = null;
            cur = curNext;
        }
        head = null;
        last = null;
    }

4.LinkedList的使用

4.1LinkedList的介绍

LinkedList的官方文档:

LinkedList (Java Platform SE 8 ) (oracle.com)

由于链表没有将元素存储在连续的空间中,元素存储在单独的节点中,然后通过引用将节点连接起来了,因此在在任意位置插入或者删除元素时,不需要搬移元素,效率比较高。

说明:

1. LinkedList实现了List接口

2. LinkedList的底层使用了双向链表

3. LinkedList没有实现RandomAccess接口, 因此LinkedList不支持随机访问

4. LinkedList的任意位置插入和删除元素时的效率比较高,时间复杂度为O(1)

5. LinkedList比较适合任意位置插入的场景

4.2LinkedList的使用

1.LinkedList的构造

public class Test3 {
    public static void main(String[] args) {
        //构造一个空的LinkedList
        List<Integer> list1 = new LinkedList<>();

        List<String> list2 = new ArrayList<>();
        list2.add("2334");
        list2.add("ieie");
        list2.add("uuy");

        //使用ArrayList构造LinkedList
        List<String> list3 = new LinkedList<>(list2);
        System.out.println(list3);
    }
}

第二种构造方法与 顺序表中第二种构造方法相似. 这里看不懂的可以去上篇看看顺序表中关于构造方法的讲解.

2.LinekedList的其他常用方法

int indexOf(Object o)    返回第一个o所在下标    
int lastlndexOf(Object 0)    返回最后一个o的下标    
List<E> subList(int fromlndex, int tolndex)    截取部分 list

5.ArrayList和LinkedList的区别

从不同点处记忆即可

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

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

相关文章

Mysql调优之性能监控(一)

前言&#xff1a; 官网就是最好的老师&#xff1a;MySQL&#xff0c;里面各种语法跟参数跟性能调试工具 一、使用show profile查询剖析工具 -- 开启 SET profiling 1; -- 关闭 SET profiling 0; -- 显示查询的性能分析信息 show profiles; -- 显示具体查询id的执行步骤耗时 S…

免费好用的ppt素材库有哪些?这2个在线网站值得推荐!

ppt素材去哪找&#xff1f; 对于很多做PPT的人来说&#xff0c;做PPT的过程中&#xff0c;不是在找素材&#xff0c;就是在去找ppt素材的路上&#xff0c;想寻找到与内容相匹配的ppt素材&#xff0c;往往占用了大量的时间&#xff0c;且ppt和ppt素材库本身是分离的&#xff0c…

超好用!分享测评10款AI论文写作助手自动生成器

在当今学术研究和写作领域&#xff0c;AI论文写作工具的出现极大地提高了写作效率和质量。这些工具不仅能够帮助研究人员快速生成论文草稿&#xff0c;还能进行内容优化、查重和排版等操作。以下是10款超好用的AI论文写作助手自动生成器&#xff0c;其中特别推荐千笔-AIPassPap…

AI带货直播虚拟主播的生成代码!

随着AI技术的飞速发展&#xff0c;AI带货直播虚拟主播已成为电商行业的新宠&#xff0c;这些虚拟主播不仅能24小时不间断地进行直播&#xff0c;还能通过智能互动提升用户体验&#xff0c;为商家带来更多的销售机会&#xff0c;本文将分享五段关键源代码&#xff0c;帮助读者了…

手写Spring第二篇,实现一个超级无敌爆炸简单的Spring工厂

今天开始我要手写一个Spring&#xff0c;就是这么膨胀。小小Spring&#xff0c;拿下&#xff01;注意是小小Spring哈&#xff0c;不是Spring本体&#xff0c;毕竟本体连看懂都难&#xff0c;就别说能充分理解然后手写出来。 本次手写Spring全程参考 第01章&#xff1a;开篇介绍…

运行容器应用

kubernetes通过各种controller来管理pod的生命周期&#xff0c;为了满足不同的业务场景&#xff0c;kubernetes开发了Deployment&#xff0c;ReplicaSet&#xff0c;DaemonSet&#xff0c;StatefulSet&#xff0c;Job等多种ControllerDeployment&#xff1a; kubectl run nginx…

视频去重剪辑软件哪个好用?这3款工具值得一试!

很多人都喜欢在视频平台上分享自己的剪辑作品。随着视频数量的激增&#xff0c;视频去重成为了一个不可忽视的问题。无论是为了遵守版权法规&#xff0c;还是为了提升内容的独特性和吸引力&#xff0c;选择一款好用的视频去重剪辑软件都显得尤为重要。本文将推荐几款优秀的视频…

硬件工程师笔试面试——晶振

目录 13、晶振 13.1 基础 晶振原理图 晶振实物图 13.1.1 概念 13.1.2 工作原理 13.1.3 应用领域 13.1.4 产品类型 13.2 相关问题 13.2.1 晶振的工作原理是什么,它如何保证频率的稳定性? 13.2.2 在工业控制领域,晶振是如何确保精确度的? 13.2.3 晶振的Q值是如何…

pc端的屏保实现

背景 偶然间&#xff0c;在使用一款google插件的时候&#xff0c;发现它有一个小功能&#xff0c;只要我停留在它的页面不操作10分钟以上&#xff0c;就会自动给我打开一个屏保界面&#xff0c;这样的 目的 这种华而不实的功能&#xff0c;正好适合个人博客&#xff0c;所以…

STM32MP157/linux驱动学习记录(二)

38.Linux INPUT 子系统实验 按键、鼠标、键盘、触摸屏等都属于输入(input)设备&#xff0c;Linux 内核为此专门做了一个叫做 input子系统的框架来处理输入事件。输入设备本质上还是字符设备&#xff0c;只是在此基础上套上了 input 框架&#xff0c;用户只需要负责上报输入事件…

健身器材识别系统源码分享

健身器材识别检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

2024年【电气试验】试题及解析及电气试验模拟考试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年【电气试验】试题及解析及电气试验模拟考试题&#xff0c;包含电气试验试题及解析答案和解析及电气试验模拟考试题练习。安全生产模拟考试一点通结合国家电气试验考试最新大纲及电气试验考试真题汇总&#xff0…

数据结构之‘栈’

文章目录 1.简介2. 栈的初始化和销毁3. 进栈和出栈3.1 进栈3.2 出栈3.3 栈的打印 1.简介 一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行&#xff08;数据插入和删除操作&#xff09;的一端称为栈顶&#xff0c;另一端称为栈底。压栈&#xf…

C++中的const \static \this

目录 前言 一、const关键字 1、const修饰类的成员变量 2、const修饰类的成员函数 3、const修饰类的对象 二、static关键字 1、static修饰类中的成员变量 1. 共享性 2. 初始化 3. 访问权限 4. 内存分配 5. 不依赖于对象 2、static修饰类中的成员函数 三、this关键字…

OpenHarmony(鸿蒙南向开发)——标准系统方案之扬帆移植案例

往期知识点记录&#xff1a; 鸿蒙&#xff08;HarmonyOS&#xff09;应用层开发&#xff08;北向&#xff09;知识点汇总 鸿蒙&#xff08;OpenHarmony&#xff09;南向开发保姆级知识点汇总~ OpenHarmony&#xff08;鸿蒙南向开发&#xff09;——轻量系统STM32F407芯片移植案…

SHL笔试测评系统题库考什么?如何通过综合测评|附性格测试104道

嘿&#xff0c;各位求职小伙伴们&#xff01;我是职小豚&#xff0c;今天就来带大家深入了解神秘又充满挑战的 SHL 笔试测评系统。 一、SHL 人才测评系统介绍 SHL 呀&#xff0c;那可是人才测评领域的超级大明星&#xff01;就像一个智慧的魔法师&#xff0c;用各种神奇的题目…

Linux系统之head命令的基本使用

Linux系统之head命令的基本使用 一、head命令介绍二、head命令的使用帮助2.1 head命令的help帮助信息2.2 head命令的语法解释 三、head的基本使用3.1 直接使用3.2 查看文件前N行3.3 查看多个文件3.4 查询文件的前5行3.5 显示文本所有内容&#xff08;除了后5行内容&#xff09;…

实战讲稿:Spring Boot整合MyBatis

文章目录 实战讲稿&#xff1a;Spring Boot整合MyBatis课程目标课程内容1. 创建员工映射器接口1.1 创建子包1.2 创建接口 2. 测试员工映射器接口2.1 自动装配员工映射器2.2 测试按标识符查询员工方法2.3 测试查询全部员工方法2.4 测试插入员工方法2.5 测试更新员工方法2.6 测试…

No module named MYSQLdb 问题解决

问题&#xff1a; 导入写好的数据库时报错 解决&#xff1a;pip install mysql-python &#xff08;又报错&#xff09; 找了网上的方法&#xff1a; 执行 pip install PyMySQL&#xff0c;将数据库连接改为 mysqlpymysql://username:passwordserver/db&#xff0c;接下来的操…

eggtart队比赛攻略

关联比赛: “新内容 新交互”全球视频云创新挑战赛--算法挑战赛道 赛题回顾 本次赛题核心为高清视频人像分割&#xff0c;属于无监督视频物体分割任务&#xff0c;要求在未提供任何额外输入的情况下&#xff0c;识别并定位视频中的主要人物&#xff0c;并精确到图像的每个像素…