链表---数据结构-黑马

news2024/11/23 22:19:28

链表

定义

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

分类

  • 单向链表,每个元素只知道自己的下一个元素是谁。
    在这里插入图片描述
  • 双向链表,每个元素知道自己的上一个元素和下一个元素。
    在这里插入图片描述
  • 循环链表,通常链表尾部节点 tail 指向的为null,而循环链表的tail指向链表的头部节点 head。
    在这里插入图片描述

链表中还有一种特殊的节点称之为,哨兵(Sentinel)节点,也称之为 哑元(Dummy)节点, 不存储数据,一般作为头尾,简化边界判断,如下如所示。
在这里插入图片描述

性能

随机访问

根据 i n d e x index index 查询,时间复杂度为 O ( n ) O(n) O(n)

插入或删除

  • 起始位置,时间复杂度为, O ( 1 ) O(1) O(1)
  • 结束位置,若已知尾部节点 tail ,时间复杂度为, O ( 1 ) O(1) O(1) ;否则为 O ( n ) O(n) O(n)
  • 中间位置,根据 index 查找的时间复杂度 + O ( 1 ) O(1) O(1)

单项链表实现

方法实现

节点类、头部添加、循环遍历、尾部添加

// 单项链表
public class SinglyLinkedList implements Iterable<Integer>() {
    
    private Node head = null;  // 头指针
    
    // 节点类
    private static class Node() {
        int value;  // 值
        Node node;  // 下一个节点
        
        public Node(int value, Node node) {
            this.value = value;
            this.node = node;
        }
    }
    
    @Override
    public Iterator<Integer> iterator() {
        // 匿名内部类 -> 带名字的内部类
        return new NodeIterator();
    }
    
    // 1.链表头部添加元素
    public void addFirst(int value) {
        // 1.当链表为空时
        // head = new Node(value, null);  因为 head = new Node(value, head);这行代码可以处理链表为空的情况,则注释
        // 2.当链表非空时
        head = new Node(value, head);
    }
    
    // 寻找最后一个节点
    private Node findLast() {
        // 链表为空
        if (head == null) {
            return null;
        }
        Node p;
        for(p = head; p.next != null; p = p.next) {
            
        }
        return p;
    }
    
    // 3.尾部添加元素
    private void addLast(int value) {
        Node last = findLast();
        if (last == null) {
            // 头部添加
            addFirst(value);
            return;
        }
        last.next = new Node(value, null);
    }
    
    // 4.根据索引值查找结点
    public Node findNode(int index) {
        int i = 0;
        for(Node p = head; p != null; p = p.next, i++) {
            if (index == i) {
                return p;
            }
        }
        // 未找到
        return null;
    }
    
    // 5.根据index找到对应节点,并返回节点值
    public int get(int index) {
        Node p = findNode(index);
        if (p == null) {
            // 节点为空,抛出异常
            threw new IllegalArgumentException(String.format("index [%d]不合法%n", index))}
        return p.value;
    }
    
    // 6.向索引位置插入元素
    public void insert(int index, int value) {
        if (index == 0) {  // 索引为0,向链表头部添加
            addFirst(value);
            return;
        }
        // 1.找到插入索引位置的上一个节点
        Node p = findNode(index - 1);
        // 判断p节点是否为空
        if (p == null) {
            // 节点为空,抛出异常
            threw new IllegalArgumentException(String.format("index [%d]不合法%n", index));
        }
        Node n = p.next;
        // p存在的情况
        p.next = new Node(value, n);
    }
    
    // 7.删除头部节点
    public void removeFirst() {
        if (head == null) {
            // 节点为空,抛出异常
            threw new IllegalArgumentException(String.format("index [%d]不合法%n", index));
        }
        // 头节点的下一个节点赋给头
        head = head.next;
    }
    
    // 8.按索引位置删除节点
    public void remove (int index) {
        if (index == 0) {
            removeFirst();
            return;
        }
        Node p = findNode(index - 1);  // 找到待删除节点的上一个节点
        if (p == null) {
            // 节点为空,抛出异常
            threw new IllegalArgumentException(String.format("index [%d]不合法%n", index));
        }
        Node n = p.next;               // 待删除节点
        if (n == null) {
            // 未找到待删除节点
            threw new IllegalArgumentException(String.format("index [%d]不合法%n", index));
        }
        p.next = n.next;			   // 待删除节点的上一个节点p的next指向待删除结点的下一个节点
    }
    
    // 6.遍历链表,方法1
    public void loop1(Consumer<Integer> consumer) {
        Node p = head; // 头节点赋给p
        while (p != null) {
            consumer.accept(p.value);
            p = p.next;
        }
    }
    // 循环遍历,方法2
    public void loop2(Consumer<Integer> consumer) {
        for(Node p = head; p != null; p = p.next) {
            consumer.accept(p.value);
        }
    }
    
    // 遍历,方法3,使用迭代器,实现接口Iterable
    public class NodeIterator implements Iterator<Integer> {
        Node p = head;
        
        @Override
        public boolean hasNext() {  //判断是否有下一个元素
            return p != null;
        }

        @Override
        public Integer next() {     //返回当前节点值
            int value = p.value;
            p = p.next;
            return value;      
        }
    }
}

测试

public class TestSinglyLinkedList() {
	
    // 尾部添加, 根据索引查找对应节点值测试
    @Test
    public void test3() {
        SinglyLinkedList list = new SinglyLinkedList();
        list.addLast(1);
        list.addLast(2);
        list.addLast(3);
        list.addLast(4);
        
        // Assertions.assertIterableEquals(List.of(1, 2, 3, 4), list);
        
        int i = list.get(2);
        System.out.println(i);  //i = 3
        
    }
    
     @Test
    public void test4() {
        SinglyLinkedList list = new SinglyLinkedList();
        list.addLast(1);
        list.addLast(2);
        list.addLast(3);
        list.addLast(4);
       
		list.insert(2, 5); 向索引为2的位置插入元素5
        for(Integer value : list) {
            System.out.print(value);
        }
        
    }
}

带哨兵的单向链表

// 单项链表
public class SinglyLinkedList implements Iterable<Integer>() {
    
    private Node head = new Node(666, null);  // 头指针
    
    // 节点类
    private static class Node() {
        int value;  // 值
        Node node;  // 下一个节点
        
        public Node(int value, Node node) {
            this.value = value;
            this.node = node;
        }
    }
    
    @Override
    public Iterator<Integer> iterator() {
        // 匿名内部类 -> 带名字的内部类
        return new NodeIterator();
    }
    
    // 1.链表头部添加元素
    public void addFirst(int value) {
		insert(0, value);
    }
    
    // 寻找最后一个节点
    private Node findLast() {
        Node p;
        for(p = head; p.next != null; p = p.next) {
            
        }
        return p;
    }
    
    // 3.尾部添加元素
    private void addLast(int value) {
        Node last = findLast();
        last.next = new Node(value, null);
    }
    
    // 4.根据索引值查找结点
    public Node findNode(int index) {
        int i = -1;
        for(Node p = head; p != null; p = p.next, i++) {
            if (index == i) {
                return p;
            }
        }
        // 未找到
        return null;
    }
    
    // 5.根据index找到对应节点,并返回节点值
    public int get(int index) {
        Node p = findNode(index);
        if (p == null) {
            // 节点为空,抛出异常
            threw new IllegalArgumentException(String.format("index [%d]不合法%n", index));
        }
        return p.value;
    }
    
    // 6.向索引位置插入元素
    public void insert(int index, int value) {
        // 1.找到插入索引位置的上一个节点
        Node p = findNode(index - 1);
        // 判断p节点是否为空
        if (p == null) {
            // 节点为空,抛出异常
            threw new IllegalArgumentException(String.format("index [%d]不合法%n", index));
        }
        Node n = p.next;
        // p存在的情况
        p.next = new Node(value, n);
    }
    
    // 7.删除头部节点
    public void removeFirst() {
		remove(0);
    }
    
    // 8.按索引位置删除节点
    public void remove (int index) {
        Node p = findNode(index - 1);  // 找到待删除节点的上一个节点
        if (p == null) {
            // 节点为空,抛出异常
            threw new IllegalArgumentException(String.format("index [%d]不合法%n", index));
        }
        Node n = p.next;               // 待删除节点
        if (n == null) {
            // 未找到待删除节点
            threw new IllegalArgumentException(String.format("index [%d]不合法%n", index));
        }
        p.next = n.next;			   // 待删除节点的上一个节点p的next指向待删除结点的下一个节点
    }
    
    // 6.遍历链表,方法1
    public void loop1(Consumer<Integer> consumer) {
        Node p = head.next; // 头节点赋给p
        while (p != null) {
            consumer.accept(p.value);
            p = p.next;
        }
    }
    // 循环遍历,方法2
    public void loop2(Consumer<Integer> consumer) {
        for(Node p = head.next; p != null; p = p.next) {
            consumer.accept(p.value);
        }
    }
    
    // 遍历,方法3,使用迭代器,实现接口Iterable
    public class NodeIterator implements Iterator<Integer> {
        Node p = head.next;
        
        @Override
        public boolean hasNext() {  //判断是否有下一个元素
            return p != null;
        }

        @Override
        public Integer next() {     //返回当前节点值
            int value = p.value;
            p = p.next;
            return value;      
        }
    }
}

双向链表–哨兵

public class DoublyLinkedListSentinel implements Iterator<Integer> {
     
    static class Node {
        Node prev;		// 上一节点
        int value;		// 值
        Node next;		// 下一节点
        
        public Node (Node prev, int value, Node next) {
            this.prev = prev;
            this.value = value;
            this.next = next;
        }
    }
    
    private Node head; //头哨兵节点
    private Node tail; //尾哨兵节点
    
    public DoublyLinkedListSentinel () {
        head = new Node(null, 666, null);
        tail = new Node(null, 888, null);
        head.next = tail;
        tail.prev = head;
    }
    
    // 根据索引位置找节点
    private Node findIndex(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);
    } 
    // 1.对应索引位置插入元素
    public void insert(int index, int value) {
        Node prev = findIndex(index - 1);		// 插入索引位置前一个结点
        if (prev == null) {
            // 节点为空,抛出异常
            threw new IllegalArgumentException(String.format("index [%d]不合法%n", index));
        }
        Node next = prev.next;					// 成为待插入元素节点的下一个节点
        Node node = new Node(prev, value, next); 
        prev.next = node;
        next.prev = node;
    }
    
    // 尾部添加元素
    public void addLast(int value) {
        Node last = tail.prev;
        Node added = new Node(last, value, tail);
        last.next = added;
        tail.prev = added; 
    }
    
    // 删除最后一个节点
    public void removeLast() {
        Node removed = tail.prev;
        if (removed == head) {
            // 节点为空,抛出异常
            threw new IllegalArgumentException(String.format("index [%d]不合法%n", index));
        }
        Node prev = removed.prev;
        prev.next = tail;
        tail.prev = prev;
    }
    
    // 删除头节点
    public void removeFirst() {
        remove(0);
    }
    
    // 删除节点
    public void remove(int index) {
        Node prev = findIndex(index - 1);		// 删除索引位置前一个结点
        if (prev == null) {
            // 节点为空,抛出异常
            threw new IllegalArgumentException(String.format("index [%d]不合法%n", index));
        }
        Node next = prev.next.next;					// 待删除元素节点的下一个节点
        if (next == null) {
            // 节点为空,抛出异常
            threw new IllegalArgumentException(String.format("index [%d]不合法%n", index));
        }
		prev.next = next;
        next.prev = prev;
    }
    
    // 迭代器遍历
    @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;
            }
        }
    }
    
}

环形链表

双向环形链表带哨兵,哨兵既作为头哨兵,又作为尾哨兵
在这里插入图片描述

public class DoublyLinkedListSentinel {
     
    private static class Node {
        Node prev;		// 上一节点
        int value;		// 值
        Node next;		// 下一节点
        
        public Node (Node prev, int value, Node next) {
            this.prev = prev;
            this.value = value;
            this.next = next;
        }
    }
    
    private Node sentinel = new Node(null, -1, null);
    
    public DoublyLinkedListSentinel () {
        sentinel.prev = sentinel;
        sentinel.next = sentinel;
    }
    
    // 根据索引位置找节点
    private Node findIndex(int index) {
        int i = -1;
        for(Node p = sentinel; p != sentinel; p = p.next, i++) {
        	if (index == i) {
                return p;
            }    
        }
        return null;
    }
    
    // 头部插入元素
    public void addFirst(int value) {
        Node a = sentinel;
        Node b = sentinel.next;
        Node added = new Node(a, value, b);
        a.next = added;
        b.prev = added;
    } 
    
    // 1.对应索引位置插入元素
    public void insert(int index, int value) {
        Node prev = findIndex(index - 1);		// 插入索引位置前一个结点
        if (prev == null) {
            // 节点为空,抛出异常
            threw new IllegalArgumentException(String.format("index [%d]不合法%n", index));
        }
        Node next = prev.next;					// 成为待插入元素节点的下一个节点
        Node node = new Node(prev, value, next); 
        prev.next = node;
        next.prev = node;
    }
    
    // 尾部添加元素
    public void addLast(int value) {
        Node b = sentinel;
        Node a = sentinel.prev;
        Node added = new Node(a, value, b);
        b.prev = added;
        a.next = added;
    }
    
    // 删除头节点
    public void removeFirst() {
        Node removed = sentinel.next;
        if (removed == sentinel) {
            // 节点为空,抛出异常
            threw new IllegalArgumentException(String.format("index [%d]不合法%n", index));
        }
        Node a = sentinel;
        Node b = removed.next;
        a.next = b;
        b.prev = a;
    }
    
    // 删除最后一个节点
    public void removeLast() {
        Node removed = sentinel.prev;
        if (removed == sentinel) {
            // 节点为空,抛出异常
            threw new IllegalArgumentException(String.format("index [%d]不合法%n", index));
        }
        Node a = sentinel;
        Node b = removed.prev;
        a.prev = b;
        b.next = a;
    }
    
    // 根据节点值查找该节点
    public Node findByValue(int value) {
        Node p = sentinel.next;
        whlie (p != sentinel) {
            if (p.value == value) {
                return p;
            }
            p = p.next;
        }
        return null;
    }
    
    // 根据节点值删除节点
    public void remove(int vlaue) {
        Node removed = findByValue(value);
        if (removed == null) {
            // 节点为空,抛出异常
            threw new IllegalArgumentException(String.format("index [%d]不合法%n", index));
        }
        Node a = removed.prev;
        Node b = removed.next;
        a.next = b;
        b.prev = a;
    }
    
    // 迭代器遍历
    @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;
            }
        }
    }
}

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

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

相关文章

分布式锁:Mysql实现,Redis实现,Zookeeper实现

目录 前置知识 Mysql实现分布式锁 1.get_lock函数 Java代码实现&#xff1a; 2.for update尾缀 Java代码实现&#xff1a; 3.自己定义锁表 Java代码实现&#xff1a; 4.时间戳列实现乐观锁 Java代码实现&#xff1a; Redis实现分布式锁 Zookeeper实现分布式锁&#…

Oracle搭建一主两备dataguard环境的详细步骤

​ 上一篇文章介绍了Oracle一主两备的DG环境&#xff0c;如何进行switchover切换&#xff0c;也许你会问Oracle一主两备dataguard环境要怎么搭建&#xff0c;本篇文章将为你讲述一主两备dataguard详细搭建步骤。 环境说明 主机名IP地址db_unique_name数据库角色ora11g10.10.1…

驱动数智化升级,AI大模型准备好了吗?

大数据产业创新服务媒体 ——聚焦数据 改变商业 AI大模型的快速崛起&#xff0c;为企业带来了前所未有的变革机遇。从自然语言处理到图像识别&#xff0c;从精准营销到智能制造&#xff0c;AI大模型正逐步渗透到各行各业的核心业务中。然而&#xff0c;随着技术的不断演进&…

力扣刷题-循环队列

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 思路&#xff1a; 我们在这里采用的是用数组的形式实现循环链表&#xff0c;我认为这个用数组是更为简单的&#xff0c;我们只需要控制下标就可以实现循环链表的效果。具体实现代…

Python数据可视化案例——折线图

目录 json介绍&#xff1a; Pyecharts介绍 安装pyecharts包 构建一个基础的折线图 配置全局配置项 综合案例&#xff1a; 使用工具对数据进行查看 &#xff1a; 数据处理 json介绍&#xff1a; json是一种轻量级的数据交互格式&#xff0c;采用完全独立于编程语言的文…

2024 该学前端还是学后端?

2024 该学前端还是学后端&#xff1f; 现状分析pragmatic-drag-and-drop后端开发 现状分析 对于这个问题&#xff0c;个人作为Java后端开发者&#xff0c;那么当然是比较熟悉Java后端开发&#xff0c;从这么久的工作体验来说&#xff0c;因为个人也是比较熟悉Java后端&#xf…

【第19章】Spring Cloud之Gateway自定义Logback配置

文章目录 前言一、内置配置1. 关联依赖2. 内置配置 二、自定义配置1. 日志级别2. 彩色日志3. 自定义配置4. 增加打印语句5. 效果展示 总结 前言 网关层作为我们程序的主入口&#xff0c;有着至关重要的作用&#xff0c;下面我们通过自定义Logback配置增强网关层的日志输出&…

【实用工具】Stirling-PDF入门安装教程: 优质开源的PDF处理工具/编辑工具

文章目录 项目简介功能展示Page Operations 页面操作Conversion Operations 转换操作Security & Permissions 安全与权限Other Operations 其他业务 如何安装并使用Docker RunDocker Compose 项目简介 这是一款使用 Docker 的基于本地托管网络的强大 PDF 操作工具。它能让…

2024年翻译工具新风尚:实时翻译与精准度并进

语言交流的障碍随着全球化的不断深入日益成为连接不同文化和国家的挑战。然而&#xff0c;在科技日新月异的今天&#xff0c;类似谷歌翻译这样的工具正在高速发展这。这次我们来一起探讨深受用户喜欢的翻译工具有哪些。 1.福昕在线翻译 链接直达&#xff1a;https://fanyi.pd…

贷齐乐系统最新版SQL注入(绕过WAF可union select跨表查询)

目录 标题&#xff1a;贷齐乐系统最新版SQL注入&#xff08;绕过WAF可union select跨表查询&#xff09; 内容&#xff1a; 一&#xff0c;环境部署 二&#xff0c;源码分析 三&#xff0c;sql注入 总结&#xff1a; [回到顶部]&#xff08;#article_top&#xff09; 一&am…

Linux使用学习笔记1到2 命令行与shell 基础运维命令

在学习使用ubuntu等各种喜他构建服务器的过程中遇到很多问题&#xff0c;意识到只是跟着网络的教程没办法管理好一个完整的应用部署和运行。遂开始学习linux基本知识&#xff0c;以应对服务器常见问题和软件的使用和维护。 shell 望文生义&#xff0c;大概意思是一个外壳&…

交错字符串[中等]

优质博文&#xff1a;IT-BLOG-CN 一、题目 给定三个字符串s1、s2、s3&#xff0c;请你帮忙验证s3是否是由s1 和s2交错 组成的。 两个字符串s和t交错 的定义与过程如下&#xff0c;其中每个字符串都会被分割成若干 非空 子字符串&#xff1a; s s1 s2 ... sn t t1 t2 …

数据结构---单链表实现

单链表是什么 我的理解是“特殊的数组”&#xff0c;通过访问地址来连接起来 1怎么创建链表 ----通过结构体&#xff08;成员有存入数据的data和指向下一个节点的地址的指针&#xff08;结构体指针&#xff09;next 初始架构---DataType 对应存入数据类型&#xff0c;此处的N…

一款基于Java外卖配送系统,专为多商户入驻设计,包含用户端、商家端、配送端以及总管理后台(附源码)

前言 在当前的外卖配送市场中&#xff0c;软件系统的状态常常面临一些挑战&#xff0c;例如多商户管理复杂性、用户体验不一致、后端服务的稳定性和安全性等。这些痛点不仅影响了商户和用户的满意度&#xff0c;也限制了平台的扩展性和发展潜力。 为了解决这些现状&#xff0…

B站搜索建库架构优化实践

前言 搜索是B站的重要基础功能&#xff0c;需要对包括视频、评论、图文等海量的站内优质资源建立索引&#xff0c;处理来自用户每日数亿的检索请求。离线索引数据的正确、高效产出是搜索业务的基础。我们在这里分享搜索离线架构整体的改造实践&#xff1a;从周期长&#xff0c;…

【论文阅读】BoT-SORT: Robust Associations Multi-Pedestrian Tracking

题目&#xff1a;BoT-SORT: Robust Associations Multi-Pedestrian Tracking 作者&#xff1a;Nir Aharon* Roy Orfaig Ben-Zion Bobrovsky motivation: 作者来得很直接&#xff0c;就说他们用相机运动模型和优化卡尔曼做了个可以解决具有挑战的跟踪问题的算法:BOT-SORT;说他们…

工程数学线性代数(同济大学数学系)第六版(更新中)

第1章 行列式 2 全排列和对换 一、排列及其逆序数 全排列 1个逆序、逆序数 奇排列&#xff0c;偶排列 二、对换 对换&#xff1a;排列中任意两个元素对调 相邻对换&#xff1a;相邻两个元素对换 对换改变排列的奇偶性。 4 行列式的性质 5 行列式按行&#xff08;列&…

有趣的的rce漏洞复现分析

目录 无字母数字绕过正则表达式 解读代码 解题思路 异或 或 取反 无字母数字绕过正则表达式 首先我们依然是搭建环境&#xff08;环境依然是Ubuntu下部署&#xff0c;和之前的漏洞环境一样&#xff09; <?php error_reporting(0); highlight_file(__FILE__); $code…

<数据集>车间工人、安全帽、安全背心识别<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;3465张 标注数量(xml文件个数)&#xff1a;3465 标注数量(txt文件个数)&#xff1a;3465 标注类别数&#xff1a;3 标注类别名称&#xff1a;[person, helmet, vest] 序号类别名称图片数框数1person346594732helm…

Android 13 GMS 内置壁纸

如图&#xff0c;原生系统上&#xff0c;设备上的壁纸 显示系统内置壁纸。如果没有添加内置壁纸&#xff0c;就显示默认的壁纸。点击进去就是预览页面 扩展下&#xff0c;默认壁纸在 frameworks/base/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png frameworks/b…