链表(1)-------数据结构

news2025/2/1 16:40:54

链表(重点):

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

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

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

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

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

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

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

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

单向带头循环的链表:链表的最后一个节点的next域指向傀儡节点的下一个节点

单向不带头循环的链表:链表的最后一个节点的next域指向第一个节点,也就是head节点

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

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

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

2)实现一个链表的类

我们链表的头部节点和尾部节点都是属于链表中的一部分,所以说我们定义的head和tail都是linkedlist中的一个属性

下面的方法:一定要记住,以后做链表的题的时候,一定要先绑定后面

1)头插法

 if(this.head==null){
            this.head=node;
        }else{
            node.next=this.head;
            this.head=node;
        }

2)尾插法:有两种写法,是按照存储的顺序来进行存储的

所以说使用尾插法进行插入元素,一定要判断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;
       }

2.1)尾差的顺序就是按照存储的位置进行存放的
我们进行寻找尾节点的方式,让current指向头结点,不断遍历如果current.next为空,
说明current指向的节点是尾巴结点,我们此时就可以新创建结点进行插入
2.2)如果说此时current为一开始指向的头结点就是空,链表里面一个元素也没有,循环进不去current一直为空,如果说此时你让current.next=node,此时就会发生空指针异常

3)求单链表的长度

4)查看某一个关键字key是否在单链表里面

package OperateNode;

public class MyLinkedList {
    public Node head;
    public Node tail;
    //遍历整个链表
    public void Display(Node head){
        Node current=this.head;
        while(current!=null){
//这里面的条件不能写成current.next!=null,因为这会少打印一个节点.因为current.next为空了
那么就满足循环退出条件,所以说此时就不会进入到循环,因此也就少打印出了一个节点
            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;
    }
//继续进行尾插法
    public void addLast(int data){//我们在这里面是寻找最后一个节点的位置
        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);
    }
}

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

1)咱们的这种思想就是说比如说插入到了2号位置,那么就新建一个节点保存要插入的数据,将原有的2号位置的元素以及2号位置之后的元素向后移动

2)所以比如说我们要往4号位置插入,那么就应该先让指针走到1号位置

 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走到了pos-1的位置的时候会进行退出
          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/104327.html

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

相关文章

Opencv(C++)笔记--Canny边缘检测算法

目录 1--算法原理 2--Opencv API 3--代码实例 1--算法原理 具体原理讲解可参考博客1 和 博客2&#xff0c;算法主要步骤如下&#xff1a; ① 使用高斯模糊&#xff08;高斯滤波&#xff09;去除噪声&#xff1b; ② 进行灰度转换&#xff0c;转换为灰度图&#xff1b; ③ 计…

数据结构---动态规划

动态规划爬楼梯问题解法1第一步第二步第三步JAVA实现解法2问题建模最优子结构边界状态转移公式求解问题递归JAVA实现备忘录算法JAVA实现解法三JAVA实现&#xff08;斐波那契数列&#xff09;国王和金矿一个错误的解法排列组合解法JAVA实现动态规划爬楼梯问题 有一座高度是10级…

rk3568 添加gc2053摄像头驱动

在设备树文件代码中添加 gc2053 设备节点 &i2c2 {status "okay";pinctrl-0 <&i2c2m1_xfer>;/* split mode: lane0/1 */gc2053: gc205337 {status "okay";compatible "galaxycore,gc2053";// 需要与驱动中的匹配字符串一致reg …

高级篇-rabbitmq的高级特性

1.消息可靠性 三种丢失的情形&#xff1a; 1.1 生产者确认机制 启动MQ 创建Queues&#xff1a; 两种Callback: 1.ReturnCallback:全局callback 2.ComfirmCallback: 发送信息时候设置 Testpublic void testSendMessage2SimpleQueue() throws InterruptedException {// 1.…

第八章练习题-3

目录 第十三题 题目 Student类 Teacher类 main类 老师的代码 Student类 Teacher类 main类 第七问&#xff1a;定义多态数组 main类 运行结果 问题 原因 解决办法 老师代码&#xff1a;main类 结果 第八问 运行结果 第十四题 第十五题 题目 方法的多态…

HFSS使用经验三

目录 一、如何量取HFSS某一点的空间位置 二、如何快速的切换视图 三、HFSS中绘制圆柱体 四、如何修改HFSS的仿真线程数量 五、HFSS中如何选取挖空的面 六、HFSS中如何实现参数扫描 一、如何量取HFSS某一点的空间位置 右击可以点击Measure按键 鼠标放到特殊点&#xff0c…

2023年申请发明专利的重要性和注意问题。

随着对知识产权意思的逐步提高&#xff0c;企业对知识产权越来越关心。知识产权包括专利权、商标权和著作权。专利包含发明专利专利、实用新型专利、外观设计专利。其中发明专利的申请难度最大&#xff0c;含金量最高。根据小编申请发明专利方面20年的经验&#xff0c;简单介绍…

【Windows|WSL|Ubuntu|VSCode】流程记录|坑点模糊回忆

无限踩坑&#xff0c;悲惨回忆&#xff0c;又似乎毫无意义&#xff1f; 1.安装WSL 官方文档&#xff1a;Install WSL | Microsoft Learn 简单来说&#xff0c;管理员身份运行PowerShell wsl --install 通常需要wsl2&#xff0c;得益于其优势&#xff0c;通过 PowerShell w…

用ERP系统做数据管理对企业有什么好处?

ERP系统数据管理解决方案是以业务为主导的工具&#xff0c;为所有的业务流程和操作创建一个单一的主记录&#xff0c;包括来自各种内部和外部应用和来源的人员和系统。 大多数企业采用不同的系统&#xff0c;包含客户、产品、销售、交易等信息数据存储在许多不同的地方&#…

葵花宝典之C语言冷知识(二)

目录 &#x1f693;&#xff08;一&#xff09;图形的打印 &#x1f699;判断类型 &#x1f68c;逻辑简单易找规律型。 &#x1f68c;存在坐标规律的图案打印 &#x1f693;&#xff08;二&#xff09;中值的表达形式 &#x1f699;(xy)/2 表达错误的原因 &#x1f699;有符号数…

Python编程课程好学吗?能学会吗?

Python编程课程好学吗&#xff1f;能学会吗&#xff1f;Python是一种计算机程序设计语言&#xff0c;一种面向对象的动态类型语言&#xff0c;最初被设计用于编写自动化脚本(shell)&#xff0c;随着版本的不断更新和语言新功能的添加越来越多被用于独立的大型项目的开发。 编程…

mvn和npm的那些事

1 mvn简介 mvn就好比java中的maven,用于管理包版本,mvn用于管理node版本,而npm来源于node中,比如用于拉取仓库中的依赖包,在构建使用项目时可以选择指定的版本,从而避免编译出错运行失败等问题 Node.js17或更高版本中出现Error: error:0308010C:digital envelope routine…

CSDN如何使用Markdown编辑器

这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注…

Pytest自动化测试 - 完美结合Allure

简介 Allure Framework是一种灵活的、轻量级、多语言测试报告工具。 不仅可以以简洁的网络报告形式非常简洁地显示已测试的内容&#xff0c; 而且还允许参与开发过程的每个人从日常执行中提取最大程度的有用信息和测试。 从开发/测试的角度来看&#xff1a; Allure报告可以…

四大蓝牙天线设计方式

https://www.elecfans.com/d/686538.html 一直以来&#xff0c;无论是智能手机&#xff0c;还是笔记本电脑&#xff0c;亦或是平板电脑&#xff0c;蓝牙都是智能设备的标配。随着移动互联网的发展&#xff0c;现在涌现出大量的智能可穿戴设备&#xff0c;而支撑这些应用的发展不…

Spring AOP源码解析——专治你不会看源码的坏毛病!

虽然现在大厂内卷现象泛滥&#xff0c;而且996的传统依旧肆虐。但没有哪位程序员能架得住互联网大厂的高薪职位诱惑。特别是我还有一位在阿里工作7年多的老表&#xff0c;在其耳旁风之下&#xff0c;不断将大厂描绘的美丽风景刻画在我脑海中&#xff0c;也让我一直有着想进大厂…

架构设计(八):数据库的水平扩展和垂直扩展

架构设计&#xff08;八&#xff09;&#xff1a;数据库的水平扩展和垂直扩展 作者&#xff1a;Grey 原文地址&#xff1a; 博客园&#xff1a;架构设计&#xff08;八&#xff09;&#xff1a;数据库的水平扩展和垂直扩展 CSDN&#xff1a;架构设计&#xff08;八&#xf…

Redis6入门到实战------ 三、常用五大数据类型(列表(List)、集合(Set)、哈希(Hash)、Zset(sorted set))

3 Redis列表(List) 3.1 简介 单键多值 Redis 列表是简单的字符串列表&#xff0c;按照插入顺序排序。你可以添加一个元素到列表的头部&#xff08;左边&#xff09;或者尾部&#xff08;右边&#xff09;。 它的底层实际是个双向链表&#xff0c;对两端的操作性能很高&#x…

组件技术--设计--MVC模式+DAO+MySQL+jsp+servlet 简单的购物车案例

MVC模式DAOMySQLjspservlet 简单的购物车案例题外话购物车案例需求核心系统组成Javaweb项目框架基本思想核心代码DaoBookDaolistenerSessionListenerservletAddServletInitServletRemoveServletvoBook.jspbuyForm.jspshowAllBook.jspshowCart.jsptargetpom.xmlWEB-INFweb.xmlli…

python绘图——坐标轴

1. 2D坐标轴 1.1 绘制简单的曲线 import matplotlib.pyplot as plt import numpy as np xnp.linspace(-1,1,50)#-1到1中画50个点 yx**2 plt.plot(x,y,colorgreen) plt.tick_params(axisx,colorsblue) plt.tick_params(axisy,colorsred) plt.show()作图&#xff1a; 1.2 坐标…