实现单链表的基本操作(力扣、牛客刷题的基础笔试题常客)

news2024/11/25 6:33:34

本节来学习单链表的实现。在链表的刷题中,单链表占主导地位,很多oj题都在在单链表的背景下进行;而且很多链表的面试题都是以单链表为背景命题。所以,学好单链表的基本操作很重要

目录

一.介绍单链表

1.链表及单链表

2.定义一个链表

二.实现单链表的功能

1.插入数据

2.打印链表

3.删除数据

4.查找某个元素

5.检测链表大小

6.完整的链表


一.介绍单链表

1.链表及单链表

(1)什么是链表

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

例如下面的这种数据结构,由一个个的结点组成。每个结点中存储着数据,又存储着其他结点的地址。

(2)什么是单链表

链表有三个特点:单向和双向、带头和不带头、循环和不循环;三三组合起来,一共8种情况(比如单向不带头不循环链表,就是本节的单向链表)。

单向和双向:单向表示每个结点只存后一个结点的地址;双向表示每个结点存放前后结点的地址。

区别:双向链表可以知道某个结点的前面结点,单向链表只能找到它后面的结点

带头和不带头:带头的链表会有一个固定的头结点(也称为哨兵位),所有操作都在头结点后面操作;不带头的链表则会自己定义一个结点用来表示当前链表的头,该结点也称为头结点,但是该头结点的位置是不停的变化的

区别:带头的链表,头结点的位置是固定不变的 

循环和非循环:循环的链表,最后一个结点存放第一个结点的地址,非循环的链表最后一个结点存放的地址为null,也就是不指向任何的结点

区别:循环的链表也可以称为环,链表的遍历不会结束,而非循环会结束

本节介绍的单链表为:单向不带头非循环的链表,如下图的链表

 

2.定义一个链表

(1)定义一个链表(单独一个类)

public class MyList{
  
}

(2)将链表的功能包装称接口

public interface IList {

    public void addFirst(int data);//头插
    public void addLast(int data);//尾插
    public void add(int index,int data);//任意位置插入
    public boolean contains(int key);//检查key元素是否存在
    public void remove(int key);//删除第一个key
    public void removeAll(int key);//删除所有key
    public int size();//求链表的长度
    public void clear();//清空链表
    public void show();//打印链表

}

(3)链表实现该接口并重写方法

public class MyList implements IList{
  
    @Override
    public void addFirst(int data) {
        //头插
    }

    @Override
    public void addLast(int data) {
        //尾插    
    }

    @Override
    public void add(int index, int data) {
        //指定位置插入
    }

    @Override
    public boolean contains(int key) {
        //查找key元素
    }

    @Override
    public void remove(int key) {
        //删除第一个key     
    }

    @Override
    public void removeAll(int key) {
        //删除所有key结点
     
    }

    @Override
    public int size() {
        //求链表大小
    
    }

    @Override
    public void clear() {
        //清空链表
    }

    @Override
    public void show() {
       
    }
}

(4)定义链表的结点

我们将结点定义成一个内部类:包括数据域(data)和next域(存放下一个结点的地址)

public class MyList implements IList{

    class ListNode {
        public int data;//数据域
        public ListNode next;

        public ListNode(int data) {
            this.data = data;
        }
     }
    public ListNode head;//定位头的位置
  
    @Override
    public void addFirst(int data) {
        //头插
    }

    @Override
    public void addLast(int data) {
        //尾插    
    }

    @Override
    public void add(int index, int data) {
        //指定位置插入
    }

    @Override
    public boolean contains(int key) {
        //查找key元素
    }

    @Override
    public void remove(int key) {
        //删除第一个key     
    }

    @Override
    public void removeAll(int key) {
        //删除所有key结点
     
    }

    @Override
    public int size() {
        //求链表大小
    
    }

    @Override
    public void clear() {
        //清空链表
    }

    @Override
    public void show() {
       
    }
}

这样一个链表的基本结构就定义完成,接下来就是实现链表的一些功能即可

二.实现单链表的功能

链表的功能大概有以下几种,插入数据(头插,尾插,随机插入),打印链表的数据,删除链表的数据,查找某个元素和监测链表的大小。

接下来我们慢慢了解

1.插入数据

(1)头插法

下面的是头插法的代码

public void addFirst(int data) {
        //头插
        ListNode node = new ListNode(data);

        node.next = head;//
        head = node;
}

第一步:需要创造一个新的结点出来

第二步:连接链表;分为两种情况:第一种是空链表的时候(一个结点都没有的时候),另一种是非空链表的时候。以上的代码都满足

 

(2)尾插法

尾插法的逻辑稍微复杂一点点,同样需要考虑链表的两种情况;空链表时需要单独讨论,而当链表非空时,则需要找链表的尾巴。

第一步:创造新的结点

ListNode node = new ListNode(data);

第二步:考虑空链表的情况

if(head == null) {
      head = node;
      return;
}

 第三步:找链表的尾巴

这个注意,我们不能移动head,head需要保持不动,不然链表的头将不见。

ListNode cur = head;
while(cur.next!=null) {
     cur = cur.next;
}

第四步:将新的结点连接到尾结点后面即可

cur.next = node;

完整的尾插法代码:

 public void addLast(int data) {
        //尾插
        ListNode node = new ListNode(data);
        if(head == null) {
            head = node;
            return;
        }
        ListNode cur = head;
        while(cur.next!=null) {
            cur = cur.next;
        }
        cur.next = node;
    }

(3)随机位置插入

 public void add(int index, int data)

随机位置插入,需要用户指定插入的位置和值;所以需要讨论以下的情况

第一步:创造新的结点

ListNode node = new ListNode(data);

第二步:检查用户指定插入的位置是否合法

 if(index < 0 || index > size()) {
      System.out.println("插入位置不合法");
      return;
}

第三步:检查链表是否为空

 if(head == null) {
      head = node;
      return;
}

第四步:检查是否为头插法

如果是头插法就直接调用头插法就可以,不需要再浪费时间去写这个代码。尾插法需要单独考虑,和普通插入当成一种即可。

 if(index == 0) {
       addFirst(data);
       return;
}

第五步:找到插入位置的前一个结点

在单链表中,只能找前一个的位置,如果找的是后一个位置,将无法获取前结点的信息

int count = index-1;
ListNode cur = head;
while(count > 0) {
      cur = cur.next;
      count--;
}

此时的cur指向插入位置的前一个结点

第六步:插入新的结点

插入新的结点都是要求连接后面,再连接前面

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

完整代码:

 public void add(int index, int data) {
        //指定位置插入
        ListNode node = new ListNode(data);
        ListNode cur = head;
        //1.检查位置是否合法
        if(index < 0 || index > size()) {
            System.out.println("插入位置不合法");
            return;
        }
        //2.空链表情况
        if(head == null) {
            head = node;
            return;
        }
        //3.头插法情况(index = 0)
        if(index == 0) {
            addFirst(data);
            return;
        }
        //4.找前位置
        int count = index-1;
        while(count > 0) {
            cur = cur.next;
            count--;
        }
        //5.插入数据
        node.next = cur.next;
        cur.next = node;
    }

2.打印链表

打印链表比较简单,就是需要将链表遍历一遍即可,同样的道理,不能动head

  public void show() {
        //打印
        ListNode cur = head;
        while(cur!=null) {
            System.out.print(cur.data+" ");
            cur = cur.next;
        }
    }
3.删除数据

删除数据分为两种:一种是删除一个key结点;另一种是删除所以的key结点。删除的参数都是根据结点的值来判断

下面我们一起来查看这两种的代码和思路

(1)删除第一个key结点

第一步:判断是否为空链表

如果是空链表的情况,无论如果都无法删除,直接返回就好

 if(head == null) {
       System.out.println("链表为空,无法删除");
       return;
 }

第二步:单独考虑头结点是否是目标结点

 if(head.data == key) {
      head = head.next;
      return;
}

第三步:一边遍历链表一边删除结点

这里只需要遍历一遍就可以完成,不需要再找什么前结点;下面的代码是判断下一个结点是否为删除的结点,如果是,直接断开连接就好,不是则继续往下走

 ListNode cur = head;
       while(cur.next!=null) {
         if(cur.next.data==key) {
             cur.next = cur.next.next;//删除操作
             return;
         }else {
              cur = cur.next;
        }
  }

第四步:链表中不存在key结点

System.out.println("没有该结点,删除失败!");

完整代码:

public void remove(int key) {
        //删除第一个key
        //1.空表
        if(head == null) {
            System.out.println("链表为空,无法删除");
            return;
        }
        //2.头结点就是目标结点
        if(head.data == key) {
            head = head.next;
            return;
        }
        //3.正常删除
        ListNode cur = head;
        while(cur.next!=null) {
            if(cur.next.data==key) {
                cur.next = cur.next.next;//删除操作
                return;
            }else {
                cur = cur.next;
            }
        }
        //4.找不到
        System.out.println("没有该结点,删除失败!");
    }

(2)删除所有的key结点

删除所有的结点是在删除一个key结点的前提下改进即可,删除一个key结点时,直接返回了,然后删除所有的key,我们不返回就好。下面分三步

第一步:判断是否为空链表

 if(head == null) {
      System.out.println("链表为空,无法删除");
      return;
 }

第二步:删除头结点后面的所有key结点

下面的代码无法删除头结点,所以头结点的情况单独考虑并且放在最后面 

        ListNode cur = head.next;//需要删除的结点
        ListNode prev = head;//删除结点的前一个
        //2.删除中间的结点
        while(cur != null) {
            if(cur.data != key) {
                prev = cur;//让prev走到cur的位置
                cur = cur.next;//cur往下走
            }else {
              prev.next = cur.next;
              cur = cur.next;
            }

        }

第三步:删除头结点

if(head.data == key) {
    head = head.next;
}

完整代码:

 public void removeAll(int key) {
        //删除所有key结点
        //1.检查空链表情况
        if(head == null) {
            System.out.println("链表为空,无法删除");
            return;
        }

        ListNode cur = head.next;//需要删除的结点
        ListNode prev = head;//删除结点的前一个
        //2.删除中间的结点
        while(cur != null) {
            if(cur.data != key) {
                prev = cur;//让prev走到cur的位置
                cur = cur.next;//cur往下走

            }else {
              prev.next = cur.next;
              cur = cur.next;
            }

        }
        //3.删除头结点
        if(head.data == key) {
            head = head.next;
        }

    }
4.查找某个元素

查找某个元素是否存在时,根据结点的值去查找

如果结点存在,返回true;不存在则返回false

 public boolean contains(int key) {
        //查找key元素
        ListNode cur = head;
        while(cur!=null) {
            if(cur.data == key) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }
5.检测链表大小

求链表的结点个数只需要遍历一遍链表即可,每走到一个结点的位置,计数器就家加1,最后返回即可

  public int size() {
        //求链表大小
        ListNode cur = head;
        int count = 0;
        while(cur!=null) {
            count++;
            cur = cur.next;
        }
        return count;
    }
6.完整的链表
public class MyList implements IList{
    class ListNode {
        public int data;//数据域
        public ListNode next;

        public ListNode(int data) {
            this.data = data;
        }
    }
    public ListNode head;//定位头的位置

    @Override
    public void addFirst(int data) {
        //头插
        ListNode node = new ListNode(data);

        node.next = head;//
        head = node;
    }

    @Override
    public void addLast(int data) {
        //尾插
        ListNode node = new ListNode(data);
        if(head == null) {
            head = node;
            return;
        }
        ListNode cur = head;
        while(cur.next!=null) {
            cur = cur.next;
        }
        cur.next = node;

    }

    @Override
    public void add(int index, int data) {
        //指定位置插入
        ListNode node = new ListNode(data);
        //1.检查位置是否合法
        if(index < 0 || index > size()) {
            System.out.println("插入位置不合法");
            return;
        }
        //2.空链表情况
        if(head == null) {
            head = node;
            return;
        }
        //3.头插法情况(index = 0)
        if(index == 0) {
            addFirst(data);
            return;
        }
        //4.找前位置
        int count = index-1;
        ListNode cur = head;
        while(count > 0) {
            cur = cur.next;
            count--;
        }
        //5.插入数据
        node.next = cur.next;
        cur.next = node;
    }

    @Override
    public boolean contains(int key) {
        //查找key元素
        ListNode cur = head;
        while(cur!=null) {
            if(cur.data == key) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

    @Override
    public void remove(int key) {
        //删除第一个key
        //1.空表
        if(head == null) {
            System.out.println("链表为空,无法删除");
            return;
        }
        //2.头结点就是目标结点
        if(head.data == key) {
            head = head.next;
            return;
        }
        //3.正常删除
        ListNode cur = head;
        while(cur.next!=null) {
            if(cur.next.data==key) {
                cur.next = cur.next.next;//删除操作
                return;
            }else {
                cur = cur.next;
            }
        }
        //4.找不到
        System.out.println("没有该结点,删除失败!");
    }

    @Override
    public void removeAll(int key) {
        //删除所有key结点
        //1.检查空链表情况
        if(head == null) {
            System.out.println("链表为空,无法删除");
            return;
        }

        ListNode cur = head.next;//需要删除的结点
        ListNode prev = head;//删除结点的前一个
        //2.删除中间的结点
        while(cur != null) {
            if(cur.data != key) {
                prev = cur;//让prev走到cur的位置
                cur = cur.next;//cur往下走

            }else {
              prev.next = cur.next;
              cur = cur.next;
            }

        }
        //3.删除头结点
        if(head.data == key) {
            head = head.next;
        }

    }

    @Override
    public int size() {
        //求链表大小
        ListNode cur = head;
        int count = 0;
        while(cur!=null) {
            count++;
            cur = cur.next;
        }
        return count;
    }

    @Override
    public void clear() {
        //清空链表
        head = null;
    }

    @Override
    public void show() {
        //打印
        ListNode cur = head;
        while(cur!=null) {
            System.out.print(cur.data+" ");
            cur = cur.next;
        }
    }
 

}

接口:

public interface IList {

    public void addFirst(int data);//头插
    public void addLast(int data);//尾插
    public void add(int index,int data);//任意位置插入
    public boolean contains(int key);//检查key元素是否存在
    public void remove(int key);//删除第一个key
    public void removeAll(int key);//删除所有key
    public int size();//求链表的长度
    public void clear();//清空链表
    public void show();//打印链表

}

实例化链表对象:

 public static void main(String[] args) {
        MyList myList = new MyList();//实例化链表对象
        myList.addLast(8);
        myList.addLast(1);
        myList.addLast(4);
        myList.show();
    }

本节单链表的实现就到这里了,快去自己模拟实现一下吧!

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

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

相关文章

【设计模式--结构型--代理模式】

设计模式--结构型--代理模式 代理模式概述结构静态代理案例&#xff1a;卖车票jdk动态代理cglib代理三种代理对比优缺点使用场景 代理模式 概述 由于某些原因需要给某对象提供一个代理以控制该对象的访问。这时&#xff0c;访问对象不适合或者不能直接引用目标对象&#xff0…

Java集合大家族(学习推荐版,通俗易懂)

4.集合&#xff08;ArrayList&#xff09;、其他集合框架及容器遍历方式 1.ArrayList 注意&#xff1a;索引从0开始 该集合可以添加任意类型的数据&#xff0c;要约束添加数据的类型&#xff0c;需用泛型约束&#xff08;jdk1.7开始支持泛型&#xff09; 删除遍历集合方式1&a…

卡尔曼(kalman)滤波学习测试例

下面两套代码一套是python&#xff0c;一套是matlab&#xff0c;效果是一样的。 PYTHON import numpy as np import matplotlib.pyplot as pltt np.arange(1, 1001) nsig 5 * np.sin(0.01 * t) np.random.rand(len(t)) np.random.randn(len(t)) 5 * np.cos(0.05 * t np.…

mac电脑m1 arm架构安装虚拟机教程

1、准备一台虚拟机&#xff0c;安装CentOS7 常用的虚拟化软件有两种&#xff1a; VirtualBoxVMware 这里我们使用VirtualBox来安装虚拟机&#xff0c;下载地址&#xff1a;Downloads – Oracle VM VirtualBox 001 点击安装 002 报错&#xff1a;he installer has detected an…

js禁止打开控制台,如何强行打开控制台?

当我在查看某个网站的源码时&#xff0c;按F12会跳转到百度页面&#xff0c;或者先打开F12再输入网站也会进入到百度首页。 首先我们要关闭控制台进入到这个网站的首页&#xff0c;然后右键查 看网站的源码。 1.找到这个js文件&#xff0c;点进去。 2.点击这个js文件之后&a…

sensitive-word 敏感词之 DFA 双数组实现源码学习

拓展阅读 敏感词工具实现思路 DFA 算法讲解 敏感词库优化流程 java 如何实现开箱即用的敏感词控台服务&#xff1f; 各大平台连敏感词库都没有的吗&#xff1f; v0.10.0-脏词分类标签初步支持 v0.11.0-敏感词新特性&#xff1a;忽略无意义的字符&#xff0c;词标签字典 …

SpringSecurity深度解析与实践(2)

目录 引言1.Springboot结合SpringSecurity用户认证流程1.1 配置pom文件1.2.配置application.yml 2.自定义MD5加密3.BCryptPasswordEncoder密码编码器4.RememberMe记住我的实现5.CSRF防御5.1.什么是CSRF 引言 上篇网址 1.Springboot结合SpringSecurity用户认证流程 1.1 配置p…

【Date对象】js中的日期类型Date对象的使用详情

&#x1f601; 作者简介&#xff1a;一名大四的学生&#xff0c;致力学习前端开发技术 ⭐️个人主页&#xff1a;夜宵饽饽的主页 ❔ 系列专栏&#xff1a;JavaScript小贴士 &#x1f450;学习格言&#xff1a;成功不是终点&#xff0c;失败也并非末日&#xff0c;最重要的是继续…

RocketMQ系统性学习-RocketMQ高级特性之消息存储在Broker的文件布局

&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308; 【11来了】文章导读地址&#xff1a;点击查看文章导读&#xff01; &#x1f341;&#x1f341;&#x1f341;&#x1f341;&#x1f341;&#x1f341;&#x1f3…

2023.12.19 关于 Redis 通用全局命令

目录 引言 Redis 全局命令 SET & GET KEYS EXISTS DEL EXPIRE TTL TYPE redis 引入定时器高效处理过期 key 基于优先级队列方式 基于时间轮方式 引言 Redis 是根据键值对的方式存储数据的必须要进入 redis-cli 客户端程序 才能输入 redis 命令 Redis 全局命令 R…

手写单链表(指针)(next域)附图

目录 创建文件&#xff1a; 具体实现&#xff1a; 首先是头插。 注意&#xff1a;一定要注意&#xff1a;再定义tmp时&#xff0c;要给它赋一个初始值&#xff08;推荐使用 new list_next) 接着是尾插&#xff1a; 随后是中间插&#xff1a; 然后是最简单的改值&#xf…

【Linux】权限篇(一)

权限篇目录 1. 前言2. shell3. 权限介绍3.1 什么是权限3.2 权限的本质3.3 Linux中的用户3.4 Linux中文件的权限 1. 前言 在之前的博客中已经学习了一些相关的操作&#xff0c;这次来分享的是与Linux的权限有关的一些笔记。 在正片开始之前&#xff0c;先来讲讲外壳(shell)。 …

实体店如何进行线上线下统一管理

随着互联网的普及和消费者行为的改变&#xff0c;实体店不再满足于单一的线下销售模式&#xff0c;开始探索线上线下的融合。本文将介绍如何通过搭建小程序和乔拓云平台&#xff0c;实现实体店的线上线下统一管理。 实体店可以通过微信小程序搭建自己的线上平台&#xff0c;实现…

【贪心算法】之 买股票的最佳时机(中等题)

1.买卖股票的最佳时机 把利润分解为每天为单位的维度&#xff0c;而不是从0天到第3天整体去考虑&#xff01; 那么只收集正利润就是贪心所贪的地方&#xff01; 局部最优&#xff1a;收集每天的正利润&#xff0c;全局最优&#xff1a;求得最大利润。 局部最优可以推出全局最…

一篇文章带你进阶CTF命令执行

以下的命令是为了方便以后做题时方便各位读者直接来这里复制使用&#xff0c;刚开始还请先看完这篇文章后才会懂得下面的命令 ?ceval($_GET[shy]);&shypassthru(cat flag.php); #逃逸过滤 ?cinclude%09$_GET[shy]?>&shyphp://filter/readconvert.base64-…

基于MLP完成CIFAR-10数据集和UCI wine数据集的分类

基于MLP完成CIFAR-10数据集和UCI wine数据集的分类&#xff0c;使用到了sklearn和tensorflow&#xff0c;并对图片分类进行了数据可视化展示 数据集介绍 UCI wine数据集&#xff1a; http://archive.ics.uci.edu/dataset/109/wine 这些数据是对意大利同一地区种植的葡萄酒进…

Navicat里放大、缩小字体的快捷方法

我是偶然误触键盘把字体缩小了&#xff0c;研究以后发现的这个快捷键&#xff0c;分享给大家。 方法&#xff1a;按住【CtrlShift】组合键&#xff0c;再拖动鼠标滚轮&#xff0c;就可以缩放字体了。 缩小效果&#xff1a; 放大效果&#xff1a;

在 TensorFlow 中启用 Eager Execution

TensorFlow 是一个端到端的开源机器学习平台&#xff0c;可以更轻松地构建和部署机器学习模型。TensorFlow 应用程序使用一种称为数据流图的结构。默认情况下&#xff0c;在 TensorFlow 1.0 版中&#xff0c;每个图形都必须在 TensorFlow 会话中运行&#xff0c;这只允许一次运…

C# Onnx Yolov8 Detect 物体检测 多张图片同时推理

目录 效果 模型信息 项目 代码 下载 C# Onnx Yolov8 Detect 物体检测 多张图片同时推理 效果 模型信息 Model Properties ------------------------- date&#xff1a;2023-12-18T11:47:29.332397 description&#xff1a;Ultralytics YOLOv8n-detect model trained on …

UE4 UE5 一直面向屏幕

一直面相屏幕&#xff0c;方法很简单 新建一个蓝图&#xff0c;如下添加组件&#xff1a; 蓝图如下&#xff1a; Rotation Actor &#xff1a;需要跟随镜头旋转的物体 Update&#xff1a;一个timeline&#xff08;替代event tick 只是为了循环&#xff09; Timeline&#xff…