数据结构与算法【链表:一】Java实现

目录

链表

单向链表

哨兵链表

双向链表

环形链表


链表

链表是数据元素的线性集合,其每个元素都指向下一个元素,元素存储上并不连续。

随机访问性能

根据 index 查找,时间复杂度 O(n)

插入或删除性能

  • 起始位置:O(1)
  • 结束位置:如果已知 tail 尾节点是 O(1)[双向链表],不知道 tail 尾节点是 O(n)
  • 中间位置:根据 index 查找时间 + O(1)

单向链表

单向链表中每个元素只知道下一个节点位置

单向链表的简单实现

public class SinglyLinkList implements Iterable<Integer> {
    private Node head = null;

    //节点类
    private static class Node {
        int value;
        Node next;

        public Node(int value, Node next) {
            this.value = value;
            this.next = next;
        }
    }

    //提供链表方法
    public void addFirst(int value) {
        head = new Node(value, head);
    }

    //向链表尾部添加节点
    public void addLast(int value) {
        Node last = findLast();
        if (last == null) {
            addFirst(value);
        }
        last.next = new Node(value, null);
    }

    private Node findLast() {
        if (head.next == null) {
            return null;
        }
        Node p = head;
        while (p.next != null) {
            p = p.next;
        }
        return p;
    }

    //遍历链表
    public void loop(Consumer<Integer> consumer) {
        Node pointer = head;
        while (pointer != null) {
            consumer.accept(pointer.value);
            pointer = pointer.next;
        }
    }

    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
            Node pointer = head;
            @Override
            public boolean hasNext() {
                return pointer != null;
            }

            @Override
            public Integer next() {
                int value = pointer.value;
                pointer = pointer.next;
                return value;
            }
        };
    }

    // 根据索引查找节点值
    private Node findByIndex(int index) {
        int i = 0;
        for (Node p = head; p != null; p = p.next, i++) {
            if (index == i) {
                return p;
            }
        }
        return null;
    }

    public int get(int index) {
        Node node = findByIndex(index);
        if (node == null) {
            throw new IllegalArgumentException("索引超出范围");
        }
        return node.value;
    }

    //插入元素
    public void insert(int index, int value) {
        if (index == 0) {
            addFirst(value);
            return;
        }
        //查找索引的上一个节点
        Node node = findByIndex(index - 1);
        if (node == null) {
            throw new IllegalArgumentException("索引超出范围");
        }
        node.next = new Node(value, node.next);
    }

    //移除首节点
    public void removeFirst() {
        if (head==null){
            throw new RuntimeException("链表为空。无法移除");
        }
        head = head.next;
    }

    //删除指定节点
    public void remove(int index){
        if (index==0){
            removeFirst();
        }
        //找到要删除的节点的前驱节点
        Node node = findByIndex(index - 1);
        Node remove = node.next;
        if (remove==null){
            //要删除的节点位置为空
            throw new IllegalArgumentException("索引越界");
        }
        node.next = remove.next;
    }
}

这种实现方法有一个弊端,那就是每次添加或是删除元素时都需要进行判断链表是否为空。因此提出了哨兵链表的概念

哨兵链表

所谓哨兵列表,就是头节点不存储数据,只为简化边缘判断使用。

具体代码如下

public class SinglyLinkListSentinel implements Iterable<Integer> {
    //头节点指向哨兵节点。值可以随便给
    private Node head = new Node(723468, null);

    //节点类
    private static class Node {
        int value;
        Node next;

        public Node(int value, Node next) {
            this.value = value;
            this.next = next;
        }
    }

    public void addFirst(int value) {
        //在链表头部添加节点时,应该在哨兵之后。
        insert(0, value);
    }

    //向链表尾部添加节点
    public void addLast(int value) {
        Node last = findLast();
        last.next = new Node(value, null);
    }

    private Node findLast() {
        Node p = head;
        while (p.next != null) {
            p = p.next;
        }
        return p;
    }

    //遍历链表
    public void loop(Consumer<Integer> consumer) {
        //从哨兵节点之后的节点开始遍历
        Node pointer = head.next;
        while (pointer != null) {
            consumer.accept(pointer.value);
            pointer = pointer.next;
        }
    }

    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
            Node pointer = head.next;

            @Override
            public boolean hasNext() {
                return pointer != null;
            }

            @Override
            public Integer next() {
                int value = pointer.value;
                pointer = pointer.next;
                return value;
            }
        };
    }

    // 根据索引查找节点值
    private Node findByIndex(int index) {
        //这里设置为-1是排除哨兵节点带来的影响
        int i = -1;
        for (Node p = head; p != null; p = p.next, i++) {
            if (index == i) {
                return p;
            }
        }
        return null;
    }

    public int get(int index) {
        Node node = findByIndex(index);
        if (node == null) {
            throw new IllegalArgumentException("索引超出范围");
        }
        return node.value;
    }

    //插入元素
    public void insert(int index, int value) {
        //查找索引的上一个节点
        Node node = findByIndex(index - 1);
        if (node == null) {
            throw new IllegalArgumentException("索引超出范围");
        }
        node.next = new Node(value, node.next);
    }

    //移除首节点
    public void removeFirst() {
        remove(0);
    }

    //删除指定节点
    public void remove(int index) {
        //找到要删除的节点的前驱节点
        Node node = findByIndex(index - 1);
        Node remove = node.next;
        if (remove == null) {
            //要删除的节点位置为空
            throw new IllegalArgumentException("索引越界");
        }
        node.next = remove.next;
    }
}

双向链表

每个元素知道上一个元素与下一个元素的地址

简单实现如下

public class DoublyLinkedListSentinel implements Iterable<Integer>{
    //头哨兵
    private Node head = new Node(null, null, 0);
    //尾哨兵
    private Node tail = new Node(null, null, 0);

    public DoublyLinkedListSentinel() {
        //初始化时,对头尾哨兵进行指定
        head.next = tail;
        tail.prev = head;
    }

    private static class Node {
        Node prev;
        Node next;
        int value;

        public Node(Node prev, Node next, int value) {
            this.prev = prev;
            this.next = next;
            this.value = value;
        }
    }

    //提供双向链表方法
    public Node findByIndex(int index) {
        int i = -1;
        for (Node p = head; p != tail; p = p.next, i++) {
            if (index == i) {
                return p;
            }
        }
        return null;
    }

    //插入首元素
    public void addFirst(int value) {
        insert(0, value);
    }

    //插入元素
    public void insert(int index, int value) {
        Node prev = findByIndex(index - 1);
        if (prev == null) {
            throw new RuntimeException("下标越界");
        }
        Node next = prev.next;
        Node node = new Node(prev, next, value);
        prev.next = node;
        next.prev = node;
    }

    //向尾节点添加
    public void addLast(int value){
        Node prev = tail.prev;
        Node addNode = new Node(prev, tail, value);
        prev.next = addNode;
        tail.prev = addNode;
    }
    //删除首节点
    public void removeFirst(){
        if (head.next==tail){
            throw new RuntimeException("链表为空");
        }
        Node remove = head.next;
        Node next = remove.next;
        head.next = next;
        next.prev = head;
    }

    //删除元素
    public void remove(int index) {
        Node prev = findByIndex(index - 1);
        if (prev == null) {
            throw new RuntimeException("下标越界");
        }
        Node remove = prev.next;
        Node next = remove.next;
        if (remove==tail){
            throw new RuntimeException("下标越界");
        }
        prev.next = next;
        next.prev = prev;
    }

    //获取元素
    public int get(int index){
        Node node = findByIndex(index);
        return node.value;
    }

    //遍历元素
    public void loop(Consumer<Integer> consumer) {
        Node pointer = head.next;
        while (pointer != tail) {
            consumer.accept(pointer.value);
            pointer = pointer.next;
        }
    }

    //迭代器实现
    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
            Node p =head.next;
            @Override
            public boolean hasNext() {
                return p!=tail;
            }

            @Override
            public Integer next() {
                int value = p.value;
                p = p.next;
                return value;
            }
        };
    }
}

环形链表

环形链表的尾节点指向的是头节点head

环形链表我们也可以引入哨兵,空链表时,哨兵既做头节点也做尾节点。

简单实现代码如下

public class DoublyLinkedListSentinelTo implements Iterable<Integer> {
    private Node sentinel = new Node(null, null, -1);

    public DoublyLinkedListSentinelTo() {
        sentinel.prev = sentinel;
        sentinel.next = sentinel;
    }

    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
            Node p = sentinel.next;

            @Override
            public boolean hasNext() {
                return p != sentinel;
            }

            @Override
            public Integer next() {
                int value = p.value;
                p = p.next;
                return value;
            }
        };
    }

    private static class Node {
        Node prev;
        Node next;
        int value;

        public Node(Node prev, Node next, int value) {
            this.prev = prev;
            this.next = next;
            this.value = value;
        }
    }

    //提供循环链表的方法
    public Node findByIndex(int index) {
        int i = 0;
        for (Node p = sentinel.next; p != sentinel; i++, p = p.next) {
            if (index == i) {
                return p;
            }
        }
        return null;
    }

    public void addFirst(int value) {
        Node next = sentinel.next;
        Node addNode = new Node(sentinel, next, value);
        sentinel.next = addNode;
        next.prev = addNode;
    }

    public void insert(int index, int value) {
        if (index == 0) {
            addFirst(value);
        }
        Node prev = findByIndex(index - 1);
        if (prev == null) {
            throw new RuntimeException("下标越界");
        }
        Node next = prev.next;
        Node insertNode = new Node(prev, next, value);
        next.prev = insertNode;
        prev.next = insertNode;
    }

    public void addLast(int value) {
        Node prev = sentinel.prev;
        Node addNode = new Node(prev, sentinel, value);
        prev.next = addNode;
        sentinel.prev = addNode;
    }

    //删除第一个节点
    public void removeFirst() {
        Node remove = sentinel.next;
        if (remove == sentinel) {
            throw new RuntimeException("下标越界");
        }
        Node next = remove.next;
        sentinel.next = next;
        next.prev = sentinel;
    }

    //删除最后一个节点
    public void removeLast() {
        Node remove = sentinel.prev;
        if (remove == sentinel) {
            throw new RuntimeException("下标越界");
        }
        Node prev = remove.prev;
        prev.next = sentinel;
        sentinel.prev = prev;
    }

    //删除指定删除节点
    public void remove(int index) {
        if (index == 0) {
            removeFirst();
        }
        Node prev = findByIndex(index - 1);
        Node remove = prev.next;
        if (remove == sentinel) {
            throw new RuntimeException("下标越界");
        }
        Node next = remove.next;
        prev.next = next;
        next.prev = prev;
    }

    //遍历节点
    public void loop(Consumer<Integer> consumer) {
        for (Node p = sentinel.next; p != sentinel; p = p.next) {
            consumer.accept(p.value);
        }
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一文说清楚Openai的这波更新内容,大地震 一大波套壳公司倒闭

前几天Openai召开了首届的开发者大会&#xff0c;45分钟的会议&#xff0c;让千万用户感到兴奋&#xff0c;但是让万千的套壳的创业公司&#xff0c;却感觉如坐针毡。这次发布会发布了哪些功能&#xff1f;为什么会导致这种情况的发生&#xff1f;让我们接着往下讲 API升级且降…

【业务场景】长列表的处理

长列表的处理 1. 什么是长列表 在前端开发中&#xff0c;经常会遇到列表展示&#xff0c;如果列表项的数量比较多&#xff0c;我们一般选择采用分页的方式来进行处理 但传统的前后翻页方式只适用于后台的管理系统中&#xff0c;而在用户端、尤其是在移动端&#xff0c;为了保…

OSCNet: Orientation-Shared Convolutional Network for CT Metal Artifact Learning

OSCNet: 面向共享的CT金属伪影学习卷积网络 论文链接&#xff1a;https://ieeexplore.ieee.org/document/10237226 项目链接&#xff1a;https://github.com/hongwang01/OSCNet&#xff08;目前不会开源&#xff09; Abstract X射线计算机断层扫描(CT)已广泛应用于疾病诊断和…

计算机毕业设计选题推荐-记录生活微信小程序/安卓APP-项目实战

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

GB28181/GB35114国标平台LiveGBS适配国产信创环境,使用国产数据库达梦数据库、高斯数据库、瀚高数据库的配置方法...

1、如何配置切换信创达梦数据库&#xff1f; livecms.ini -> [db]下面添加配置如&#xff1a; ... [db] dialectdm url dm://SYSDBA:Aa12345678localhost:5236/livegbs 2、如何配置切换高斯数据库&#xff1f; livecms.ini -> [db]下面添加配置如&#xff1a; ... [db] d…

洗地机是智商税吗?洗地机有没有必要买?2023洗地机推荐

传统的扫地拖地方式不仅时间长&#xff0c;被毛孩子和萌娃制造的顽固污渍更是让人头痛不已&#xff0c;高效又有效的地面清洁方式成了我们最大的诉求。目前洗地机受到青睐&#xff0c;异常火爆&#xff0c;也成为一众清洁扫地的选择之一&#xff0c;那洗地机到底是不是智商税呢…

物联网AI MicroPython学习之语法 umqtt客户端

学物联网&#xff0c;来万物简单IoT物联网&#xff01;&#xff01; umqtt 介绍 模块功能: MQTT客户端功能 - 连线、断线、发布消息、订阅主题、KeepAlive等功能。 MQTT协议采用订阅者/发布者模式&#xff0c;协议中定义了消息服务质量&#xff08;Quality of Service&#x…

Winform / WPF 自定义控件 —— IPV4 地址输入框

在开始阅读本文之前&#xff0c;如果您有学习创建自定义控件库并在其他项目中引用的需求&#xff0c;请参考&#xff1a;在Visual Studio中创建自定义Winform控件库并在其他解决方案中引用https://blog.csdn.net/YMGogre/article/details/126508042 0、引言 Winform / WPF 框架…

docker命令大全

1、查看Docker 容器占用的空间 docker ps -s2、查看所有容器 docker ps -a3、启动、关闭、重启一个已存在的容器 docker start <容器ID> docker stop <容器ID> docker restart <容器ID> 4、进入容器&#xff0c;退出终端的时候不会关闭container的ma…

线程池的使用

线程池的作用 降低线程创建和销毁的开销&#xff1a;线程的创建和销毁是比较昂贵的操作。通过使用线程池&#xff0c;可以避免频繁地创建和销毁线程&#xff0c;而是复用线程池中已经存在的线程&#xff0c;从而降低了开销。 控制并发度&#xff1a;通过控制线程池中线程的数量…

(个人实测保熟)记录Tecnomatix Process Simulate 16.1.2官方安装包及授权许可配置教程(Win10环境)

Tecnomatix Process Simulate 16是一款由西门子公司推出的一款工艺仿真解决方案,是虚拟制造仿真领域的领先解决方案,可帮助您数字化制造以及将创新思想和原材料转变为变革性产品的过程。在网上找了一些盗版的安装包&#xff0c;就很离谱。直接提示本"无法打开此安装程序包…

spring-cloud-alibaba-nacos

spring cloud nacos 安装和启动nacos # 解压nacos安装包 # tar -zvxf nacos-server-1.4.1.tar.gz# nacos默认是以集群的模式启动&#xff0c;此处先用单机模式 # cd /usr/local/mysoft/nacos/bin # sh startup.sh -m standalone# nacos 日志 # tail -f /usr/local/mysoft/na…

智慧隧道:TSINGSEE青犀远程视频AI智能监管平台保障隧道施工安全

一、背景与需求分析 随着我国交通运输量的增加以及新基建的不断规划和建设&#xff0c;公路建设工作也在持续开展中。高速公路隧道属于特殊构造段&#xff0c;因为隧道空间小&#xff0c;密闭性强&#xff0c;施工过程中一旦发生火灾、事故等&#xff0c;将带来重大人员伤亡和…

身份证照片怎么弄成200k以内?超级好用!

一些网站为了限制大的文件上传&#xff0c;提出了一些大小限制的要求&#xff0c;那么身份证如何弄成200k呢&#xff1f;下面介绍三种方法。 方法一&#xff1a; 使用嗨格式压缩大师 1、在电脑上打开安装好的软件&#xff0c;在首界面中点击“图片压缩”。 2、进入后上传需要…

Vue CLI脚手架安装、搭建、配置 和 CLI项目分析

目录 一、CLI快速入门 1. 官方介绍 : 2.安装Vue CLI : 3.搭建Vue CLI : 4.IDEA配置Vue CLI : 二、Vue CLI项目分析 1.结构分析 : 1.1 config 1.2 node_modules 1.3 src 1.4 static 2.流程分析 : 2.1 main.js 2.2 router/index.js 2.3 components/HelloWorld.vue 2.4 A…

“糖尿病日”感言

长期旺盛的写作欲&#xff0c;今天忽地就莫名其妙地衰退下来了。感到浑身都不舒服&#xff0c;特别是过去从未出现过的腰微痛、乏力现象发生了。 转念一想&#xff0c;或是老龄人一日不如一日的正常反应吧&#xff1f;而且&#xff0c;今天恰逢“ 联合国糖尿病日”&#xff0c…

mysql之MHA

1、定义 全称是masterhigh avaliabulity。基于主库的高可用环境下可以实现主从复制及故障切换&#xff08;基于主从复制才能故障切换&#xff09; MHA最少要求一主两从&#xff0c;半同步复制模式 2、作用 解决mysql的单点故障问题。一旦主库崩溃&#xff0c;MHA可以在0-30…

Spark读取excel文件

文章目录 一、excel数据源转成csv二、Spark读取csv文件(一)启动spark-shell(二)读取csv生成df(三)查看df内容一、excel数据源转成csv 集群bigdata - ubuntu: 192.168.191.19master(bigdata1) - centos: 192.168.23.78 slave1(bigdata2) - centos: 192.168.23.79 slave2(b…

多商家签到打卡奖励免单霸王餐小程序开发

多商家签到打卡奖励免单霸王餐小程序开发 用户注册和登录&#xff1a;提供用户注册和登录功能&#xff0c;以便用户能够参与签到打卡活动。 商家入驻&#xff1a;商家可申请入驻平台&#xff0c;提交相关资料并等待平台审核&#xff0c;审核通过后即可发布活动和奖励。 签到打…