【数据结构】链表(单链表实现 + 详解 + 原码)

news2024/9/22 7:34:02

🎇🎉🎉🎉点进来你就是我的人了
博主主页:🙈🙈🙈戳一戳,欢迎大佬指点!
人生格言: 当你的才华撑不起你的野心的时候,你就应该静下心来学习!
欢迎志同道合的朋友一起加油喔 💪💪💪
目标梦想:进大厂,立志成为一个牛掰的Java程序猿,虽然现在还是一个🐒嘿嘿
谢谢你这么帅气美丽还给我点赞!比个心

文章目录

  • 一、ArrayList的问题及思考(前言)
    • 1. 插入和删除元素
    • 2. 扩容
    • 3. 思考
  • 二、链表
    • 1. 链表的概念及结构
    • 2. 结点
    • 3. 利用链表存储数据
  • 三、链表的分类
    • 1. 单向或双向
    • 2. 带头或不带头
    • 3. 循环或非循环
    • 4. 单向带头非循环
    • 5. 掌握
  • 四、链表的实现
    • 1. 如何把一个一个结点 串起来
    • 2. 打印链表
    • 3. 求当前链表有多少个结点
    • 4. 当前链表是否存在 key
    • 5. 头插法
    • 6. 尾插法
    • 7. 在 index 位置 添加数据
    • 8. 删除给定的数字 Key
    • 9. 删除所有的关键字 Key
    • 10. 清空链表
  • 五、完整代码
    • 1. 异常类
    • 2. 接口
    • 3. 自己实现的MySingleList类

一、ArrayList的问题及思考(前言)

1. 插入和删除元素

ArrayList底层使用连续的空间,任意位置插入或删除元素时,需要将该位置后序元素整体往前或者往后搬移,故时间复杂度为O(N)

2. 扩容

  • 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
  • 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继 续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    // ...
//  默认容量是10
    private static fi nalint
    DEFAULT_CAPACITY =10;

    //...
// 数组 :用来存储元素
    transient Object[] elementData; // non-private to simplify nested class access
    // 有效元素个数
    private int size;
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
        }
    }
// ...
}

3. 思考

如何解决以上问题呢?

  1. 随用随取
  2. 插入 或者 删除元素 是否可以不移动元素
  3. Java 集合中又引入了 LinkedList,即链表结构

二、链表

1. 链表的概念及结构

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

类似于火车的一节一节车厢

在这里插入图片描述

2. 结点

在这里插入图片描述

3. 利用链表存储数据

(单向、非循环、不带头链表)
在这里插入图片描述
head(头结点)相当于是一个变量,存放了 0x112 这个地址
是一个引用指向第一个结点,通过 头结点 能够找到其他结点

注意:
物理上 (内存) 不一定连续
逻辑上连续的一种结构

三、链表的分类

在这里插入图片描述

1. 单向或双向

在这里插入图片描述

2. 带头或不带头

在这里插入图片描述

3. 循环或非循环

在这里插入图片描述

4. 单向带头非循环

这里的 head 永远表示这个结点,是这个链表的 头结点
在这里插入图片描述

5. 掌握

无头单向非循环链表:结构简单 ,一般不会单独用来存数据。
实际中更多是作为其他数据结构的子结构,如 哈希桶、图的邻接表等等。
另外这种结构在笔试面试中出现很多。
无头双向链表:在Java的集合框架库中LinkedList底层实现就是无头双向循环链表

四、链表的实现

// 1、无头单向非循环链表实现
public class SingleLinkedList {
    //头插法
    public void addFirst(int data) {
    }

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

    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index, int data) {
    }

    //查找是否包含关键字key是否在单链表当中
    public boolean contains(int key) {
        return false;
    }

    //删除第一次出现关键字为key的节点
    public void remove(int key) {
    }

    //删除所有值为key的节点
    public void removeAllKey(int key) {
    }

    //得到单链表的长度
    public int size() {
        return -1;
    }

    public void clear() {
    }

    public void display() {
    }
}

1. 如何把一个一个结点 串起来

在这里插入图片描述

  • node1的下一个结点是node2,怎么用代码实现?
node1.next = node2;
  • 代码实例
public void createList(){
        //在这里面 实例化几个结点
        ListNode node1 = new ListNode(12);
        ListNode node2 = new ListNode(23);
        ListNode node3 = new ListNode(34);
        ListNode node4 = new ListNode(45);

        node1.next = node2;
        node2.next = node3;
        node3.next = node4;

        this.head = node1;//头结点
    }

在这里插入图片描述
在这里插入图片描述

2. 打印链表

  • 怎么从第一个结点走到第二个结点
head = head.next;
  • 什么时候算把 结点都遍历完
//判断条件
head != null;

在这里插入图片描述

    /**
     * 打印链表
     */
    @Override
    public void display() {
        ListNode cur = head;
//        System.out.println("val值:");
        while (cur != null){
            System.out.print(head.val + " ");
            head = head.next;
        }
    }

注意点:
定义一个 cur ,让头结点赋值给 cur
这样当链表遍历完时,只是cur为空,头结点还在

3. 求当前链表有多少个结点

 /**
     * 求当前链表有多少个结点
     * @return
     */
    @Override
    public int size() {
        ListNode cur = head;
        int count = 0;
        while (cur != null){
            cur = cur.next;
            count++;
        }
        return count;
    }

4. 当前链表是否存在 key

/**
     * 当前链表是否存在 key
     * @param key
     * @return
     */
    @Override
    public boolean contains(int key) {
        ListNode cur = head;
        while (cur != null){
            if (cur.val == key){
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

5. 头插法

/**
     * 头插法
     * @param data
     */
    @Override
    public void addFirst(int data) {
        //首先要把 数据存放到一个结点里面
        ListNode node = new ListNode(data);
        //注意:
        //插入数据的时候,一定要先绑后面
        //现在 head 指向头结点的地址
        node.next = head;
        head = node;
        //包含链表为空的情况
    }

6. 尾插法

/**
     * 尾插法
     * @param data
     */
    @Override
    public void addLast(int data) {
        //首先要把 数据存放到一个结点里面
        ListNode node = new ListNode(data);
        //首先,要判断链表是否为空
        if (head == null){
            head = node;
        }else {
            //要找到最后一个结点
            ListNode cur = head;
            while (cur.next != null){
                cur = cur.next;
            }
            cur.next = node;
        }
    }

7. 在 index 位置 添加数据

  • 要在 index 位置插入数据,就需要把 cur 走到 index 的前一个位置

在这里插入图片描述

/**
     * 在 index 位置 添加数据
     *
     * @param index
     * @param data
     */
    @Override
    public void addIndex(int index, int data) throws IndexException {
        //还要判断 index 是否合法
        if (index < 0 || index > this.size()) {
            throw new IndexException("index位置不合法:" + index);
        }
        ListNode node = new ListNode(data);
        if (head == null){
            head = node;
            return;
        }

        if (index == 0) {
            addFirst(data);
        }
        if (index == this.size()) {
            addLast(data);
        }

        //处理中间插入
        //要找到index 的前一个位置
        ListNode cur = searchPrevIndex(index);
        node.next = cur.next;
        cur.next = node;
    }

    private ListNode searchPrevIndex(int index){
        ListNode cur = head;
        //定义一个计数器 进行统计
        int count = 0;
        while (count != index - 1){
            cur = cur.next;
            count++;
        }
        return cur;
    }

8. 删除给定的数字 Key

    /**
     * 删除给定的数字
     * 只删除一个 key
     * @param key
     */
    @Override
    public void remove(int key) {

        //如果链表等于空,不能删
        if (head == null){
            return;
        }
        //要判断头结点
        if (head.val == key){
            head = head.next;
        }

        ListNode prevKey = findPrevKey(key);
        if (prevKey == null){
//            System.out.println("没有要删除的数字");
            return;
        }
        ListNode delNode = prevKey.next;
        prevKey.next = delNode.next;
    }

    private ListNode findPrevKey(int key){
        ListNode cur = head;
        //首先要找到前驱cur,再删除
        while (cur.next != null){
            if (cur.next.val == key){
                return cur;
            }else {
                cur = cur.next;
            }
        }
        return null;
    }

9. 删除所有的关键字 Key

/**
     * 删除所有的关键字 Key
     * @param key
     */
    @Override
    public void removeAllKey(int key) {

        //遍历一遍 链表删除所有的 key
        //定义两个指针
        // prev指向第一个结点 cur指向第二个结点
        //从第二个结点向后判断,先不考虑特殊情况
        ListNode cur = head.next;
        ListNode prev = head;
        while (cur != null){
            if (cur.val == key){
                prev.next = cur.next;
                cur = cur.next;
            }else {
                prev = cur;
                cur = cur.next;
            }
        }

        //如果头结点的 val 是 key
        if (head.val == key){
            head = head.next;
            return;
        }
    }

10. 清空链表

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

五、完整代码

1. 异常类

public class IndexException extends RuntimeException{
    public IndexException() {
    }

    public IndexException(String message) {
        super(message);
    }
}

2. 接口

public interface IList {
    //无头单向非循环链表实现
    //头插法
    void addFirst(int data);
    //尾插法
    void addLast(int data);
    //任意位置插入,第一个数据节点为0号下标
    void addIndex(int index, int data);
    //查找是否包含关键字key是否在单链表当中
    boolean contains(int key);
    //删除第一次出现关键字为key的节点
    void remove(int key);
    //删除所有值为key的节点
    void removeAllKey(int key);
    //得到单链表的长度
    int size();
    void clear();
    void display();
}

3. 自己实现的MySingleList类

public class MySingleList implements IList {

    //想要创建链表,要知道链表是由 一个一个结点组成的
    //先创建结点
    //利用静态代码块 内部类
    static class ListNode {
        //结点由两部分组成
        // val 域
        public int val;
        // 呢 next 域呢?
        // next域存放的是 下一个结点的地址
        //所以 next 的类型就是结点类型
        public ListNode next;

        //构造方法
        public ListNode(int val) {
            this.val = val;
            //为什么不构造 next 域呢???
            //因为在实例化 结点的时候 不知道next域指向谁
        }
    }

    //链表的属性  链表的头结点
    public ListNode head;

    public void createList() {
        //在这里面 实例化几个结点
        ListNode node1 = new ListNode(12);
        ListNode node2 = new ListNode(23);
        ListNode node3 = new ListNode(34);
        ListNode node4 = new ListNode(45);
        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        this.head = node1;//头结点
    }

    /**
     * 打印链表
     */
    @Override
    public void display() {
        ListNode cur = head;
//        System.out.println("val值:");
        while (cur != null) {
            System.out.print(cur.val + " ");
            cur = cur.next;
        }
    }

    /**
     * 求当前链表有多少个结点
     *
     * @return
     */
    @Override
    public int size() {
        ListNode cur = head;
        int count = 0;
        while (cur != null) {
            cur = cur.next;
            count++;
        }
        return count;
    }

    /**
     * 当前链表是否存在 key
     *
     * @param key
     * @return
     */
    @Override
    public boolean contains(int key) {
        ListNode cur = head;
        while (cur != null) {
            if (cur.val == key) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

    /**
     * 头插法
     *
     * @param data
     */
    @Override
    public void addFirst(int data) {
        //首先要把 数据存放到一个结点里面
        ListNode node = new ListNode(data);
        //注意:
        //插入数据的时候,一定要先绑后面
        //现在 head 指向头结点的地址
        node.next = head;
        head = node;
        //包含链表为空的情况
    }

    /**
     * 尾插法
     *
     * @param data
     */
    @Override
    public void addLast(int data) {
        //首先要把 数据存放到一个结点里面
        ListNode node = new ListNode(data);
        //首先,要判断链表是否为空
        if (head == null) {
            head = node;
        } else {
            //要找到最后一个结点
            ListNode cur = head;
            while (cur.next != null) {
                cur = cur.next;
            }
            cur.next = node;
        }
    }

    /**
     * 在 index 位置 添加数据
     *
     * @param index
     * @param data
     */
    @Override
    public void addIndex(int index, int data) throws IndexException {
        //还要判断 index 是否合法
        if (index < 0 || index > this.size()) {
            throw new IndexException("index位置不合法:" + index);
        }
        ListNode node = new ListNode(data);
        if (head == null){
            head = node;
            return;
        }

        if (index == 0) {
            addFirst(data);
        }
        if (index == this.size()) {
            addLast(data);
        }

        //处理中间插入
        //要找到index 的前一个位置
        ListNode cur = searchPrevIndex(index);
        node.next = cur.next;
        cur.next = node;
    }

    private ListNode searchPrevIndex(int index){
        ListNode cur = head;
        //定义一个计数器 进行统计
        int count = 0;
        while (count != index - 1){
            cur = cur.next;
            count++;
        }
        return cur;
    }

    /**
     * 删除给定的数字
     * 只删除一个 key
     * @param key
     */
    @Override
    public void remove(int key) {

        //如果链表等于空,不能删
        if (head == null){
            return;
        }
        //要判断头结点
        if (head.val == key){
            head = head.next;
        }

        ListNode prevKey = findPrevKey(key);
        if (prevKey == null){
//            System.out.println("没有要删除的数字");
            return;
        }
        ListNode delNode = prevKey.next;
        prevKey.next = delNode.next;
    }

    private ListNode findPrevKey(int key){
        ListNode cur = head;
        //首先要找到前驱cur,再删除
        while (cur.next != null){
            if (cur.next.val == key){
                return cur;
            }else {
                cur = cur.next;
            }
        }
        return null;
    }

    /**
     * 删除所有的关键字 Key
     * @param key
     */
    @Override
    public void removeAllKey(int key) {

        //遍历一遍 链表删除所有的 key
        //定义两个指针
        // prev指向第一个结点 cur指向第二个结点
        //从第二个结点向后判断,先不考虑特殊情况
        ListNode cur = head.next;
        ListNode prev = head;
        while (cur != null){
            if (cur.val == key){
                prev.next = cur.next;
                cur = cur.next;
            }else {
                prev = cur;
                cur = cur.next;
            }
        }

        //如果头结点的 val 是 key
        if (head.val == key){
            head = head.next;
            return;
        }
    }


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

}

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

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

相关文章

uni-app AppStore Connect上传拒绝汇总

1.Guideline 2.3.3 - Performance - Accurate Metadata 问题是图片不对&#xff0c;最好是自己截图&#xff0c;然后用香蕉云编 上传图片合成图片 2.Guideline 5.1.2 - Legal - Privacy - Data Use and Sharing 解决办法&#xff1a;在uniapp manifest.json找到 APP常用其他…

LabVIEW汽车动态信号模拟系统

随着汽车工业的快速发展&#xff0c;对汽车电子控制单元&#xff08;ECU&#xff09;的测试与仿真需求日益增加。开发了一种基于LabVIEW软件开发的汽车动态信号模拟系统&#xff0c;该系统能有效模拟ECU在实车环境下的工作状态&#xff0c;为ECU的开发和测试提供了一个高效、经…

react中如何mock数据

1.需求说明 因为前后端分离开发项目&#xff0c;就会存在前端静态页面写好了&#xff0c;后端数据接口还没写好&#xff1b;这时候前端就需要自己定义数据来使用。 定义数据有三种方式&#xff1a;直接写死数据、使用mock软件、json-server工具 这里讲解通过json-server工具…

C# 匿名函数与Lambda表达式

本文仅作学习笔记与交流&#xff0c;不作任何商业用途&#xff0c;作者能力有限&#xff0c;如有不足还请斧正 1.匿名函数 在 C# 中&#xff0c;匿名函数是一种没有名称的函数&#xff0c;可以直接在代码中定义和使用 匿名函数主要有两种形式&#xff1a;匿名方法和Lambda 表…

Jetpack Compose多页面跳转示例

示例&#xff1a; 使用Jetpack Compose开发android,实现功能&#xff1a;第1个界面有一个文本输入框输入消费金额&#xff0c;下面有个确定按钮&#xff0c;点击确定按钮后跳转到第2个界面&#xff0c;显示你的消费金额是XX,这个金额来自第1个界面的输入。 实操 使用 Jetpack …

C语言实现顺序结构二叉树-堆

文章目录 &#x1f3af;引言&#x1f453;C语言实现顺序结构二叉树-堆1.树的概念与结构1.1概念与结构1.2树的相关术语 2.二叉树2.1概念与结构2.2特殊的二叉树2.2.1满二叉树2.2.2完全二叉树 3.二叉树的存储结构3.1顺序存储3.2链式存储 4.堆的实现4.1堆的概念与结构4.2向上调增算…

AI+HPC 部署优化面试范围分享

背景 最近几年生成式AI技术和自动驾驶技术发展发展很快&#xff0c;这些行业对于算法的运行效率有很高的要求&#xff0c;尤其一个模型在训练完成后运行到设备上&#xff0c;需要大量的工作&#xff0c;包括模型的剪枝、蒸馏、压缩、量化、算子优化、系统优化等。 对于传统的…

浏览器的最大并发数(http1.1)

HTTP/1.1&#xff1a;每个资源请求通常需要单独的TCP连接&#xff0c;尽管支持Keep-Alive机制&#xff0c;允许在同一个TCP连接上连续发送多个请求。但通常浏览器限制并发TCP连接数&#xff08;例如&#xff0c;每个域名最多6个并发连接&#xff09;。 HTTP/2&#xff1a;引入…

C++:函数对象和函数调用运算符(函数、函数指针、lambda函数对象、bind创建的对象、重载了函数调用符的类对象)

函数对象和函数调用运算符 A.What&#xff08;函数对象&#xff09; 如果类定义了函数调用运算符&#xff0c;则该类的对象称为函数对象 其中重载的运算符operator()被称为函数调用运算符 B.Which&#xff08;有哪些可调用函数对象&#xff09; 函数&#xff1a;一般函数&…

UE4-打包游戏,游戏模式,默认关卡

一.打包游戏 注意windows系统无法打包苹果系统的执行包&#xff0c;只能使用苹果系统打包。 打包完之后是一个.exe文件。 打包要点&#xff1a; 1.确定好要操控的角色和生成位置。 2.设置默认加载的关卡和游戏模式。 在这个界面可以配置游戏的默认地图和游戏的模式&#xff0c;…

springboot+webSocket对接chatgpt

webSocket对接参考 话不多说直接上代码 WebSocket package com.student.config;import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; import org.springfram…

C#知识|账号管理系统:实现修改管理员登录密码

哈喽,你好啊,我是雷工! 本节主要记录实现修改管理员登录密码的后端逻辑及相关功能,以下为学习笔记。 01 实现逻辑 ①:首先输入原密码,验证,验证通过然后可以输入新密码进行修改; ②:新密码修改为了避免输入失误导致输入的密码与自己以为修改的密码不符的情况,增加了…

Langchain核心模块与实战[7]:专业级Prompt工程调教LLM[输入输出接口、提示词模板与例子选择器的协同工程]

Langchain核心模块与实战[7]:专业级Prompt工程调教LLM[输入输出接口、提示词模板与例子选择器的协同工程] 1. 大模型IO接口 任何语言模型应用的核心元素是…模型的输入和输出。LangChain提供了与任何语言模型进行接口交互的基本组件。 提示 prompts : 将模型输入模板化、动态…

**卷积神经网络典型CNN**

LeNet&#xff1a;最早用于数字识别的CNN AlexNet&#xff1a;2012年ILSVRC比赛冠军&#xff0c;远超第二名的CNN&#xff0c;比LeNet更深&#xff0c;用多层小卷积叠加来替换单个的大卷积 ZF Net&#xff1a;2013ILSVRC冠军 GoogleNet&#xff1a;2014ILSVRC冠军 VGGNet&a…

go语言Gin框架的学习路线(十一)

目录 GORM的CRUD教程 更新操作 更新所有字段 更新指定字段 使用 Select 和 Omit 更新 无 Hooks 更新 批量更新 删除操作 删除记录 批量删除 软删除 物理删除 示例代码 GORM的CRUD教程 CRUD 是 "Create, Read, Update, Delete"&#xff08;创建、查询、…

百度,有道,谷歌翻译API

API翻译 百度&#xff0c;有道&#xff0c;谷歌API翻译&#xff08;只针对中英相互翻译&#xff09;,其他语言翻译需要对应from&#xff0c;to的code 百度翻译 package fills.tools.translate; import java.util.ArrayList; import java.util.HashMap; import java.util.Lis…

windows服务器启动apache失败,提示请通过cmd命令行启动:net start apache

Windows Server 2012 R2服务器突然停止运行apche&#xff0c;启动apache失败&#xff0c;提示请通过cmd命令行启动:net start apache 1.报错截图&#xff1a; 进入服务里输入命令启动也不行&#xff0c;提示由于登录失败而无法启动服务。 2.问题原因&#xff1a; 服务器www用…

数据库(MySQL)-DQL数据查询语言

DQL(Data Query Language 数据查询语言)的用途是查询数据库数据&#xff0c;如select语句。其中&#xff0c;可以根据表的结构和关系分为单表查询和多表联查。 单表查询 单表查询&#xff1a;针对数据库中的一张数据表进行查询 全字段查询 语法&#xff1a;select 字段名 fro…

User Allocation In MEC: A DRL Approach 论文笔记

论文&#xff1a;ICWS 2021 移动边缘计算中的用户分配&#xff1a;一种深度强化学习方法 代码地址&#xff1a;使用强化学习在移动边缘计算环境中进行用户分配 目录 Ⅰ.Introduction II. MOTIVATION-A.验证假设的观察结果 II. MOTIVATION-A Motivating Example 数据驱动…

我在百科荣创企业实践——简易函数信号发生器(5)

对于高职教师来说,必不可少的一个任务就是参加企业实践。这个暑假,本人也没闲着,报名参加了上海市电子信息类教师企业实践。7月8日到13日,有幸来到美丽的泉城济南,远离了上海的酷暑,走进了百科荣创科技发展有限公司。在这短短的一周时间里,我结合自己的教学经验和企业的…