数据结构 之 链表LinkedList

news2024/10/5 19:09:25

目录

1. ArrayList的缺陷:

2. 链表:

2.1 链表的概念及结构:

 3. 链表的使用和模拟实现:

3.1 构造方法:

3.2 模拟实现:

4. 源码分享:


在我学习顺序表之后,我就立马开始了链表的学习,但是在学习链表之前,我就有一个疑问,为什么明明有了顺序表这一种数据结构为什么我们还要有链表这一种数据结构呢?

1. ArrayList的缺陷:

通过对ArrayList的简单了解,我们知道,其实顺序表的底层是由数组来实现的,他是一段连续的空间,所以,当ArrayList在增删元素的时候,通过计算我们发现,他的时间复杂度为O(n),效率比较低下,如果数据很大的情况下,使用顺序表进行增删操作,会浪费非常多的时间,所以,就引入了链表这一种数据结构!!!

2. 链表:

2.1 链表的概念及结构:

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

在现实生活中,火车的结构就类似于我们的链表

链表的结构多种多样:

1. 单向或者双向:

2. 带头或者不带头:

3.循环或者不循环:

以上就是链表的结构,所以一共有八种链表:

单向不带头不循环链表;

单向带头不循环链表;

单向不带头循环链表;

单向带头循环链表;

双向不带头不循环链表;

双向带头不循环链表;

双向不带头循环链表;

双向带头循环链表;

 3. 链表的使用和模拟实现:

3.1 构造方法:

链表源码提供了两个构造方法:

这是不带参数的构造方法;

这是带一个参数的构造方法,将c中的全部元素都拷贝到链表中;

3.2 模拟实现:

首先,我们需要创建一个My_LinkedList类:(我们以单向不带头不循环链表为例)

在这个类中创建一个静态内部类,称为ListNode,一共有两个成员,一个是value用来存放该节点的值的,一个是next,用来存放下一个节点的地址,同时我们还可以写一个构造方法;

再实例化一个头节点

public class My_LinkedList {
    static class ListNode {
        public int val;//数值域
        public ListNode next;//存储下一个节点的地址

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

    public ListNode head = new ListNode(0);   //实例化头节点

    public My_LinkedList(int val) {
        head.val = val;                       //构造方法
    }
}

< 1 > addFirst方法:

头插法,在链表的第一个节点插入新的节点:

假如我们在如图所示的链表中头插一个node节点

为了防止我们的首节点丢失,我们需要先将首节点的地址传给新的节点,再将新节点更新为head

/**
     * 头插法
     * addFrist
     */
    public void addFirst(int data) {
        ListNode node = new ListNode(data);     //实例化一个节点node
        /*if(this.head == null) {
            this.head = node;
        }else {
            node.next = this.head;
            this.head = node;
        }*/
        node.next = this.head;              //将首节点的地址赋给新的节点
        this.head = node;                   //将新的节点更新为head
    }

< 2 > addLast方法:

尾插法,将新节点插入链表末端:

public void addLast(int data) {
        ListNode node = new ListNode(data);     //实例化一个node节点
        if (head == null) {
            head = node;                        //若链表为空,则直接将node更新为head
        } else {
            ListNode cur = head;                //实例化一个cur节点
            while (cur.next != null) {          //用cur节点去遍历链表,知道cur节点为最后一个节点
                cur = cur.next;                 
            }
            //cur.next == null;
            cur.next = node;                    //将node赋值给cur.next,也就是将node节点放在了链表的最末端
        }
    }

< 3 > size方法:

得到并返回单链表的长度:

//得到单链表的长度
    public int size() {
        int count = 0;              //创建整形变量count作为计数器
        ListNode cur = this.head;   //实例化cur = 当前的头节点
        while (cur != null) {       //遍历整个链表
            count++;                //每遍历一个节点,则count++
            cur = cur.next;         //cur一直向后移动
        }
        return count;               //返回单链表的长度
    }

< 4 > add方法:

任意位置插入,将节点插入指定位置:

在插入之前,我先要检测给定的节点位置是否合理:

private void checkIndexAdd(int index) {
        if (index < 0 || index > size()) {          //如果位置不合理
            throw new MySingleListIndexOutOfException("任意位置插入的时候,index不合法!");
        }           //若不合理,则抛出异常
    }
MySingleListIndexOutOfException :
public class MySingleListIndexOutOfException extends RuntimeException{
    public MySingleListIndexOutOfException() {
        super();
    }

    public MySingleListIndexOutOfException(String str) {
        super(str);
    }
}
public void add(int index, int data) throws MySingleListIndexOutOfException {
        checkIndexAdd(index);                   //检查index位置是否合法
        if (index == 0) {                       //如果index为0
            addFirst(data);                     //进行头插法
            return;
        }
        if (index == size()) {                  //如果index = 单链表长度
            addLast(data);                      //进行尾插法
            return;
        }
        ListNode node = new ListNode(data);     //实例化一个node节点,值为data
        ListNode cur = findIndexSubOne(index);  //找到index下标的前一个节点
        node.next = cur.next;                   //为了防止节点丢失,先将要插入节点的next更新为前一个节点的原来的下一个节点的地址
        cur.next = node;                        //将cur的next更新为新的节点的地址
    }
private ListNode findIndexSubOne(int index) {
        ListNode cur = this.head;           //实例化一个cur = 头节点
        while (index - 1 != 0) {            //向后遍历index - 1次,找到index下标节点的前一个节点
            cur = cur.next;
            index--;
        }
        return cur;                         //返回当前节点 
    }

< 5 > contains方法:

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

//查找是否包含关键字key是否在单链表当中
    public boolean contains(int key) {
        if (head == null) {                 //如果head为空,则直接返回false
            return false;
        }
        ListNode cur = this.head;           //实例化一个cur节点 = 头节点
        //cur != null 说明 没有遍历完 链表
        while (cur != null) {               //cur不停向后遍历
            if (cur.val == key) {           //找到了key
                return true;                //返回true
            }
            cur = cur.next;
        }
        return false;                       //循环结束,cur.next = null,说明没有找到key,返回false
    }

< 6 > remove方法:

删除第一次出现的值为key的节点:

//删除第一次出现关键字为key的节点
    public void remove(int key) {
        if (this.head == null) {                            //如果链表为空,则没有元素,无法删除
            System.out.println("此时链表为空,不能进行删除!");
            return;
        }
        if (this.head.val == key) {
            //判断 第一个节点是不是等于我要删除的节点this.head = this.head.next;
            return;
        }
        ListNode cur = this.head;           //实例化一个cur = head
        while (cur.next != null) {          //遍历整个链表,直到cur为最后一个节点为止
            if (cur.next.val == key) {      //如果找到了值为key的节点
                //进行删除了
                ListNode del = cur.next;    //实例化del节点为cur节点的下一个节点
                cur.next = del.next;        //将前一个节点的next更新为后一个节点的地址
                return;
            }
            cur = cur.next;
        }
        System.out.println("未找到值为key的节点");      //跳出循环,说明没有值为key的节点
    }

< 7 > removeAll方法:

//删除所有值为key的节点
    public void removeAllKey(int key) {
        if (this.head == null) {                    //如果head == null,则链表为空,不能进行删除操作
            return;
        }
        //单独处理了头节点,若头节点的值为key,则头节点向后移动
        if(this.head.val == key) {
            head = head.next;
        }
        ListNode cur = this.head.next;              //实例化cur节点 == head节点的下一个节点
        ListNode prev = this.head;                  //实例化prev节点 == head节点
        while (cur != null) {                       //cur节点不断向后遍历
            if (cur.val == key) {                   //如果cur.val == key即找到了值为key的节点
                prev.next = cur.next;               //将prev节点即cur的前一个节点的next = cur.next, 即删除了cur位置的节点
                cur = cur.next;                     //cur继续向后走,查找值为key的节点
            } else {                                //若cur.val != key
                prev = cur;                         //prev和cur一起向后移动                
                cur = cur.next;
            }
        }
    }

< 8 > clear方法:

clear方法的实现有两种方法:

第一种:

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

这种做法比较粗暴,直接将head置为空,由于之前的链表中的节点没有了引用,故会被系统给自动回收;

第二种:

/**
     * 当我们 执行clear这个函数的时候,会将这个链表当中的所有的节点回收
     */
    public void clear() {
        ListNode cur = this.head;           //令cur = head
        ListNode curNext = null;            //实例化一个curNext
        while (cur != null) {               
            curNext = cur.next;             //将curNext更新为cur.next
            cur.next = null;                //再将cur节点的next置为空
            cur = curNext;                  //再将cur更新为curNext,一个一个的去删除链表中的所有的节点
        }
        head = null;                        //最后将head置为空即可
    }

4. 源码分享:

在我的模拟实现源码中,我多写了createList方法和display方法,即创建链表和打印链表方法,为的是模拟实现后方便进行测试,以找出代码的不足!!!

public class My_LinkedList {
    static class ListNode {
        public int val;//数值域
        public ListNode next;//存储下一个节点的地址

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

    public ListNode head;//代表单链表的头结点的引用

    /**
     * 这里只是简单的进行,链表的构造。
     */
    public void createList() {
        ListNode listNode1 = new ListNode(12);
        ListNode listNode2 = new ListNode(23);
        ListNode listNode3 = new ListNode(34);
        ListNode listNode4 = new ListNode(45);
        ListNode listNode5 = new ListNode(56);

        listNode1.next = listNode2;

        listNode2.next = listNode3;
        listNode3.next = listNode4;
        listNode4.next = listNode5;
        this.head = listNode1;
    }

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

    /**
     * 从指定的节点开始答应
     *
     * @param newHead
     */
    public void display(ListNode newHead) {
        ListNode cur = newHead;
        while (cur != null) {
            System.out.print(cur.val + " ");
            cur = cur.next;
        }
        System.out.println();
    }

    /**
     * 头插法
     * addFrist
     */
    public void addFirst(int data) {
        ListNode node = new ListNode(data);     //实例化一个节点node
        /*if(this.head == null) {
            this.head = node;
        }else {
            node.next = this.head;
            this.head = node;
        }*/
        node.next = this.head;              //将首节点的地址赋给新的节点
        this.head = node;                   //将新的节点更新为head
    }

    //尾插法 O(n)
    public void addLast(int data) {
        ListNode node = new ListNode(data);     //实例化一个node节点
        if (head == null) {
            head = node;                        //若链表为空,则直接将node更新为head
        } else {
            ListNode cur = head;                //实例化一个cur节点
            while (cur.next != null) {          //用cur节点去遍历链表,知道cur节点为最后一个节点
                cur = cur.next;
            }
            //cur.next == null;
            cur.next = node;                    //将node赋值给cur.next,也就是将node节点放在了链表的最末端
        }
    }

    public void add(int index, int data) throws MySingleListIndexOutOfException {
        checkIndexAdd(index);                   //检查index位置是否合法
        if (index == 0) {                       //如果index为0
            addFirst(data);                     //进行头插法
            return;
        }
        if (index == size()) {                  //如果index = 单链表长度
            addLast(data);                      //进行尾插法
            return;
        }
        ListNode node = new ListNode(data);     //实例化一个node节点,值为data
        ListNode cur = findIndexSubOne(index);  //找到index下标的前一个节点
        node.next = cur.next;                   //为了防止节点丢失,先将要插入节点的next更新为前一个节点的原来的下一个节点的地址
        cur.next = node;                        //将cur的next更新为新的节点的地址
    }

    /**
     * 找到index-1位置的节点
     *
     * @param index
     * @return 该节点的地址
     */
    private ListNode findIndexSubOne(int index) {
        ListNode cur = this.head;           //实例化一个cur = 头节点
        while (index - 1 != 0) {            //向后遍历index - 1次,找到index下标节点的前一个节点
            cur = cur.next;
            index--;
        }
        return cur;                         //返回当前节点
    }

    private void checkIndexAdd(int index) {
        if (index < 0 || index > size()) {          //如果位置不合理
            throw new MySingleListIndexOutOfException("任意位置插入的时候,index不合法!");
        }           //若不合理,则抛出异常
    }

    //查找是否包含关键字key是否在单链表当中
    public boolean contains(int key) {
        if (head == null) {                 //如果head为空,则直接返回false
            return false;
        }
        ListNode cur = this.head;           //实例化一个cur节点 = 头节点
        //cur != null 说明 没有遍历完 链表
        while (cur != null) {               //cur不停向后遍历
            if (cur.val == key) {           //找到了key
                return true;                //返回true
            }
            cur = cur.next;
        }
        return false;                       //循环结束,cur.next = null,说明没有找到key,返回false
    }

    //删除第一次出现关键字为key的节点
    public void remove(int key) {
        if (this.head == null) {                            //如果链表为空,则没有元素,无法删除
            System.out.println("此时链表为空,不能进行删除!");
            return;
        }
        if (this.head.val == key) {
            //判断 第一个节点是不是等于我要删除的节点this.head = this.head.next;
            return;
        }
        ListNode cur = this.head;           //实例化一个cur = head
        while (cur.next != null) {          //遍历整个链表,直到cur为最后一个节点为止
            if (cur.next.val == key) {      //如果找到了值为key的节点
                //进行删除了
                ListNode del = cur.next;    //实例化del节点为cur节点的下一个节点
                cur.next = del.next;        //将前一个节点的next更新为后一个节点的地址
                return;
            }
            cur = cur.next;
        }
        System.out.println("未找到值为key的节点");      //跳出循环,说明没有值为key的节点
    }

    //删除所有值为key的节点
    public void removeAllKey(int key) {
        if (this.head == null) {                    //如果head == null,则链表为空,不能进行删除操作
            return;
        }
        //单独处理了头节点,若头节点的值为key,则头节点向后移动
        if(this.head.val == key) {
            head = head.next;
        }
        ListNode cur = this.head.next;              //实例化cur节点 == head节点的下一个节点
        ListNode prev = this.head;                  //实例化prev节点 == head节点
        while (cur != null) {                       //cur节点不断向后遍历
            if (cur.val == key) {                   //如果cur.val == key即找到了值为key的节点
                prev.next = cur.next;               //将prev节点即cur的前一个节点的next = cur.next, 即删除了cur位置的节点
                cur = cur.next;                     //cur继续向后走,查找值为key的节点
            } else {                                //若cur.val != key
                prev = cur;                         //prev和cur一起向后移动
                cur = cur.next;
            }
        }
    }

    //得到单链表的长度
    public int size() {
        int count = 0;              //创建整形变量count作为计数器
        ListNode cur = this.head;   //实例化cur = 当前的头节点
        while (cur != null) {       //遍历整个链表
            count++;                //每遍历一个节点,则count++
            cur = cur.next;         //cur一直向后移动
        }
        return count;               //返回单链表的长度
    }

    /**
     * 当我们 执行clear这个函数的时候,会将这个链表当中的所有的节点回收
     */
    public void clear() {
        //this.head = null;//这种做法 比较粗暴!
        ListNode cur = this.head;           //令cur = head
        ListNode curNext = null;            //实例化一个curNext
        while (cur != null) {
            curNext = cur.next;             //将curNext更新为cur.next
            cur.next = null;                //再将cur节点的next置为空
            cur = curNext;                  //再将cur更新为curNext,一个一个的去删除链表中的所有的节点
        }
        head = null;                        //最后将head置为空即可
    }
}

以上就是链表的所有的内容了,感谢大家的观看!!!

制作不易,三连支持

谢谢!!!

以上的模拟实现代码未必是最优解,仅代表本人的思路,望多多理解,谢谢!!

最后送给大家一句话,同时也是对我自己的勉励:

在心里种花,人生才不会荒芜!!!!

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

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

相关文章

idea 导入项目

idea 导入项目并运行 导入设置设置 jdk查看maven 设置 导入 在项目首页 或者 file 选择 open, 然后选择项目根路径 设置 设置 jdk 查看maven 设置

Vue 中的 key:列表渲染的秘诀

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

基于eleiment-plus的表格select控件

控件不是我写的&#xff0c;来源于scui,但在使用中遇到了一些问题&#xff0c;希望能把过程记录下来&#xff0c;同时把这个问题修复掉。 在使用的时候对控件进行二级封装&#xff0c;比如我的一个商品组件&#xff0c;再很多地方可以用到&#xff0c;于是 <template>&l…

SQLiteC/C++接口详细介绍-sqlite3类(一)

上一篇&#xff1a;SQLiteC/C接口简介 下一篇&#xff1a;SQLiteC/C接口详细介绍&#xff08;二&#xff09; 引言&#xff1a; SQLite C/C 数据库接口是一个流行的SQLite库使用形式&#xff0c;它允许开发者在C和C代码中嵌入 SQLite 基本功能的解决方案。通过 SQLite C/C 数据…

ElementUI两个小坑

1.form表单绑定的是一个对象&#xff0c;表单里的一个输入项是对象的一个属性之一&#xff0c;修改输入项&#xff0c;表单没刷新的问题&#xff0c; <el-form :model"formData" :rules"rules" ref"editForm" class"demo-ruleForm"…

MyFileServer3

信息收集 # nmap -sn 192.168.101.0/24 -oN live.nmap Starting Nmap 7.94 ( https://nmap.org ) at 2024-02-22 19:14 CST Nmap scan report for 192.168.101.1 Host is up (0.00050s latency). MAC Address: 00:50:56:C0:00:08 (VMware) Nmap scan report fo…

R语言tidycmprsk包分析竞争风险模型

竞争风险模型就是指在临床事件中出现和它竞争的结局事件&#xff0c;这是事件会导致原有结局的改变&#xff0c;因此叫做竞争风险模型。比如我们想观察患者肿瘤的复发情况&#xff0c;但是患者在观察期突然车祸死亡&#xff0c;或者因其他疾病死亡&#xff0c;这样我们就观察不…

GO语言-切片底层探索(上)

目录 1.前言 2. 算法题目 错误代码 3. 错误分析 4.总结&#xff1a; 5.正确代码&#xff1a; 6.本地测试代码&#xff1a; 1.前言 今天在力扣上写算法&#xff0c;遇到了一个比较"奇怪"的错误。由于自己使用了递归切片&#xff0c;导致一开始没有看明白&…

vue学习笔记23-组件事件⭐

组件事件 在组件的模板表达式中&#xff0c;可以直接使用$emit方法触发自定义事件&#xff1b;触发自定义事件的目的是组件之间传递数据 好好好今天又碰到问题了&#xff0c;来吧来吧 测试发现其他项目都可以 正常的run ,就它不行 搜索发现新建项目并进入以后&#xff0c;用指…

【刷题日志3.4--3.10】

绕过flag关键字od读取&#xff08;脚本&#xff09;空格过滤 [广东强网杯 2021 团队组]love_Pokemon <?php error_reporting(0); highlight_file(__FILE__); $dir sandbox/ . md5($_SERVER[REMOTE_ADDR]) . /;if(!file_exists($dir)){mkdir($dir); }function DefenderBon…

(vb-asp.net)lw-学生信息管理系统(学生成绩管理,补考考场分配)-158-(代码+说明)

转载地址: http://www.3q2008.com/soft/search.asp?keywordasp.net&#xff09;lw 非常不错! 有兴趣的可以咨询客服, 或下载演示查看 目 录 ABSTRACT 3 1&#xff0e; 系统规划 6 1&#xff0e;3 需求分析 6 1&#xff0e;3&#xff0e;1 功能需求 6 通过了解学生管理系统…

HTTP/2、HTTP/3对HTTP/1.1的性能改进和优化

HTTP/1.1 相比 HTTP/1.0 提高了什么性能&#xff1f; 性能上的改进&#xff1a; 使用长连接的方式改善了 HTTP/1.0 短连接造成的性能开销。 支持管道&#xff08;pipeline&#xff09;网络传输&#xff0c;只要第一个请求发出去了&#xff0c;不必等其回来&#xff0c;就可以…

C++14之std::index_sequence和std::make_index_sequence

相关文章系列 std::apply源码分析 C之std::tuple(一) : 使用精讲(全) 目录 1.std::integer_sequence 2.std::index_sequence 3.std::make_index_sequence 4.运用 4.1.打印序列的值 4.2.编译时求值 4.3.std::tuple访问值 5.总结 1.std::integer_sequence 运行时定义一个…

OSI七层模型TCP四层模型横向对比

OSI 理论模型&#xff08;Open Systems Interconnection Model&#xff09;和TCP/IP模型 七层每一层对应英文 应用层&#xff08;Application Layer&#xff09; 表示层&#xff08;Presentation Layer&#xff09; 会话层&#xff08;Session Layer&#xff09; 传输层&#x…

【JavaScript】面试手撕深拷贝

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 深拷贝的作用深浅拷贝的区别浅拷贝深拷贝 深拷贝实现方式JSON.parse(JSON.stringi…

开发知识点-python-Tornado框架

介绍 Tornado是一个基于Python语言的高性能Web框架和异步网络库&#xff0c;它专注于提供快速、可扩展和易于使用的网络服务。由于其出色的性能和灵活的设计&#xff0c;Tornado被广泛用于构建高性能的Web应用程序、实时Web服务、长连接的实时通信以及网络爬虫等领域。 Torna…

【物联网设备端开发】FastBee Arduino固件开发指南

目录 一、收集数据 二、打开FastBeeArduino 源码 三、修改 Config.cpp 文件 四、修改物模型数据 五、小程序配网 本文以 WeMOS D1 R1&#xff08;8266WIFI 模块&#xff09;固件开发为例&#xff0c;实现以下功能&#xff1a; 设备认证设备 Mqtt 交互Wifi 类设备配网 一…

ffmpeg解码和渲染理解

ffmpeg解码和渲染理解 ffmpeg视频解码步骤 FFmpeg 是一个功能强大的跨平台多媒体处理工具&#xff0c;包含了音视频编解码、封装/解封装、过滤器等功能。下面是一般情况下使用 FFmpeg 进行视频解码的步骤&#xff1a; 初始化 FFmpeg 库&#xff1a;首先需要初始化 FFmpeg 库&a…

SAM(Segment Anything Model)大模型使用--point prompt

概述 本系列将做一个专题&#xff0c;主要关于介绍如何在代码上运行并使用SAM模型以及如何用自己的数据集微调SAM模型&#xff0c;也是本人的毕设内容&#xff0c;这是一个持续更新系列&#xff0c;欢迎大家关注~ SAM&#xff08;Segment Anything Model&#xff09; SAM基于…

自然语言处理的概念及发展介绍

自然语言处理&#xff08;Natural Language Processing&#xff0c;NLP&#xff09;是计算机科学、人工智能和语言学的交叉领域&#xff0c;旨在使计算机能够理解、解释和生成人类语言。自然语言处理的发展对于实现人机交互、信息检索、机器翻译、情感分析等应用至关重要。 概念…