LinkedList和链表(上)

news2024/10/17 10:39:23

1. 顺序表ArrayList的缺点和优点

        优点:

                1> 在给定下标进行查找的时候,时间复杂度是O(1)

        缺点:

                1> 插入数据必须移动其他数据,最坏情况下,插入到0位置,时间复杂度为O(N)

                2> 删除数据也需要移动数据,最坏情况下,就是删除0位置.时间复杂度为O(N)

                3> 扩容之后(1.5倍扩容)假设我们只用了扩容部分的一点点,这个就造成了空间的浪费

        总结: 顺序表比较适合进行给指定下标查找的情景

    由此我们引出了链表来解决这三个问题.

2. 链表的概念以及结构

        2.1 链表的结构

        链表是一种逻辑上顺序存储,物理上不一定连续存储的结构.它由若干个结点通过引用链接组成,每个结点分为value数据域和next引用域组成.

        

        2.2 链表的类型

        根据链表是否能够直接访问上一个结点,我们分为:单向和双向链表

        根据链表有没有头节点我们分为:带头和不带头

        根据链表是否首尾相连我们分为:循环和非循环

        根据排列组合,我们可以了解到由8种组合:单向带头循环,单向带头不循环,单向不带头循环,单向不带头不循环,双向带头循环,双向带头不循环,双向不带头循环,双向不带头不循环.我们最主要学习的是单向不带头非循环,双向不带头循环

        我们来看几个图简单了解一下

        单向不带头非循环链表: 结构简单一般不会用来单独存储数据.实际过程种事作为其他数据结构的字结构,比如哈希桶,图的邻接表等等.

        双向不带头非循环链表:

        双向不带头循环链表: 底层的LinkedList就是这么实现的

3. 链表的实现

        3.1 前置变量和类的解释

        我们为了更好的了解底层代码,我们来自己实现一个链表

        我们先写一个Ilist接口

        然后我们再为链表写出一个类:MySingleList,实现Ilist接口,并且重写里面的方法

        

        

         结点,作为我们链表里面的一个整体,我们用内部类来表示,叫做:ListNode.每一个结点里面存在数据域(val,存链表的数据)和next域(存结点的地址)这里我们实现的是单向带头不循环的链表,因此next指向的是下一个结点,保存的是下一个结点的地址.并且我们 提供一个构造方法,每次创建结点都要把数据域的值传进去.

这个地方我要强调一下,就是我们引用类型我们创建出来的实例,如果没有重写toString方法里面存放的就是地址

如果重写了里面的方法,就根据方法来执行

然后我们创建一个引用类型变量,不重写里面的toString方法,这个变量里面存的也是地址

package LinkedList和链表;
class Student {
    public int id;
}
class Person {
    public int age;
    public String name;
    public Student s1;

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}
public class t3 {
    public static void main(String[] args) {
        Person p = new Person();
        Student s1 = new Student();
        p.s1 = s1;
        System.out.println(p.s1);
        System.out.println(p);
    }
}
//
LinkedList和链表.Student@1540e19d
Person{age=0, name='null'}

Process finished with exit code 0

        head的头节点我们要单独定义出来,因为head不是结点内部类里面的成员,它是独立于其他节点的,其他节点都是要在head节点存在的基础上再进行创建和连接.

        我们先通过穷举的方式来理解节点这个内部类,我们写了一个createList()方法,手动来连接节点成一个链表.我们先把节点内部类进行实例化,并且传进去相关的值.我们再把每个节点的next域保存指向下一个节点,listNode1.next = listNode2;就表示,我调用listNode1的next属性,把listNode2的地址传给它

       

        我们在main里面进行测试,发现确实是把节点连成了一个链表

        3.2 具体的重写方法的介绍

                        3.2.1 遍历链表 display()

                我们通过display()方法来遍历链表,打印链表中的每一个元素

                1> 怎么从一个节点走到下一个节点? 先取出值 然后 cur = cur.next(我们为了知道头节点的位置,我们选择用cur来代替head来进行遍历)

                2> 怎么判断所有节点都遍历完: cur.next == null

                           3.2.2 求链表的结点个数 size()

                定义一个变量count;我们遍历每一个结点,并且count++.

                这里注意一下,我们的判断条件,

这个图表示了cur != null的情况

这个图表示了 cur.next != null的情况                      

        由此我们可以看出,如果想遍历并且打印每一个元素,我们应该使用cur !=null作为判断条件

                

                                3.2.3  链表中是否包含某个元素 contains()           

           我们使用contains()方法来判断这个元素是否在链表中,我们需要遍历整个链表,并且每一次遍历需要对cur当前所指向的结点val进行和key的比较,如果存在就返回true,如果经过了遍历之后还没有找到,则会返回false.

        1. 实例化一个结点. 2. 改变插入节点的next 3. 改变head                        

                                3.2.4  头插法addFirst()

        头插法: 如果链表不为null,就先创建一个结点,再连接head的结点,最后让head指向新的结点

        其中 node.next = this.head; this.head = node;这俩行代码是不能换位置的,如果我们head直接指向head,node后面就没有结点了.

        1. 实例化一个结点 2. 找到最后一个cur 3. cur.next = node;

                                3.2.5 尾插法addLast()

        尾插法: 我们把待插入的结点放在链表的最后一个位置,同样也要考虑head为null的情况

                在这里做一个小总结:

        如果我们想让cur停在最后一个节点的位置 cur.next != null;

        如果我们想把整个链表的每一个节点都遍历完,那么cur != null

        头插法的时间复杂度为O(1)

        尾插法的时间复杂度为O(N)

                                3.2.6 在任意位置插入元素addIndex()

        先判断是不是在首位插入,如果是的话就调用上述的相应方法,如果不是,那么就是中间的位置

      我们就需要先找到index的前一个,我们调用searchPre方法

1. 让cur 走 index- 1步,找到要插入的前一个位置

然后为你产生一个新的节点,把数据传进去然后这么操作

2. node.next = cur.next; cur.next = node;        

        这里面我们要注意,因为我们是通过下标来进行插入的,因此,我们需要对下标的合法性进行判断

                                3.2.7 删除元素remove()

        我们对一个元素进行删除,首先要看这个链表是不是空的,然后我们要判断并这个元素是否存在,并且要获得它的前一个元素.然后进行下标的操作即可

这个是找到前一个元素的方法findPre()

        .

                                 3.2.8 删除所有值为key的元素removeAllKey()

                在删除这个元素之前,我们需要判断这个链表是不是空的也就是头节点为null,然后我们进行遍历,每次到一个节点就判断它的val和我们要删除的key是不是相同,如果相同,我们就让删除的节点的前一个结点直接指向删除的后一个结点.然后更新cur结点.但是在这个过程中,我们有个问题,就是head结点并没有判断,因为我们的cur是从当前结点的下一个结点开始的.

                             3.2.9 清空链表中的所有元素clear()   

                也就是我们需要把链表所有的结点都置为空(如果是引用数据类中,数据域也要置为空)

这里要注意,最后我们的cur,curNext都会置空,但是我们的head一直没有置空,因此我们要手动把head也置空.

                3.3 整体代码

package LinkedList和链表;

import java.util.List;

public class MySingleList implements Ilist{
    //TODO 先构造一个链表结构
    //我们把一个个结点构造成内部类
    static class ListNode {
        public int val;
        public ListNode next;//这里面存的是结点的地址
        public ListNode(int val) {
            this.val = val;
        }
    }
    //TODO head头节点
    //head是属于链表的成员,而不是结点内部类的成员,所以要定义在外面
    public ListNode head;
//    public int usedSize;//有效数据元素的数量

    //先通过穷举的方法来理解这个结构
    public void createList() {

        ListNode listNode1 = new ListNode(1);//每引用类型的变量名存着它这个引用类型变量的地址
        ListNode listNode2 = new ListNode(2);
        ListNode listNode3 = new ListNode(3);
        ListNode listNode4 = new ListNode(4);
        ListNode listNode5 = new ListNode(5);
        listNode1.next = listNode2;
        listNode2.next = listNode3;
        listNode3.next = listNode4;
        listNode4.next = listNode5;
        listNode5.next = null;
        this.head = listNode1;

    }
    //TODO 1.遍历列表,打印链表的每一个元素
    //怎么从一个结点走到下一个结点: 先取出值 再head = head.next
    //怎么判断所有结点都遍历完: head.next == null ??这样会漏掉最后一个结点,因此head == null才是终止条件
    @Override
    public void display() {
        ListNode cur = this.head;//为了防止我们遍历一遍链表我们头结点的地址就找不到了
        while (cur != null) {//是否遍历完了链表
            System.out.print(cur.val + " ");
            cur = cur.next;//如何从当前位置结点指向下一个结点
        }
        System.out.println();
    }
    //TODO 2.求链表的结点个数
    //遍历每一个结点,count++
    @Override
    public int size() {
        int count = 0;
        ListNode cur = this.head;
        while (cur != null) {
            count++;
            cur = cur.next;
        }
        return count;
    }
    //TODO 3.链表中是否包含某个元素

    @Override
    public boolean contains(int key) {
        ListNode cur = this.head;
        while (cur != null) {
            if(cur.val == key) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }
    //头插法: 如果链表不为null 先创建一个结点 再链接head的结点 最后让head指向新的结点
    //如果head为null 直接head指向新结点即可
    @Override
    public void addFirst(int data) {
        ListNode node = new ListNode(data);
        if(this.head == null) {//其实也可以不要if
            this.head = node;
        } else {
          node.next = this.head;
          this.head = node;
        }
    }
//TODO 4.尾插法 把待插入的结点放在链表的最后一个位置
    //1. 实例化一个结点,2. 找到最后一个结点cur,3. cur.next = node;
    //head为空
    @Override
    public void addLast(int data) {
        ListNode node = new ListNode(data);
        ListNode cur = this.head;
        if (this.head == null) {
            this.head = node;
        } else {
            //找到尾巴
            while (cur.next != null) {//cur指向的是最后一个结点
                cur = cur.next;
            }
            //现在指向最后一个结点
            cur.next = node;
        }
    }
    //TODO 总结
    //如果向让cur停在最后一个结点的位置 cur.next != null
    //如果想把整个链表的每一个结点都遍历完,那么cur != null
    //头插法的时间复杂度O(1)
    //尾插法的时间复杂度O(N)
    //链表的插入只要改变指向即可

    //TODO 5.任意一个位置插入元素
    //我们需要找到index的前一个元素 node.next = cur.next,cur.next = node
    //1.让cur走index-1步
    //2. node.next = cur.next,cur.next = node 这里不能换过来
    //在插入一个结点的时候,一定要先绑定后面的这个结点
    @Override
    public void addIndex(int index, int data) throws IndexIlleage{
        //判断index的合法性
        if(index < 0 || index > size()) {
            throw new IndexIlleage("下标非法! "+ index);

        }
        //如果是往第一个位置插,就调用头插法
        if (index == 0) {
            addFirst(data);
            return;
        }
        //如果是在最后一个位置插,就用尾插法
        if (index == size()) {
            addLast(data);
            return;
        }
        //如果是在中间插的话,我们就需要找到index的前一个
        ListNode cur = searchPre(index);
        ListNode node = new ListNode(data);
        node.next = cur.next;
        cur.next = node;
    }
    //找到index的前一个结点
    private ListNode searchPre(int index) {
        ListNode cur = this.head;
        int count = 0;
        while (count != index - 1) {
            cur = cur.next;
            count++;
        }
        return cur;
    }

//TODO 6.删除元素
    // 1.找到删除元素的前一个cur.next.value == key
    @Override
    public void remove(int key) {
        //如果为空,一个结点都没有,无法删除
        if(this.head == null) {
            System.out.println("链表为空,无法删除!");
            return;
        }
        //如果我们要删去的是第一个元素
        if(this.head.val == key){
            this.head = this.head.next;
        }
        //1.找到前驱
        ListNode preNode = findPre(key);
        //2.判断返回值是否为空
        if(preNode == null) {
            System.out.println("没有这个元素");
            return;
        }
        //3.删除元素(修改指向即可)
        ListNode delNode = preNode.next;
        preNode.next = delNode.next;
        //delNode是局部变量,当这个方法结束了,局部变量就没了
    }
    //找到前一个元素的位置
    private ListNode findPre(int key) {
        ListNode cur = this.head;

        while (cur.next != null) {//避免空指针异常,因为我们要用到下一个结点的val
            if(cur.next.val == key){
                return cur;
            }
            cur = cur.next;
        }
        return null;
    }
//TODO 7.删除所有值为key的元素
    @Override
    public void removeAllKey(int key) {
        //判断头节点是不是空的
        if(this.head == null) {
            return;
        }
        //判断头结点是不key
        if(head.val == key) {
            this.head = this.head.next;
        }
        ListNode prev = head;
        ListNode cur = head.next;
        //我们遍历除了head后面的元素
        while (cur != null) {
            if (cur.val == key) {
                prev.next = cur.next;
                cur = cur.next;
            } else {
                prev = cur;
                cur = cur.next;
            }
        }
    }

//TODO 清空链表所有的元素
    //每一个结点数值域(可能为引用数据类型)
    @Override
    public void clear() {
//    head = null;
        ListNode cur = head;
     while (cur != null) {
         ListNode curNext = cur.next;
         cur.next = null;
         //cur.val = null;如果是引用数据类型
         cur = curNext;
     }//后面所有的结点都置为null了,但是head还没有置为空
     head = null;
    }


}

package LinkedList和链表;

public class Test {
    public static void main(String[] args) {
    MySingleList mySingleList = new MySingleList();
    mySingleList.createList();
        System.out.println("777");
        mySingleList.display();
        System.out.println(mySingleList.size());
        System.out.println(mySingleList.contains(1));

//使用头插法来
        mySingleList.addFirst(12);
        mySingleList.addFirst(22);
        mySingleList.addFirst(32);
        mySingleList.addFirst(42);
        mySingleList.addLast(90);
        mySingleList.addLast(12);
        mySingleList.addLast(12);
        mySingleList.addLast(12);
        mySingleList.display();
        System.out.println(mySingleList.size());
        System.out.println(mySingleList.contains(1));
        mySingleList.addIndex(1,3);
        mySingleList.addIndex(0,3);
        mySingleList.addIndex(1,3);
        System.out.println(mySingleList.size());
        mySingleList.addIndex(9,78);

        mySingleList.display();
        mySingleList.remove(90);
        mySingleList.display();
        mySingleList.remove(78);
        mySingleList.display();
        mySingleList.removeAllKey(12);
        mySingleList.display();

        

//        mySingleList.addIndex(10,90);

    }
}
package LinkedList和链表;

public class IndexIlleage extends RuntimeException{
    public IndexIlleage(String s) {
        super(s);
    }
}

package LinkedList和链表;

public interface Ilist {
    //头插法
     void addFirst(int data);
     void addLast(int data);
     void addIndex(int index,int data);
     boolean contains(int key);public void remove(int key);
    //删除所有值为key的节点
     void removeAllKey(int key);
     int size();
     void clear();
     void display();
}



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

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

相关文章

[PHP]Undefined index错误只针对数组

1、示例一 <?php $a null; var_dump($a[name]); 结果&#xff1a;无报错 2、示例二 <?php $a []; var_dump($a[name]);结果&#xff1a;报错

v853 tina 文件系统挂载不全问题

文章目录 1、前言2、环境介绍3、解决 1、前言 正常文件系统挂载后应如下图所示&#xff1a; 但目前是这样&#xff1a; 2、环境介绍 硬件&#xff1a;韦东山v853 aicit板卡 软件&#xff1a;v853 tina sdk 3、解决 menuconfig中开启e2fsprogs&#xff1a; Utilities ---&…

C++ —— 关于继承(inheritance)

目录 1. 继承的概念及定义 1.1 继承的概念 1.2 继承的定义格式 1.3 继承基类成员访问方式的变化 1.4 类模板的继承 2.基类与派生类的转换 3. 继承中的作用域 3.1 隐藏规则 4. 派⽣类的默认成员函数 4.1 4个常见默认成员函数 4.2 实现⼀个不能被继承的类 5. 继承与友元…

房产销售系统(论文+源码)_kaic

摘 要 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势&#xff1b;对于房产销售系统当然也不能排除在外&#xff0c;随着网络技术的不断成熟&#xff0c;带动了房产销售系统&#xff0c;它彻底改变了过去传统的…

wps安装教程

WPS office完整版是一款由金山推出的免费办公软件&#xff0c;软件小巧安装快&#xff0c;占用内存极小&#xff0c;启动速度快。WPS office完整版包含WPS文字、WPS表格、WPS演示三大功能模块&#xff0c;让我们轻松办公。WPS的功能是依据OFFICE用户的使用习惯而设计&#xff0…

ios局域网访问主机Xcode配置

前景&#xff1a; 公司业务是做智能家居&#xff0c;所有设备通过主机控制&#xff0c;目前有个产品需求是&#xff0c;在没有外网的情况下依然能够通过局域网控制主机的设备。 IOS开发需要做的&#xff1a; 除了业务代码之外&#xff0c;前提还要配置访问局域网功能。有以下…

专升本:开启人生新征程

在当今社会&#xff0c;学历的重要性日益凸显。对于专科生来说&#xff0c;专升本无疑是一次改变命运、开启人生新征程的重要机遇。 一、专升本的价值与意义 &#xff08;一&#xff09;学历提升当你通过专升本考试&#xff0c;成功踏入本科院校的大门&#xff0c;你将获得更高…

【Word原件测试资料合集】软件系统功能测试方案,软件测试方案(整体方案),软件测试文档-测试计划模版(功能与性能),软件测试流程

一、 前言 &#xff08;一&#xff09; 背景 &#xff08;二&#xff09; 目的 &#xff08;三&#xff09; 测试目标 &#xff08;四&#xff09; 适用范围与读者对象 &#xff08;五&#xff09; 术语与缩写 二、 软件测试实施流程 &#xff08;一&#xff09; 测试工作总体流…

【uniapp】实现触底加载数据

前言&#xff1a;实现界面触底数据加载。后端接口得支持翻页传参&#xff08;本案例使用django&#xff09; 1、后端接口 1.1 封装翻页公共方法standardPagination.py # -*- coding: utf-8 -*- # Time : 2024/10/15 13:15 # Author : super # File : standardPaginat…

idea2024年版本

最简单安装2024.2版本idea 内带安装教程 ** 下载链接&#xff1a;https://pan.quark.cn/s/ab24afbaa43f 提取码&#xff1a;KHrq

《计算机视觉》—— 基于PyCharm中的dlib库实现人脸关键点定位

文章目录 1. 安装必要的库2. 下载dlib的人脸检测器和关键点预测器模型3. 编写代码 人脸关键点定位是指通过计算机视觉技术&#xff0c;识别和定位人脸图像中的关键点&#xff0c;如眼睛、鼻子、嘴巴等特定位置。这些关键点的准确定位对于人脸识别、表情分析、姿态估计等应用具有…

天地伟业NVR管理工具EasyNVR平台多个NVR同时管理运行后日志一直在打印error

视频安防监控平台天地伟业NVR管理工具EasyNVR可支持设备通过RTSP/Onvif协议接入&#xff0c;并能对接入的视频流进行处理与多端分发&#xff0c;包括RTMP、RTSP、WS-FLV、HTTP-FLV、WebRTC、HLS等多种视频流格式。平台可提供视频实时监控直播、云端录像、录像检索、云存储与回看…

基于SSM的药品商城系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

沉浸式娱乐新纪元,什么是5G+实时云渲染VR大空间?

近年来&#xff0c;虚拟现实&#xff08;VR&#xff09;技术在娱乐、教育、医疗等多个领域展现出巨大的潜力&#xff0c;尤其是VR大空间体验&#xff0c;更是以其沉浸式和互动性的特点&#xff0c;迅速成为市场的新宠。据Statista数据显示&#xff0c;2023年&#xff0c;全球虚…

Lumerical学习——资源管理和运行模拟

一、资源管理&#xff08;Resource Manager&#xff09; 在模拟计算前必须对计算资源进行配置。采用资源管理器可以完成这项任务。单击主工具条的“资源&#xff08;Resources&#xff09;”按钮&#xff08;见上图&#xff09;就可以打开资源管理器。通常每个计算机只需设置一…

【含文档】基于Springboot+Vue的仓库管理系统设计与实现(含源码+数据库+lw)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 系统定…

JAVA基础【第二篇】

文章目录 类变量介绍语法代码示例 类方法介绍注意事项代码示例 代码块介绍基本语法注意事项 设计模式单例设计模式介绍饿汉式懒汉式两者区别 模板设计模式介绍代码示例 final关键字介绍代码示例注意事项 抽象类介绍注意事项 接口介绍基本语法注意细节接口特性 内部类介绍分类局…

深入理解 KMP 算法

《深入理解 KMP 算法》 在计算机科学中&#xff0c;字符串匹配是一个常见且重要的问题&#xff0c;而 KMP&#xff08;Knuth - Morris - Pratt&#xff09;算法则是一种高效的字符串匹配算法。 一、KMP 算法的背景 在传统的字符串匹配算法中&#xff0c;当遇到不匹配的字符时…

基于AES的遥感图像加密算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 4.1 AES 加密算法概述 4.2 基于 AES 的遥感图像加密算法原理 5.完整程序 1.程序功能描述 通过AES算法对遥感图像进行加密和解密&#xff0c;分析加解密处理后图像的直方图&#xff0c;相关…

React之组件渲染性能优化

关键词&#xff1a; shouldComponentUpdate、PureComnent、React.memo、useMemo、useCallback shouldComponentUpdate 与 PureComnent shouldComponentUpdate 与 PureComnent 用于类组件。虽然官方推荐使用函数组件&#xff0c;但我们依然需要对类组件的渲染优化策略有所了解…