LinkedList与链表(数据结构系列5)

news2024/9/20 12:35:14

目录

前言:

1.链表的概念以及分类

1.1链表的概念

1.2分类

1.2.1单向和双向

1.2.2循环和非循环

1.2.3带头和不带头

2.无头单链表的模拟实现

3.双向链表的模拟实现

4.LinkedList的简单介绍

5.LinkedList的遍历

5.1直接打印

5.2for-each遍历

5.3迭代器遍历

6.LinkedList与ArrayList的区别

结束语:


前言:

我们之前学习了线性结构中的顺序存储,那么在之前也与大家一起实现了ArrayList的基本的方法,我们会发现这种结构的优点是当我们在给定下标查询的时候速度会非常的快,时间复杂度可以达到O(1),但是当我们在插入元素、删除元素和扩容的时候你会发现时间会很慢,我们必须通过挪动元素才可以达到我们想要的效果,在扩容的时候也会非常的浪费空间,所以这种顺序表适合随机查找某个元素,那么为了解决我们上述的一些问题,接下来我们就需要学习另一种线性结构——链式结构。

1.链表的概念以及分类

1.1链表的概念

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

一个结点存在两个域:数据域地址域

数据域用来存储当前结点的数据,地址域用来存储下一个结点的地址,这样我们就可以实现逻辑上连续了。

1.2分类

链表的分类一共有八种:带头和不带头、循环和非循环、单向和双向,那么接下来我们来分别看一下都是怎么构成的。

1.2.1单向和双向

单向:

双向:

  

1.2.2循环和非循环

非循环:

循环:

1.2.3带头和不带头

带头:


不带头: 

上面的这四类八种组合中接下来我们主要学习就是单向不带头非循环双向不带头非循环。 

2.无头单链表的模拟实现

主要核心代码如下所示:

public class MySingleList {
    class Node{
        public int val;//存储数据
        public Node next;//存储下一个结点的地址

        public Node(int val) {
            this.val = val;
        }
    }
    public Node head = null;//头结点
    //接下来我们直接手动创建一个链表
    public void createList() {
        Node node1 = new Node(1);
        Node node2 = new Node(2);
        Node node3 = new Node(3);
        Node node4 = new Node(4);
        //关联上面的四个结点
        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        //让头结点引用结点
        head = node1;
    }

    /**
    下面我们来实现一些方法
     */
    //遍历链表
    public void display() {
        Node cur = head;
        while (cur != null) {
            System.out.print(cur.val + " ");
            cur = cur.next;
        }
    }

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

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

    //链表的插入只是修改指向
    //头插法
    //时间复杂度O(1)
    public void addFirst(int data){
        //先得到一个结点
        Node node = new Node(data);
        //先绑定后面
        node.next = head;
        head = node;
    }

    //尾插法
    //时间复杂度O(N)
    public void addLast(int data) {
        //先得到一个结点
        Node node = new Node(data);
        if (head == null) {
            head = node;
            return;
        }
        //找到最后一个结点
        Node cur = head;
        while (cur.next != null) {
            cur = cur.next;
        }
        //绑定
        cur.next = node;
    }

    //任意位置插入
    public void addIndex(int index, int data) throws IndexOutOfBoundsException{
        Node node = new Node(data);
        //查下标的合法性
        checkIndex(index);
        if (index == 0) {
            addFirst(data);
            return;
        }
        if (index == size()) {
            addLast(data);
            return;
        }
        //先要找到index - 1的位置
        Node cur = findIndexSubOne(index);
        //先绑定后面的
        node.next = cur.next;
        cur.next = node;
    }
    //找到index - 1位置结点的位置
    private Node findIndexSubOne(int index) throws IndexOutOfBoundsException{
        Node cur = head;
        int count = 0;
        while (count != index - 1) {
            cur = cur.next;
            count++;
        }
        return cur;
    }
    //检查下标的合法性
    private void checkIndex(int index) throws IndexOutOfBoundsException{
        if (index < 0 || index > size()) {
            throw new IndexOutOfException("该下标不合法");
        }
    }

    //删除第一次出现关键字为key的结点
    //时间复杂度O(N)
    public void remove(int key) {
        if (head.val == key) {
            head = head.next;
            return;
        }
        //先找到前key个结点
        Node cur = searchPrev(key);
        if (cur == null) {
            return;
        }
        cur.next = cur.next.next;
    }
    //找到key的前一个结点
    private Node searchPrev(int key) {
        if (head == null) {
            return null;
        }
        Node cur = 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;
        }
        Node prev = head;
        Node cur = head.next;
        while (cur != null) {
            if (cur.val == key) {
                prev.next = cur.next;
                cur = cur.next;
            }else {
                cur = cur.next;
                prev = prev.next;
            }
        }
        //处理头部是关键字的情况
        if (head.val == key) {
            head = head.next;
        }
    }

    //清除
    public void clear() {
        head = null;
    }
}

异常代码如下所示:

public class IndexOutOfException extends RuntimeException{
    public IndexOutOfException() {

    }
    public IndexOutOfException(String mag) {
        super(mag);
    }
}

 

测试代码如下所示:

public class Test {
    public static void main(String[] args) {
        MySingleList mySingleList = new MySingleList();
        mySingleList.createList();
        mySingleList.display();
        System.out.println();
        System.out.println("=========1.查找是否包含关键字测试============");
        System.out.println(mySingleList.contains(2));
        System.out.println("=========2.长度测试============");
        System.out.println(mySingleList.size());
        System.out.println("=========3.头插发测试============");
        mySingleList.addFirst(88);
        mySingleList.display();
        System.out.println();
        System.out.println("=========4.尾插发测试============");
        mySingleList.addLast(88);
        mySingleList.display();
        System.out.println();
        System.out.println("=========5.任意位置插入测试============");
        try {
            mySingleList.addIndex(0,1);
        }catch (IndexOutOfBoundsException e) {
            e.printStackTrace();
        }
        mySingleList.display();
        System.out.println();
        System.out.println("=========6.删除第一次出现的关键字测试============");
        mySingleList.remove(1);
        mySingleList.display();
        System.out.println();
        System.out.println("=========7.删除出现的所有关键字测试============");
        mySingleList.removeAll(88);
        mySingleList.display();
        System.out.println();
        System.out.println("=========8.清除测试============");
        mySingleList.clear();
        mySingleList.display();
    }
}

结果如下所示:

 

3.双向链表的模拟实现

核心代码:

public class MyLinkedList {
    static class ListNode{
        public int val;
        public ListNode prev;//前驱
        public ListNode next;//后继
        public ListNode(int val) {
            this.val = val;
        }
    }
    public ListNode head;
    public ListNode last;
    //打印
    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) {
            cur = cur.next;
            count++;
        }
        return count;
    }

    //判断是否包含
    public boolean contains(int key) {
        ListNode cur = head;
        if (head.val == key) {
            return true;
        }
        while (cur != null) {
            if (cur.val == key) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

    //头插
    //时间复杂度O(1)
    public void addFirst(int data) {
        ListNode node = new ListNode(data);
        if (head == null) {
            head = node;
            last = node;
        }else {
            node.next = head;//先绑定前面的
            head.prev = node;//修改刚刚head的前驱
            head = node;//修改头部
        }
    }
    //尾插法
    //时间复杂度O(1)
    public void addLast(int data) {
        ListNode node = new ListNode(data);
        if (head == null) {
            head = node.next;
            last = node.next;
        }else {
            last.next = node;
            node.prev = last;
            last = node;
        }
    }
    //任意位置插入
    public void addIndex(int index,int data) {
        //处理合法性
        checkIndex(index);
        if (index == 0) {
            addFirst(data);
            return;
        }
        if (index == size()) {
            addLast(data);
            return;
        }
        ListNode node = new ListNode(data);
        //找到index - 1的位置
        ListNode cur = findIndex(index);
        //注意下面修改地址的顺序
        node.next = cur;
        cur.prev.next = node;
        node.prev = cur.prev;
        cur.prev = node;
    }
    private void checkIndex(int index) {
        if (index < 0 || index > size()) {
            throw new IndexOutOfException("下标不合法!");
        }
    }
    //找到index的位置
    private ListNode findIndex(int index) {
        ListNode cur = head;
        while (index != 0) {
            cur = cur.next;
            index--;
        }
        return cur;
    }
    //删除
    public void remove(int key) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.val == key) {
                //删除的是头结点
                if (cur == head) {
                    head = head.next;
                    //只有一个结点的时候
                    if (head != null) {
                        head.prev = null;
                    }
                }else {
                    //中间或者是尾部
                    cur.prev.next = cur.next;
                    if (cur.next != null) {
                        //不是尾巴结点
                        cur.next.prev = cur.prev;
                    }else {
                        //是尾部结点
                        last = last.prev;
                    }
                }
                return;
            }
            cur = cur.next;
        }
    }
    //删除所有
    public void removeAll(int key) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.val == key) {
                //删除的是头结点
                if (cur == head) {
                    head = head.next;
                    //只有一个结点的时候
                    if (head != null) {
                        head.prev = null;
                    }
                }else {
                    //中间或者是尾部
                    cur.prev.next = cur.next;
                    if (cur.next != null) {
                        //不是尾巴结点
                        cur.next.prev = cur.prev;
                    }else {
                        //是尾部结点
                        last = last.prev;
                    }
                }
            }
            cur = cur.next;
        }
    }
    //清空
    public void clear() {
        //遍历双向链表的每一个结点,把里面的后继和前驱都置为空
        ListNode cur = head;
        while (cur != null) {
            ListNode curNext = cur.next;
            cur.prev = null;
            cur.next = null;
            cur = curNext;
        }
        head = null;
        last = null;
    }
}


测试代码:

public class Test {
    public static void main(String[] args) {
        MyLinkedList myLinkedList = new MyLinkedList();
        System.out.println("===========1.头插法测试==========");
        myLinkedList.addFirst(1);
        myLinkedList.addFirst(1);
        myLinkedList.addFirst(1);
        myLinkedList.addFirst(1);
        myLinkedList.addFirst(5);
        myLinkedList.display();
        System.out.println();
        System.out.println("===========2.尾插法测试==========");
        myLinkedList.addLast(11);
        myLinkedList.addLast(12);
        myLinkedList.addLast(13);
        myLinkedList.addLast(14);
        myLinkedList.addLast(15);
        myLinkedList.display();
        System.out.println();
        System.out.println("===========3.任意位置插入测试==========");
        try {
            myLinkedList.addIndex(2,99);
        }catch (IndexOutOfException e){
            e.printStackTrace();
        }
        myLinkedList.display();
        System.out.println();
        System.out.println("===========4.删除测试==========");
        myLinkedList.remove(99);
        myLinkedList.display();
        System.out.println();
        System.out.println("===========4.删除所有测试==========");
        myLinkedList.removeAll(1);
        myLinkedList.display();
        System.out.println();
        System.out.println("===========5.清除测试==========");
        myLinkedList.clear();
        myLinkedList.display();
    }
}

异常处理代码:

public class IndexOutOfException extends RuntimeException{
    public IndexOutOfException() {

    }
    public IndexOutOfException(String mag) {
        super(mag);
    }
}

结果截图:

 

 

4.LinkedList的简单介绍

LinkedList是一个双向链表,也就是在一个结点中包含三个域(数值域、前驱、后继)如下所示:

使用如下所示:

import java.util.LinkedList;

public class Test6 {
    public static void main(String[] args) {
        LinkedList<Integer> linkedList = new LinkedList<>();
        linkedList.add(1);
        linkedList.add(2);
        linkedList.add(3);
        linkedList.add(4);
        System.out.println(linkedList);
    }
}


结果如下所示:

 

 

5.LinkedList的遍历

5.1直接打印

代码如下所示:

import java.util.LinkedList;

public class Test2 {
    public static void main(String[] args) {
        LinkedList<String> linkedList = new LinkedList<>();
        linkedList.add("hello");
        linkedList.add("world");
        linkedList.add("!!!");
        System.out.println(linkedList);
    }
}


结果截图如下所示:

 

5.2for-each遍历

代码如下所示:

import java.util.LinkedList;

public class Test3 {
    public static void main(String[] args) {
        LinkedList<String> linkedList = new LinkedList<>();
        linkedList.add("hello");
        linkedList.add("world");
        linkedList.add("!!!");
        for (String x:linkedList) {
            System.out.print(x + " ");
        }
    }
}


结果截图如下所示:

 

5.3迭代器遍历

正向打印:

代码如下所示:

import java.util.LinkedList;
import java.util.ListIterator;

public class Test4 {
    public static void main(String[] args) {
        LinkedList<String> linkedList = new LinkedList<>();
        linkedList.add("hello");
        linkedList.add("world");
        linkedList.add("!!!");
        //正向打印
        ListIterator<String> listIterator = linkedList.listIterator();
        while (listIterator.hasNext()) {
            System.out.print(listIterator.next() + " ");
        }
    }
}


结果截图如下所示:

反向打印:

代码如下所示:

import java.util.LinkedList;
import java.util.ListIterator;

public class Test5 {
    public static void main(String[] args) {
        LinkedList<String> linkedList = new LinkedList<>();
        linkedList.add("hello");
        linkedList.add("world");
        linkedList.add("!!!");
        //反向遍历
        ListIterator<String> listIterator = linkedList.listIterator(linkedList.size());
        while (listIterator.hasPrevious()) {
            System.out.print(listIterator.previous() +" ");
        }
    }
}


结果截图如下所示: 

6.LinkedList与ArrayList的区别

顺序表在物理上和逻辑上都是连续的,但是链表只是在逻辑上是连续的,在物地址上的不连续的。

随机访问上ArrayList比LinkedList要快很多,ArrayList随机访问的时间复杂度是O(1),LinkedList随机访问的时间复杂度是O(n)。

在插入的时候ArrayList中会有扩容的概念,而LinkedList中是没有这个概念的。

应用场景:ArrayList中适合高效存储和数据的频繁访问,LinkedList适合任意位置的插入和频繁的删除。

结束语:

好了这节小编带着大家简单的认识了什么是链表以及自己模拟实现了一个链表,那么下一次小编将会和大家分享一些链的练习题巩固一下我们这节的知识点,大家记得查看哦!大家继续跟紧小编的步伐一起往下走吧!!!希望对大家有所帮助,想要学习的同学记得关注小编和小编一起学习吧!如果文章中有任何错误也欢迎各位大佬及时为小编指点迷津(在此小编先谢过各位大佬啦!)

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

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

相关文章

【Vercel】教你部署imsyy/home个人主页

本篇博客教你如何部署一个自己的个人主页 项目地址&#xff1a;https://github.com/imsyy/home 本文首发于 慕雪的寒舍 1.fork仓库vercel部署 首先我们点击fork&#xff0c;将仓库复刻到自己的账户 随后进入vercel&#xff0c;点击dashboard-add new-project 选择你复刻的仓库…

我的Android开发【分代收集算法】

为什么要采用分代收集算法&#xff1f; 分代的垃圾回收策略&#xff0c;是基于这样⼀个事实&#xff1a;不同的对象的⽣命周期是不⼀样的。因此&#xff0c;不同⽣命周期的对象可以采取不同的收集⽅式&#xff0c;以便提⾼回收效率。在 Java 程序运⾏的过程中&#xff0c;会产…

【大数据基础】vmware+ununtu安装详细过程

环境配置 1. 虚拟机VMware下载与安装 首先安装vmware workstation 密钥直接利用科技生成。 检查vmware是否成功安装 2. VMware安装Ubuntu18.04 LTS 在清华源镜像站找到合适版本的ubuntu 联网安装需要一些时间&#xff0c;大致在一到两个小…

NetSuite Balancing Segment平衡段

春节假期偷了一段时间懒&#xff0c;现在开始工作了。今朝谈一个偏门题目&#xff0c;于未知领域再下一城。说这个题目偏&#xff0c;就要讲讲渊源。话说在Oracle的EBS和Fusion产品中的COA领域有个功能叫做“Balancing Segment”。 问了几位Oracle老炮&#xff0c;也说是对第二…

RDS-MySQL算不算国产数据库及其风险问题

作者&#xff1a;IT圈黎俊杰 笔者在参与国内某大型央企信创替代研究工作中&#xff0c;遇到从各方面传来的“云平台上的RDS就是国产数据库&#xff0c;使用RDS替代原数据库&#xff0c;也是信创替代”的说法&#xff0c;当说的人多了&#xff0c;产生的误导性强了以后&#xff…

Qt OpenGL(三十八)——Qt OpenGL 核心模式-绘制彩色的点

提示:本系列文章的索引目录在下面文章的链接里(点击下面可以跳转查看): Qt OpenGL 核心模式版本文章目录 Qt OpenGL(三十八)——Qt OpenGL 核心模式-绘制彩色的点 一、场景 续接上一篇文章(动态刷新点云),动态刷新点云数据,如下图: 如果,我们需要的点的绘制是彩…

数据挖掘学习笔记——GEO数据库:芯片数据分析

数据挖掘 数据挖掘学习笔记——GEO数据库&#xff1a;芯片数据分析 文章目录数据挖掘一、芯片基础知识1.1、背景二、GEO数据库概述2.1、基础简介2.2、检索页面展示三、GSE项目的三种下载方式3.1、主页下载原始数据3.2、主页下载表达矩阵3.3、GEOquery包下载表达量四、基因名与…

AI与制造的联合与突破,捷配是作何选择的?

最近的chatGPT可谓是火遍全球&#xff0c;人工智能与AI的话题再一次被推上了各大平台热搜&#xff0c;各个行业、各路专家也大谈对各个行业的影响。那么PCB制造业中&#xff0c;AI意味着什么&#xff1f;某一天&#xff0c;在生产工厂中&#xff0c;看着每一条生产链条持续运行…

一文教你学会添加通达信外挂接口(财经外挂)

今天重点讲解怎样在您的股票交易软件里增加财经类外挂&#xff0c;对您的股票交易软件进行便利化的设置与完善&#xff0c;把您想看的内容统统加紧你的交易软件&#xff0c;方便以后的研究操作。 具体流程是这样的&#xff1a;打开您要添加的财经网站官网&#xff0c;复制官网…

第四章 模块和组件、模块化和组件化的理解

1、模块 理解&#xff1a;向外提供特定功能的js程序&#xff0c;一般就是一个js文件为什么要拆成模块&#xff1a;随着业务逻辑增加&#xff0c;代码越来越多且复杂作用&#xff1a;复用js&#xff0c;简化js的编写&#xff0c;提高js运行效率 我们以一段代码举例说明拆分模块…

C#WPF基础教程3——资源字典、样式、

资源字典 创建资源字典 创建合并资源属性 资源字典的使用 引用类库中的资源字典 在类库中创建资源字典 类库中资源字典的使用 样式 添加样式 使用静态的方式为按钮添加样式 设置背景样式&#xff0c;不能直接使用value 关联事件处理程序 UI层创建事件关联程序 model端创建…

每日学术速递2.13

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV、cs.AI、cs.CL 1.Deep Intra-Image Contrastive Learning for Weakly Supervised One-Step Person Search 标题&#xff1a;深度图像内对比学习用于弱监督的单步人物搜索 作者&#…

模型解释性:SHAP包的使用

本篇博客介绍另一种事后可解释性方法&#xff1a;SHAP(SHapley Additive exPlanation)方法。 1. Shapley值理论 Shapley值是博弈论中的一个概念&#xff0c;通过衡量联盟中各成员对联盟总目标的贡献程度&#xff0c;从而根据贡献程度来进行联盟成员的利益分配&#xff0c;避免…

机器学习-特征工程

特征工程是将原始数据转换为更好的代表预测模型的潜在问题的特征的过程&#xff0c;从而提高了对未知数据的预测准确性。特征抽取文本特征抽取&#xff0c;sklearn的API是sklearn.feature_extraction.text.CountVectorizer。&#xff08;1&#xff09;.英文分词from sklearn.fe…

Smart Finance 热启动创世开启,参与质押瓜分SMART资产

在2023年开年以来&#xff0c;加密市场开始迎来复苏&#xff0c;以BTC、ETH等为代表的主流加密资产迎来普涨&#xff0c;虽然相较于2021年顶峰时期相比仍存在一定的差距&#xff0c;但市场的回暖正在带动加密行业在2023年的复苏。而随着DigiDaigaku登录美国体育盛会“超级碗”&…

手把手教你抢BingChatGPT免费体验名额!

✅作者简介&#xff1a;CSDN内容合伙人、信息安全专业在校大学生&#x1f3c6; &#x1f525;系列专栏 &#xff1a;ChatGPT-Bing &#x1f4c3;新人博主 &#xff1a;欢迎点赞收藏关注&#xff0c;会回访&#xff01; &#x1f4ac;舞台再大&#xff0c;你不上台&#xff0c;永…

关于docker在CentOS6与CentOS7的安装教程

CentOS 6 安装docker步骤yum install -y epel-releaseyum install -y docker-io安装后的配置文件&#xff1a;/etc/sysconfig/docker启动Docker后台服务&#xff1a;service docker startdocker version验证CentOS 7 安装docker步骤感兴趣的可以看看docker安装文档https://docs…

企业如何解决内容审核的安全风险?

内容审核是什么&#xff1f;随着互联网的快速发展&#xff0c;与之而来的是信息爆炸式增长&#xff0c;而且这些互联网信息良莠不齐&#xff0c;其发布者也鱼龙混杂&#xff0c;常常混入很多不良或者违规违法信息&#xff0c;例如涉政、涉黄、暴恐、违禁、不良价值观、广告等。…

使用FirewallD构建动态防火墙(9)

预备知识 firewalld 是新一 Linux 代防火墙工具&#xff0c;它提供了支持网络 / 防火墙区域 (zone) 定义网络链接以及接口安全等级的动态防火墙管理工具。它也支持允许服务或者应用程序直接添加防火墙规则的接口。在 Linux 历史上已经使用过的防火墙工具包括&#xff1a;ipfwa…

redis命令大量超时 连接数突增

大家好&#xff0c;我是烤鸭&#xff1a; 今天分享一个线上线上redis命令大量超时&#xff0c;连接数突增的问题。由于不是我这边的业务&#xff0c;只能根据事后的一些客观数据进行分析。 配置&#xff1a; ​ redis 4.0 3主3从&#xff0c;总内存36G。 ​ 业务服务7台…