链表-------数据结构

news2025/2/4 13:05:56

链表(重点):

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

1)在顺序表中,我们不光引入了一段连续的内存,还引入了一块连续的内存空间,叫做usedsize,来表示对应数组中元素的个数,在我们增加和删除元素的时候,都会涉及到一定范围内下标的移动;

2)在我进行扩容的时候,例如说我原来想要进行扩容,以数组的二倍形式来进行扩容,但是我进行而被扩容之后只放了一个元素,扩容的效率就会大大降低,我们想要一种数据结构,再进行插入元素和删除元素的时候,都不要涉及到原来元素的插入和删除,时间复杂度可以达到O(1),节省空间

下面我们主要介绍的是单向,不带头非循环的和双向,不带头,非循环的链表

1)带头:也是有一个head(是一个傀儡节点),head里面的数据是任意的,真正的有效数据放到head.next的下一个节点之后,当我们使用傀儡节点的时候是用头插法,只能插入到傀儡节点和头结点的中间,head的指向是不会发生变化的,这样就可以不用修改头指针了

但是如果不是用带头的傀儡节点,每一次使用头插法的时候,head头节点都会发生变化

2)循环:尾巴节点的next域指向head节点,如果next指向了head.next或者其他节点,就是带有环的链表

3)双向:每一个节点即存放下一个结点的地址,也保存前一个结点地址,还保存数据

节点:有数据域(主要是进行存放数据)和指针域(存放下一个结点的地址),本质上是一个类;

下面我们来实现一个链表:

1)用一个类来进行描述一个节点,因为链表就是一个一个的节点构成的

2)实现一个链表的类

下面的方法包括

package OperateNode;

public class MyLinkedList {
    public Node head;
    public Node tail;
    //遍历整个链表
    public void show(Node head){
        Node current=this.head;
        while(current!=null){
            System.out.println(current.data);
            current=current.next;
        }
    }
    //查看单链表的长度
    public int Size(){
        int count=0;
        Node current=this.head;
        while(current!=null){
            current=current.next;
            count++;
        }
        return count;
    }
    //查找是否包含某个关键字Key是否在单链表里面
    public boolean contains(int key){
        Node current=this.head;
        while(current!=null){
            if(current.data==key){
                return true;
            }
            current=current.next;
        }
        return false;
    }
//继续进行头插法
    public void addFrist(int data){
//如果说一个节点也没有,那么就说明head为空
//但是下面的代码已经囊盖了这种情况就不需要进行额外考虑
//head为空的情况了
 Node node=new Node(data);
 node.next=head;
 head=node;
//   这么做也是可以的     if(this.head==null){
//            this.head=node;
//        }else{
//            node.next=this.head;
//            this.head=node;
//        }
    }
//继续进行尾插法
    public void addLast(int data){
//尾差的顺序就是按照存储的位置进行存放的
//我们进行寻找尾节点的方式,让current指向头结点,不断遍历如果current.next为空,
//说明current指向的节点是尾巴结点,我们此时就可以新创建结点进行插入
//如果说此时current为空,链表里面一个元素也没有,循环进不去current一直为空
//你让current.next=node,此时就会发生空指针异常
        //所以说使用尾插法进行插入元素,一定要判断head是否为空
//       if(head==null){
//           Node node=new Node(data);
//           head=node;
//           tail=node;
//           return;
//       }else{
//           Node node=new Node(data);
//           tail.next=node;
//           tail=node;
//       }
        Node node=new Node(data);
        if(head==null){
            head=node;
        }else{
            Node current=head;
            while(current.next!=null){
                current=current.next;
            }
            //此时的current的next为空,我们在这里面就可以进行设置
            current.next=node;
        }
    }
    public static void main(String[] args) {
        MyLinkedList list=new MyLinkedList();
        list.addLast(1);
        list.addLast(23);
        list.addLast(98);
        list.addLast(100);
        list.addLast(101);
        list.show(list.head);
    }
}

链表的插入:给定固定数据和要进行插入的位置进行插入操作,两种写法

 public void Insert(int pos,int data){//从0好位置元素开始记起
        if(pos<0||pos>Size()){
            throw new RuntimeException("当前您插入的位置下标不合法");
        }
        Node PreNode=this.head;
        for(int i=0;i<pos-1;i++){
            PreNode=PreNode.next;
        }//先让他走index-1步,让他找到index节点的前一个节点
        Node node=new Node(data);
        if(head==PreNode){//判断是否是头结点,要进行特殊处理
          node.next=head;
          head=node;
        }else {//不是头结点
            node.next = PreNode.next;
            PreNode.next = node;
        }
    }
 public void Insert(int pos,int data){//从0好位置元素开始记起
        if(pos<0||pos>Size()){
            throw new RuntimeException("当前您插入的位置下标不合法");
        }
        //判断是不是在头结点进行插入
        if(pos==0) {
         addFrist(data);
         return;//在这里面要进行返回操作,不要再让程序进行向下执行
        }
        //判断是否在为节点进行插入
        if(pos==Size()){
            addLast(data);
            return;
        }
        //代码走到这里面说明已经我们想要进行插入的节点是中间的节点,设置的节点既不是头结点也不是尾结点
        Node current=this.head;
       for(int i=0;i<pos-1;i++){
          current=current.next;
       }
       Node node=new Node(data);
       node.next=current.next;
       current.next=node;
    }

删除第一次出现的指定节点:

 public void remove(int key){
     if(head==null)//先判断链表里面是否有元素
     {
         throw new RuntimeException("当前您的链表里面没有元素");
     }
     if(head.data==key){
         head=head.next;//头结点进行特殊处理
         return;
        }
     Node PreNode=null;
     Node current=this.head;
     while(current!=null){
         if(current.data==key){
             break;
         }
         PreNode=current;
         current=current.next;
       }
        if(current==null){//如果说当前链表没有这个结点,抛出异常
            throw new RuntimeException("当前链表里面没有你指定的节点");
        }
     PreNode.next=current.next;
    }

1)先进行判断整个链表是否为空,在进行判断当前删除的元素是否是头结点

2)如果是头结点,删除头结点,否则就进行查找要删除节点的前一个节点

第二种方法:遍历链表一遍,删除所有值为key的结点

 public void remove(int key){
        if(this.head==null){//先进行判断当前链表中是否有元素
            throw new RuntimeException("此处的链表中的元素是空,不能进行删除操作");
        }
        if(head.data==key){//观察头结点是否是删除的节点
            head=head.next;
            return;
        }
        Node current=this.head;//我们想要最后删除的节点的前一个结点是current
        //开始找要被删除节点的前一个节点
        while(current.next!=null){//意思是说如果current在循环退出指向的是最后一个节点,那么说明key根本就在链表中不存在
            if(current.next.data==key){
                break;
            }
            current=current.next;
        }
      if(current.next==null){
          throw new RuntimeException("当前您想要删除的key根本就不在链表里面");
      }
      Node Pre=current;
      Node Del=current.next;
      Pre.next=Del.next;
    }

删除一个链表中所有指定为Key的结点:

  public void removeAll(int key){
        if(head==null){
            throw new RuntimeException("当前整个链表里面没有元素");
        }
        Node current=this.head;
        Node prev=null;
        while(current!=null){
            if(current.data==key){
                //首先判断是否是头结点
                if(head.data==key){
                    head=head.next;
                    current=head;
                }else{
                    prev.next=current.next;
                    current=prev.next;
                }
            }else{
                prev=current;
                current=current.next;
            }
        }
 public void removeAll(int key){
        if(head==null){
            throw new RuntimeException("当前整个链表里面没有元素");
        }
        Node current=this.head.next;
        Node prev=this.head;
        while(current!=null){
            if(current.data==key){
                    prev.next=current.next;
                    current=prev.next;
            }else{
                prev=current;
                current=current.next;
            }
        }
        if(head.data==key){
            this.head=this.head.next;
        }
    }

清空链表:

1)直接置为空

2)逐步将每一个结点的next置为空

  public void clear(){
        Node current=null;
        while(head!=null){
            current=head.next;
            head.next=null;
            head=current;
        }
        head=null;
    }

1)进行反转单链表

输入:1,2,3,4,5;

输出:5,4,3,2,1;

1)我们先写两个变量,front=head,current=front.next;我们设想一下只用这两个变量是否可以进行反转单链表的操作呢;

我让current.next=front,再让front=current;这是我们就发现current已经无法走到下一个节点了,因为此时current.next已经被修改了,所以我们可以让curNext来进行保存每一次反转操作的current.next的值(这个操作再循环中的第一条语句)

2)循环条件为current.next!=null;

public Node resverse()
{
    if(head==null)
    {
        throw new RuntimeException("链表长度为空");
    }
    Node front=null;
    Node current=head;
    Node currentNext=head.next;
    while(current!=null)
    {  currentNext=current.next;
        current.next=front;
        front=current;
        current=currentNext;
    }
    return front;
}

2.只遍历单链表一遍,找出链表的中间节点,如果有两个中间节点,那么返回第二个中间节点

输入:1,2,3,4,5; 返回:3

输入:1,2,3,4,5,6;返回:4;如果一个链表有两个中间节点,那么我们返回第二个节点

思路:进行快慢指针,我们可以定义一个快慢指针,一开始让fast节点和slow节点都指向head,然后我们指向循环,让fast一次走两步,让slow一次走一步;

最终slow就是中间节点

我们可以自己画图演示一下,链表长度为奇数或者链表长度是偶数的截至条件是不一样的

   public ListNode middleNode(ListNode head) {
       ListNode fast=head;
       ListNode slow=head;
       while(fast!=null&&fast.next!=null)
       {
           fast=fast.next.next;
           slow=slow.next;
       }
       return slow;

3.找出链表中的倒数第K个节点,能不能遍历单链表一遍

要找到倒数第K个,要从前向后走len-k步(至少要遍历两遍,因为首先要知道链表的长度); 

1)我们进行定义两个快慢指针,fast为快指针,slow是慢指针,我们先让fast走k-1步,然后再让fast和slow同时走;等到fast.next为空的时候(或者说fast走到最后一个节点),并返回slow节点,这时的slow才为倒数第K个节点;

2)更简单的方法是先求出链表的长度,让current走len-k步

class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
          if(k<=0||k>size()){//必须加上,否则下面的循环会出现空指针异常
            return null;
            }
        int count=0;
        ListNode current=head;
        while(current!=null){
            count++;
            current=current.next;
        }
        current=head;
        for(int i=0;i<count-k;i++){
              current=current.next;
        }
     return current;
    }
}

public Node returnLastK(int index)
{
    if(index<0)
    {
        throw new UnsupportedOperationException("此时的链表的倒数第K个节点位置不正确");
    }
    if(head==null)
    {
        throw new UnsupportedOperationException("此时链表长度是空");
    }
    Node fast=head;
    Node slow=head;
    for(int i=0;i<index-1;i++)
    {  fast=fast.next;
        if(fast==null)
        {
            throw new UnsupportedOperationException("当前你输入的index的值已经超过了链表的长度");
        }
    }
    while(fast.next!=null)
    {
        fast=fast.next;
        slow=slow.next;
    }
    return slow;
}

4.合并两个有序链表

思路:

1)我们首先定义一个虚拟节点是newHead作为要合并在一起的总链表的新节点,注意它是一个虚拟节点,里面的值是任意的

2)我们再合并headA和headB这两个链表的时候,保存两个链表的右节点是没有什么用处的,如果发现headA.data>headB.data,我们就把HeadA指向的这个头结点放到先开辟的链表后面,同时让headA进行向后移动

   if(headA.data>headB.data)

      {

       }else if(HeadA.data<HeadB.data){

   }else{

   }

3)再进行合并的过程中,两个链表所走的过程中都不可以是空的,所以循环条件是HeadA!=null&&HeadB!=null

4)在循环出来之后,会出现一种情况,HeadA过长或者HeadB过长,此时我们还要进行特殊处理

 public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode HeadA=list1;
        ListNode HeadB=list2;
        ListNode newHead=new ListNode(-1);
       ListNode temp=newHead;
        while(HeadA!=null&&HeadB!=null)
        {
            if(HeadA.val<HeadB.val)
            {
                temp.next=HeadA;
                HeadA=HeadA.next;
                temp=temp.next;
            }else
            {
                temp.next=HeadB;
                HeadB=HeadB.next;
                temp=temp.next;
            }
        }
        if(HeadA!=null)
        {
            temp.next=HeadA;
        }
        if(HeadB!=null)
        {
            temp.next=HeadB;
        }
        return newHead.next;
class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
       ListNode head1=list1;
       ListNode head2=list2;
       ListNode NewHead=new ListNode(17);
       ListNode current=NewHead;
       while(head1!=null&&head2!=null){
           if(head1.val>=head2.val){
               ListNode node=new ListNode(head2.val);
               current.next=node;
               head2=head2.next;
               current=current.next;
           }else{
               ListNode node=new ListNode(head1.val);
               current.next=node;
               head1=head1.next;
               current=current.next;
           }
       }
       while(head1!=null){
           ListNode node=new ListNode(head1.val);
           current.next=node;
           head1=head1.next;
           current=current.next;
       }
       while(head2!=null){
           ListNode node=new ListNode(head2.val);
               current.next=node;
               head2=head2.next;
               current=current.next;
       }
     return NewHead.next;
     }
}

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

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

相关文章

PMP每年考几次,费用如何?

PMP每年考四次&#xff0c;整个考证一次通关大致需要 7000 元左右&#xff0c;主要是下面几项费用&#xff1a; 部分学习笔记&#xff1a; 一、考证费用 分为基础费用报班费用 基础费用&#xff1a;报名费续证费用&#xff08;补考费 / 退考费&#xff09; 报名费 3900 元是固…

【软件测试】测试人的内卷,掀起血雨腥风......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 铺天盖地的职场内卷…

如何利用深度学习中的AutoEncoder进行特征降维和特征可视化,pytorch代码

我们将使用 Pytorch 中的 AutoEncoder(自动编码器架构)来减少特征维度和可视化。 北大出版社&#xff0c;人工智能原理与实践 人工智能和数据科学从入门到精通 详解机器学习深度学习算法原理 人工智能原理与实践 全面涵盖人工智能和数据科学各个重要体系经典 首先&#xff0c…

力扣(142.1002)补9.17

142.环形链表Ⅱ 不会&#xff0c;不过答案用了数学的想法&#xff0c;我以为计算机里只有暴力呢。 public class Solution { public ListNode detectCycle(ListNode head) { if(headnull||head.nextnull) return null; ListNode phead; ListNode p2head; while(true){ if(p2.ne…

Excel教程之学生成功所需的 5 个电子表格

作为一名学生,跟踪你盘子里的所有任务和责任可能会让人不知所措。 这就是为什么拥有一套组织良好的电子表格可以成为救命稻草的原因。出于多种原因,维护自己的电子表格可能是一项宝贵的技能。首先,它可以帮助您养成良好的习惯,例如组织和关注细节。通过创建和维护您自己的…

RabbitMQ之Exchange(交换机)

目录 一、Exchange简介 二、Exchange(交换机)的类型 1.直连交换机&#xff1a;Direct Exchange 2.主题交换机&#xff1a;Topic Exchange 3.扇形交换机&#xff1a;Fanout Exchange 4、默认交换机 5、Dead Letter Exchange&#xff08;死信交换机&#xff09; 三、交换机…

Conan 上传预编译的包

目录 1. 组织文件 2. 编写conanfile.py 3. 然后执行export 命令 4. 上传到自己的center 疫情肆虐&#xff0c;阳了一周&#xff0c;今天可以正常工作了&#xff0c;刚接触conan, 确实一脸懵逼&#xff0c;今天的任务是把项目转成Conan 管理&#xff0c;因为项目用到了第三方…

R语言学习笔记——扩展篇:第十九章-使用ggplot2进行高级绘图

R语言 R语言学习笔记——扩展篇&#xff1a;第十九章-使用ggplot2进行高级绘图 文章目录R语言一、R中的四种图形系统二、ggplot2包介绍三、用几何函数指定图的类型四、分组&#xff08;重叠图形&#xff09;五、刻面&#xff08;并排图形&#xff09;六、添加光滑曲线七、修改…

SpringMVC的AOP总结

SpringMVC的AOP总结 1、Filter 过滤器 Filter是Servlet规范中规定的&#xff0c;只能用于WEB中, 在Servlet前后起作用 它可以对几乎所有请求进行过滤&#xff0c;但是缺点是一个过滤器实例只能在容器初始化时调用一次 使用场景: 修改字符编码; 对入参进行校验, 校验不通过返回…

java实验报告之Employee类的设计

一个不知名大学生&#xff0c;江湖人称菜狗 original author: jacky Li Email : 3435673055qq.com Time of completion&#xff1a;2022.12.20 Last edited: 2022.12.20 目录 一、实验目的 二、实验内容 三、总体设计&#xff08;设计原理、设计方案及流程等&#xff09; 四…

Python数据结构+算法全面讲解:定义函数、定义类

之前的过程抽象例子调用了 Python数学模块中的 sqrt 函数来计算平方根。通常来说,可以 通过定义函数来隐藏任何计算的细节。函数的定义需要一个函数名、一系列参数以及一个函数体。 函数也可以显式地返回一个值。例如,下面定义的简单函数会返回传入值的平方。 >>> …

使用 Appium 报错“... Could not find ‘adb‘ in ...”

使用 Appium 报错 “... Could not find adb in ...”1. 现象2. 问题定位3. 解决方案4. 验证1. 现象 在 Robot Framework 中使用 Open Application 关键字以通过 Appium 来打开模拟器上的应用报错&#xff1a; WebDriverException: Message: An unknown server-side error occ…

Transformer17

还是transformer 这次还是谷歌哈 又在机器人领域发力 谷歌机器人团队等在机器人领域构建了一个多任务 transformer 模型&#xff0c;显著改进了对新任务、环境和对象的零样本泛化。轻松完成700多条指令、成功率达97%&#xff01;谷歌开源机器人领域 我们知道&#xff0c;机器…

Docker搭建MySQL主从集群

使用Docker搭建一主一从的MySQL集群&#xff0c;使用的是8版本的MySQL镜像不是8的版本部分命令会无效&#xff0c;宿主机任意 规划 端口角色3307master3308slave 思路 事先准备 要确保linux宿主机已经安装上Docker。然后将MySQL镜像下载到本地 &#xff0c;可以先去Docker Hu…

ssm java mysql_医院门诊管理系统_

息化不断建设发展的今天&#xff0c;医院看病预约&#xff0c;医生的挂号等&#xff0c;已经十分方便&#xff0c;通过在线挂号&#xff0c;医生的查看&#xff0c;就能够了解到医院的门诊基本信息&#xff0c;并且可以在线进行门诊的医生查看&#xff0c;医院最新的资讯等&…

Golang 【basic_leaming】切片

阅读目录1、为什么要使用切片2、切片的定义3、关于nil 的认识4、切片的循环遍历5、基于数组定义切片6、切片再切片7、关于切片的长度和容量8、切片的本质9、使用 make() 函数构造切片10、切片不能直接比较11、切片是引用数据类型 -- 注意切片的赋值拷贝12、append() 方法为切片…

Mycat(10):分片详解之固定分片hash算法

1 找到conf/schema.xml并备份 2 固定分片hash算法 本条规则类似于十进制的求模运算&#xff0c;区别在于是二进制的操作,是取id的二进制低10位&#xff0c;即id二进制 。 此算法的优点在于如果按照 10进制取模运算&#xff0c;在连续插入1-10 时候1-10会被分到1-10个分片&…

手机号格式检查系统(Java)

本系统支持的手机号检查如下所示&#xff1a; /** * 中国移动&#xff0c;中国联通&#xff0c;中国电信都为11位的手机号 * 中国"移动"前三位: * 135、136、137、138、139、147、150、151、152、157、 * 158、159、172、178、182、183、184、187、188、195、197、19…

铝网初效过滤器及金属网过滤器的区别

广州特耐苏净化设备有限公司详细介绍&#xff1a;粗效过滤器主要技术参数 什么叫铝网初效过滤器及金属网过滤器?铝网初效过滤器也叫金属网过滤器也可叫GH金属孔网过滤器只是人们的叫法不同。 铝网初效过滤器均具安全&#xff0c;坚固&#xff0c;耐高温,耐酸碱,之特性.一般应…

WebDAV之葫芦儿•派盘+Obsidian笔记

Obsidian 支持WebDAV方式连接葫芦儿派盘。 还在为大量的日记、笔记管理而烦恼?推荐一款可以作为第二大脑、支持双向链接、基于Markdown文件的本地知识管理软件。 Obsidian是一款全设备端的笔记软件,让用户能够非常方便的进行笔记上面的记录,纸张无限边界,想到哪,写到哪,不…