数据结构 之 链表LinkedList

目录

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置为空即可
    }
}

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

制作不易,三连支持

谢谢!!!

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

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

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

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

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

相关文章

专业138+总分400+南航南京航空航天大学878考研经验电子信息与通信工程,真题,大纲,参考书

经过一年的复习&#xff0c;顺利被南京航空航天大学录取&#xff0c;初试专业课878数字电路和信号与系统138&#xff0c;总分400&#xff0c;回看这一年的复习&#xff0c;从择校到考研备考经历了很多&#xff0c;也有很多想和大家分享的复习经验&#xff0c;希望对大家复习有所…

MateBook X Pro 2019款 集显(MACHR-WX9)工厂模式原装出厂Win10系统 带F10智能还原

HUAWEI华为MateBook X笔记本电脑原厂Windows10系统恢复工厂包 适用型号&#xff1a;MACHR-WX9、MACHR-W29、MACHR-W19 链接&#xff1a;https://pan.baidu.com/s/1x6vvCxmEgM2Oa_Uom8r9Iw?pwd588m 提取码&#xff1a;588m 系统自带F10一键智能还原功能、自带所有驱动、系统…

【C++】反向迭代器仿函数模板进阶

反向迭代器&仿函数&模板进阶 一&#xff0c;反向迭代器1. 什么是反向迭代器2. 模拟实现3. 如何使用 二&#xff0c;仿函数1. 仿函数的概念2. 仿函数的用法 三&#xff0c;模板1. 非类型模板参数2. 模板的特化2.1 特化概念2.2 函数模板特化2.3 类模板特化2.3.1 全特化2.…

【Java设计模式】十五、命令模式

文章目录 1、命令模式2、案例3、总结 1、命令模式 餐厅点餐&#xff1a; 创建一个厨师对象&#xff0c;让服务员对象调用厨师对象中的方法进行点餐通知&#xff0c;当后面厨师换人&#xff0c;服务员类的代码也要修改&#xff0c;耦合 不符合开闭。理想状态&#xff1a;服务员…

java SSM农产品订购网站系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java SSM农产品订购网站系统是一套完善的web设计系统&#xff08;系统采用SSM框架进行设计开发&#xff0c;springspringMVCmybatis&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采…

Asp .Net Web Forms 系列:配置图片防盗链的几种方法

通过 URL Rewrite Module 组件 URL Rewrite Module 是一个用于在 ASP.NET Web Forms 或其他基于 IIS 的 Web 应用程序中重写 URL 的强大工具。这个模块允许你将复杂的、不易于记忆或不利于搜索引擎优化的 URL 转换为更简洁、更友好的格式。通过 URL 重写&#xff0c;你可以提高…

Opencv 插值方法 总结

一、概括 面试的时候问到了一个图&#xff0c;就是如何将一个算子放缩&#xff1f;&#xff1f;我第一反应是resize&#xff08;&#xff09;,但是后来我转念一想&#xff0c;人家问的是插值方式&#xff0c;今天来总结一下 最邻近插值法原理分析及c实现_最临近插值法-CSDN博…

050-WEB攻防-PHP应用文件包含LFIRFI伪协议编码算法无文件利用黑白盒

050-WEB攻防-PHP应用&文件包含&LFI&RFI&伪协议编码算法&无文件利用&黑白盒 #知识点&#xff1a; 1、文件包含-原理&分类&危害-LFI&RFI 2、文件包含-利用-黑白盒&无文件&伪协议 演示案例&#xff1a; ➢文件包含-原理&分类&am…

解决达梦集成 JPA 时表和字段注释注解不生效的问题

前言 最近在做达梦数据库集成 JPA 时&#xff0c;发现使用的表注解和字段注解均未生效&#xff08;MySQL、Oracle、PostgreSQL中均可以在建表时正常生成相应的注释&#xff09;&#xff0c;经过调试发现解决办法也很简单&#xff1a; 自定义方言类继承自org.hibernate.dialect…

vue3 动态路由及使用动态路由后刷新界面出现空白页或者404

最近编写vue3动态路由的功能遇到了一些问题&#xff0c;处理好了&#xff0c;总结出来&#xff0c;希望能帮助到你。正片开始 先写好本地缓存菜单的方法&#xff08;存储、删除、获取&#xff09; // utils/menu.jsconst getMenuList () > {return JSON.parse(localStorag…

C语言——简易版扫雷

目录 前言 ​编辑 游戏规则 游戏结构的分析 游戏的设计 使用多文件的好处有以下几点&#xff1a; 游戏代码实现 框架&#xff08;test.c&#xff09; game函数&#xff08;test.c&#xff09; InitBoard初始化&#xff08;game.c&#xff09; Print打印棋盘&#xff08;g…

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

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

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

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

搭建mysql主从复制(主主复制)

1&#xff1a;设主库允许远程连接(注意&#xff1a;设置账号密码必须使用的插件是mysql_native_password&#xff0c;其他的会连接失败) #切换到mysql这个数据库&#xff0c;修改user表中的host&#xff0c;使其可以实现远程连接 mysql>use mysql; mysql>update user se…

使用C#创建服务端Web API

前言 C# Web API 是一种基于 .NET 平台&#xff08;包括但不限于.NET Framework 和 .NET Core&#xff09;构建 HTTP 服务的框架&#xff0c;用于创建 RESTful Web 服务。REST&#xff08;Representational State Transfer&#xff09;是一种软件架构风格&#xff0c;它利用HT…

linux中查看目录文件(ls)用法:

查看目录下的文件&#xff1a;ls&#xff08;list&#xff09; 作用 查看目录下的内容 格式 ls -参数 操作对象参数 参数功能-l以长格形式显示文件和目录的详细信息,ls命令默认只显示名称的短格式。-d显示指定目录本身的信息,而不显示目录下的各个文件和子目录的信息。-…

【机器学习】机器学习是什么?用在哪里?怎么用?

1.机器学习是什么&#xff1f; 机器学习&#xff08;Machine Learning&#xff09;是人工智能的一个分支&#xff0c;它是一种通过对数据进行训练和学习&#xff0c;让计算机系统从中获取知识并改善性能的方法。简而言之&#xff0c;机器学习使计算机具有从数据中学习并自动改…

信息系统项目管理师009:消费互联网(1信息化发展—1.3现代化创新发展—1.3.3 消费互联网)

文章目录 1.3.3 消费互联网1.基本属性2.应用新格局 1.3.3 消费互联网 消费互联网是以个人为用户&#xff0c;以日常生活为应用场景的应用形式&#xff0c;满足消费者在互联网中的消费需求而生的互联网类型。消费互联网以消费者为服务中心&#xff0c;针对个人用户提升消费过程的…

机器学习模型—支持向量机 (SVM)

机器学习模型—支持向量机 (SVM) 支持向量机 (SVM) 是一种强大的机器学习算法,用于线性或非线性分类、回归,甚至异常值检测任务。SVM 可用于各种任务,例如文本分类、图像分类、垃圾邮件检测、笔迹识别、基因表达分析、人脸检测和异常检测。SVM 在各种应用中具有适应性和高效…

Github上哪些好用的工具

专注于web漏洞挖掘、内网渗透、免杀和代码审计&#xff0c;感谢各位师傅的关注&#xff01;网安之路漫长&#xff0c;与君共勉&#xff01; Qexo-爱写博客的师傅强烈推荐 漂亮的 Hexo 静态博客编辑器。该项目是基于 Django 的 Hexo 静态博客管理后台&#xff0c;支持文章管理、…