链表——LinkedList类的概述和实现

news2024/11/23 12:17:31

LinkedList类

1.1LinkedList类概述

  • LinkedList类底层是基于双向链表结构实现的,不同于ArrayList类和Vector类是基于数组实现的;
  • LinkedList类是非线程安全的;
  • LinkedList类元素允许为null,允许重复元素;
  • LinkedList类插入、删除效率高,查询效率低;
  • LinkedList类基于链表实现的,因此不存在容量不足的问题,不用动态扩容;
  • 双向链表有三个区域,前指针域、数据域、后指针域。
  • LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
  • LinkedList 实现 List 接口,能对它进行队列操作。
  • LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。
  • LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。
  • LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。
  • LinkedList 是非同步的。

1.2LinkedList类的方法

与ArrayList类相似,LinkedList类常见方法也有add()、get()、remove()、set()、size()等,用法也类似。

  • Object getFirst()        返回链表的第一个元素。
  • Object getLast()        返回链接列表的最后一个元素。
  • boolean contains(Object element)如果元素存在于列表中,则返回true。
  • void clear()                删除列表中的所有元素。 

1.3LinkedList类的特点

  • LinkedList的底层结构为双向链表,将零散的内存单元通过附加的引用关联起来体现出其顺序性,相比数组的连续空间存储,链表对内存的利用率更高;
  • 有序:插入元素的顺序和取出顺序一致;
  • 可重复:可以插入相同的元素(元素值也允许为null);
  • 插入、删除操作效率高,和ArrayList恰恰相反,按索引查找效率较低;
  • 线程不安全:没有线程锁,多个线程同步进行写操作的时候可能导致数据错误;
  • 使用场景:不会随机访问数据,更多的插入删除操作,更少的查询读取操作。 

链表

链表通常由一连串节点组成,每个节点包含任意的实例数据(data fields)和一或两个用来指向上一个/或下一个节点的位置的链接(links

链表Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。

使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。

单向链表

普通链表,也叫单链表或者单向链表(Single-Linked List)。单链表是链表中结构最简单的。

一个单链表的节点(Node)分为两个部分,第一个部分(data)保存或者显示关于节点的信息,另一个部分存储下一个节点的地址。最后一个节点存储地址的部分指向空值。

  • 查找:单向链表只可向一个方向遍历,一般查找一个节点的时候需要从第一个节点开始每次访问下一个节点,一直访问到需要的位置。
  • 插入:而插入一个节点,对于单向链表,我们只提供在链表头插入,只需要将当前插入的节点设置为头节点,next指向原头节点即可。
  • 删除:删除一个节点,我们将该节点的上一个节点的next指向该节点的下一个节点。

查找图:

在表头增加节点:

删除节点:

2.1 单向链表的具体实现

public class SingleLinkedList {
    private int size;//链表节点的个数
    private Node head;//头节点

    public SingleLinkedList() {
        size = 0;
        head = null;
    }

    //链表的每个节点类
    private class Node {
        private Object data;//每个节点的数据
        private Node next;//每个节点指向下一个节点的连接

        public Node(Object data) {
            this.data = data;
        }
    }

    //在链表头添加元素
    public Object addHead(Object obj) {
        Node newHead = new Node(obj);
        if (size == 0) {
            head = newHead;
        } else {
            newHead.next = head;
            head = newHead;
        }
        size++;
        return obj;
    }

    //在链表头删除元素
    public Object deleteHead() {
        Object obj = head.data;
        head = head.next;
        size--;
        return obj;
    }

    //查找指定元素,找到了返回节点Node,找不到返回null
    public Node find(Object obj) {
        Node current = head;
        int tempSize = size;
        while (tempSize > 0) {
            if (obj.equals(current.data)) {
                return current;
            } else {
                current = current.next;
            }
            tempSize--;
        }
        return null;
    }

    //删除指定的元素,删除成功返回true
    public boolean delete(Object value) {
        if (size == 0) {
            return false;
        }
        Node current = head;
        Node previous = head;
        while (current.data != value) {
            if (current.next == null) {
                return false;
            } else {
                previous = current;
                current = current.next;
            }
        }
        //如果删除的节点是第一个节点
        if (current == head) {
            head = current.next;
            size--;
        } else {//删除的节点不是第一个节点
            previous.next = current.next;
            size--;
        }
        return true;
    }

    //判断链表是否为空
    public boolean isEmpty() {
        return (size == 0);
    }

    //显示节点信息
    public void display() {
        if (size > 0) {
            Node node = head;
            int tempSize = size;
            if (tempSize == 1) {//当前链表只有一个节点
                System.out.println("[" + node.data + "]");
                return;
            }
            while (tempSize > 0) {
                if (node.equals(head)) {
                    System.out.print("[" + node.data + "->");
                } else if (node.next == null) {
                    System.out.print(node.data + "]");
                } else {
                    System.out.print(node.data + "->");
                }
                node = node.next;
                tempSize--;
            }
            System.out.println();
        } else {//如果链表一个节点都没有,直接打印[]
            System.out.println("[]");
        }
    }
}

测试:

public void testSingleLinkedList(){
    SingleLinkedList singleList = new SingleLinkedList();
    singleList.addHead("A");
    singleList.addHead("B");
    singleList.addHead("C");
    singleList.addHead("D");
    //打印当前链表信息
    singleList.display();
    //删除C
    singleList.delete("C");
    singleList.display();
    //查找B
    System.out.println(singleList.find("B").data);
}

打印结果:

[D->C->B->A]
[D->B->A]
B

2.2 用单向链表实现栈

栈的pop()方法和push()方法,对应于链表的在头部删除元素deleteHead()以及在头部增加元素addHead()

public class StackSingleLink {
    private SingleLinkedList link;
    
    public StackSingleLink() {
        link = new SingleLinkedList();
    }
    
    //添加元素
    public void push(Object obj) {
        link.addHead(obj);
    }
    
    //移除栈顶元素
    public Object pop() {
        Object obj = link.deleteHead();
        return obj;
    }
    
    //判断是否为空
    public boolean isEmpty() {
        return link.isEmpty();
    }
    
    //打印栈内元素信息
    public void display() {
        link.display();
    }
}

双端链表(单向引用)

对于单向链表,我们如果想在尾部添加一个节点,那么必须从头部一直遍历到尾部,找到尾节点,然后在尾节点后面插入一个节点。这样操作很麻烦,如果我们在设计链表的时候多个对尾节点的引用,那么会简单很多。

链表的节点类保留下一节点的引用(单向引用)。这就是双端链表。

双端链表:保留对头节点、尾节点的引用,可以从尾节点插入,但只能从头节点删除,只能从一个方向遍历,相当于单向链表多了一个对尾节点的引用。

双端链表的特点:双端链表的含有对第一个节点和最后一个节点的引用

双端链表的操作读取(引用)插入删除
头部
尾部x

注意和后面讲的双向链表的区别!!!

3.1 双端链表的具体实现

public class DoublePointLinkedList {
    private Node head;//头节点
    private Node tail;//尾节点
    private int size;//节点的个数

    private class Node {
        private Object data;
        private Node next;

        public Node(Object data) {
            this.data = data;
        }
    }

    public DoublePointLinkedList() {
        size = 0;
        head = null;
        tail = null;
    }

    //链表头新增节点
    public void addHead(Object data) {
        Node node = new Node(data);
        if (size == 0) {//如果链表为空,那么头节点和尾节点都是该新增节点
            head = node;
            tail = node;
            size++;
        } else {
            node.next = head;
            head = node;
            size++;
        }
    }

    //链表尾新增节点
    public void addTail(Object data) {
        Node node = new Node(data);
        if (size == 0) {//如果链表为空,那么头节点和尾节点都是该新增节点
            head = node;
            tail = node;
            size++;
        } else {
            tail.next = node;
            tail = node;
            size++;
        }
    }

    //删除头部节点,成功返回true,失败返回false
    public boolean deleteHead() {
        if (size == 0) {//当前链表节点数为0
            return false;
        }
        if (head.next == null) {//当前链表节点数为1
            head = null;
            tail = null;
        } else {
            head = head.next;
        }
        size--;
        return true;
    }

    //判断是否为空
    public boolean isEmpty() {
        return (size == 0);
    }

    //获得链表的节点个数
    public int getSize() {
        return size;
    }

    //显示节点信息
    public void display() {
        if (size > 0) {
            Node node = head;
            int tempSize = size;
            if (tempSize == 1) {//当前链表只有一个节点
                System.out.println("[" + node.data + "]");
                return;
            }
            while (tempSize > 0) {
                if (node.equals(head)) {
                    System.out.print("[" + node.data + "->");
                } else if (node.next == null) {
                    System.out.print(node.data + "]");
                } else {
                    System.out.print(node.data + "->");
                }
                node = node.next;
                tempSize--;
            }
            System.out.println();
        } else {//如果链表一个节点都没有,直接打印[]
            System.out.println("[]");
        }
    }
}

3.2 用双端链表实现队列

public class QueueLinkedList {
    
    private DoublePointLinkedList dp;

    public QueueLinkedList() {
        dp = new DoublePointLinkedList();
    }

    public void insert(Object data) {
        dp.addTail(data);
    }

    public void delete() {
        dp.deleteHead();
    }

    public boolean isEmpty() {
        return dp.isEmpty();
    }

    public int getSize() {
        return dp.getSize();
    }

    public void display() {
        dp.display();
    }
    
}

双向链表

我们知道单向链表只能从一个方向遍历,那么双向链表(Double-Linked List)它可以从两个方向遍历。

具体代码实现:

public class TwoWayLinkedList {
    private Node head;//表示链表头
    private Node tail;//表示链表尾
    private int size;//表示链表的节点个数

    private class Node {
        private Object data;
        private Node next;
        private Node prev;

        public Node(Object data) {
            this.data = data;
        }
    }

    public TwoWayLinkedList() {
        size = 0;
        head = null;
        tail = null;
    }

    //在链表头增加节点
    public void addHead(Object value) {
        Node newNode = new Node(value);
        if (size == 0) {
            head = newNode;
            tail = newNode;
            size++;
        } else {
            head.prev = newNode;
            newNode.next = head;
            head = newNode;
            size++;
        }
    }

    //在链表尾增加节点
    public void addTail(Object value) {
        Node newNode = new Node(value);
        if (size == 0) {
            head = newNode;
            tail = newNode;
            size++;
        } else {
            newNode.prev = tail;
            tail.next = newNode;
            tail = newNode;
            size++;
        }
    }

    //删除链表头
    public Node deleteHead() {
        Node temp = head;
        if (size != 0) {
            head = head.next;
            head.prev = null;
            size--;
        }
        return temp;
    }

    //删除链表尾
    public Node deleteTail() {
        Node temp = tail;
        if (size != 0) {
            tail = tail.prev;
            tail.next = null;
            size--;
        }
        return temp;
    }

    //获得链表的节点个数
    public int getSize() {
        return size;
    }

    //判断链表是否为空
    public boolean isEmpty() {
        return (size == 0);
    }

    //显示节点信息
    public void display() {
        if (size > 0) {
            Node node = head;
            int tempSize = size;
            if (tempSize == 1) {//当前链表只有一个节点
                System.out.println("[" + node.data + "]");
                return;
            }
            while (tempSize > 0) {
                if (node.equals(head)) {
                    System.out.print("[" + node.data + "->");
                } else if (node.next == null) {
                    System.out.print(node.data + "]");
                } else {
                    System.out.print(node.data + "->");
                }
                node = node.next;
                tempSize--;
            }
            System.out.println();
        } else {//如果链表一个节点都没有,直接打印[]
            System.out.println("[]");
        }

    }
}

有序链表

前面的链表实现插入数据都是无序的,无序链表最大特点就是在头部或尾部增加新节点。在有些应用中需要链表中的数据有序。

有序链表:递增,递减或者其他满足一定条件的规则,插入数据时,从头结点开始遍历每个节点,在满足规则的地方插入新节点。

在有序链表中,数据是按照关键值有序排列的。一般在大多数需要使用有序数组的场合也可以使用有序链表。有序链表优于有序数组的地方是插入的速度(因为元素不需要移动),另外链表可以扩展到全部有效的使用内存,而数组只能局限于一个固定的大小中。

public class OrderLinkedList {
    private Node head;

    private class Node {
        private int data;
        private Node next;

        public Node(int data) {
            this.data = data;
        }
    }

    public OrderLinkedList() {
        head = null;
    }

    //插入节点,并按照从小打到的顺序排列
    public void insert(int value) {
        Node node = new Node(value);
        Node pre = null;
        Node current = head;
        while (current != null && value > current.data) {
            pre = current;
            current = current.next;
        }
        if (pre == null) {
            head = node;
            head.next = current;
        } else {
            pre.next = node;
            node.next = current;
        }
    }

    //删除头节点
    public void deleteHead() {
        head = head.next;
    }

    public void display() {
        Node current = head;
        while (current != null) {
            System.out.print(current.data + " ");
            current = current.next;
        }
        System.out.println("");
    }
}

在有序链表中插入和删除某一项最多需要O(N)次比较,平均需要O(N/2)次,因为必须沿着链表上一步一步走才能找到正确的插入位置,然而可以最快速度删除最值,因为只需要删除表头即可,如果一个应用需要频繁的存取最小值,且不需要快速的插入,那么有序链表是一个比较好的选择方案。比如优先级队列可以使用有序链表来实现。

5.1 有序链表和无序数组组合排序

比如有一个无序数组需要排序,解冒泡排序、选择排序、插入排序这三种简单的排序时,需要的时间级别都是O(N^2^)

现在我们讲解了有序链表之后,对于一个无序数组,我们先将数组元素取出,一个一个的插入到有序链表中,然后将他们从有序链表中一个一个删除,重新放入数组,那么数组就会排好序了。

和插入排序一样,如果插入了N个新数据,那么进行大概N^2^/4次比较。但是相对于插入排序,每个元素只进行了两次排序,一次从数组到链表,一次从链表到数组,大概需要2*N次移动,而插入排序则需要N^2^次移动,

效率肯定是比前面讲的简单排序要高,但是缺点就是需要开辟差不多两倍的空间,而且数组和链表必须在内存中同时存在,如果有现成的链表可以用,那么这种方法还是挺好的。

总结

上面我们讲了各种链表,每个链表都包括一个LinkedList对象和许多Node对象,LinkedList对象通常包含头和尾节点的引用,分别指向链表的第一个节点和最后一个节点。

而每个节点对象通常包含数据部分data,以及对上一个节点的引用prev和下一个节点的引用next,只有下一个节点的引用称为单向链表,两个都有的称为双向链表。

next值为null则说明是链表的结尾,如果想找到某个节点,我们必须从第一个节点开始遍历,不断通过next找到下一个节点,直到找到所需要的。

栈和队列都是ADT,可以用数组来实现,也可以用链表实现。

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

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

相关文章

Python识别抖音Tiktok、巨量引擎滑块验证码识别

由于最近比较忙,所以本周搞了一个相对简单的验证码,就是抖音Tiktok的滑块验证码,这也是接到客户的一个需求。这种验证码通常在电脑端登录抖音、巨量引擎的的时候出现。 首先看一下最终的效果: 验证码识别过程 1、利用爬虫采集图…

CS录屏教程,录制游戏需要注意哪些方面?

​最近有个CS手游的玩家小伙伴咨询想要做一些游戏视频录制,但是不知道有哪些好用的工具来使用,对于游戏录制我们其实是需要注意一些事项的,想要观众的观感上比较好就需要把握好视频的帧率等问题,下面我们就来看看录制方法和需要注…

Vue3自定义简单的Swiper滑动组件-触控板滑动鼠标滑动左右箭头滑动-demo

代码实现了一个基本的滑动功能,通过鼠标按下、鼠标松开和鼠标移动事件来监听滑动操作。 具体实现逻辑如下: 在 onMounted 钩子函数中,我们为滚动容器添加了三个事件监听器:mousedown 事件:当鼠标按下时,设置…

windows环境下编译OpenJDK12

环境:Windows11 目录: 1、下载OpenJDK12源码 下载地址: https://hg.openjdk.org/jdk/jdk12 点击zip下载到本地。 解压到本地。 Tip:注意本地路径中最好不要包含中文或空格。 2、阅读一遍doc/building.html 如果只是想构建J…

C++11 新特性 ---- 原始字面量

一、原始字面量 R “xxx(原始字符串)xxx”&#xff0c;其中&#xff08;&#xff09;两边的字符串可以省略。 #include <iostream> #include <string> using namespace std; int main() {string str1 R"(D:\hello\heheda\test.txt)";string str2 R&q…

dfs之卒的遍历

题面 题目描述 在一张nm 的棋盘上&#xff08;如 6 行 7 列&#xff09;的最左上角(1,1) 的位置有一个卒。该卒只能向下或者向右走&#xff0c;且卒采取的策略是先向下&#xff0c;下边走到头就向右&#xff0c;请问从(1,1) 点走到 (n,m) 点可以怎样走&#xff0c;输出这些走法…

TM4C123库函数学习(1)--- 点亮LED+TM4C123的ROM函数简介+keil开发环境搭建

前言 &#xff08;1&#xff09; 首先&#xff0c;我们需要知道TM4C123是M4的内核。对于绝大多数人而言&#xff0c;入门都是学习STM32F103&#xff0c;这款芯片是采用的M3的内核。所以想必各位对M3内核还是有一定的了解。M4内核就是M3内核的升级版本&#xff0c;他继承了M3的的…

pka 预测汇总

汇总内容来自 Open-Source Machine Learning in Computational Chemistry 中表格涉及的内容 文章目录 1.DeepKa2.Machine learning meets pKa 1.DeepKa 预测蛋白质的 pka&#xff0c;文章是 Basis for Accurate Protein p Ka Prediction with Machine Learning 和 Protein p…

web-csrf

目录 CSRF与XSS的区别&#xff1a; get请求 原理&#xff1a; pikachu为例 post请求 pikachu为例 CSRF与XSS的区别&#xff1a; CSRF是借用户的权限完成攻击&#xff0c;攻击者并没有拿到用户的权限&#xff0c;而XSS是直接盗取到了用户的权限 get请求 原理&#xff1a;…

20230807在WIN10下使用python3将TXT文件转换为DOCX(在UTF8编码下转换为DOCX有多一行的瑕疵)

20230807在WIN10下使用python3将TXT文件转换为DOCX&#xff08;在UTF8编码下转换为DOCX有多一行的瑕疵&#xff09; 2023/8/7 12:58 https://translate.google.com/?slen&tlzh-CN&opdocs 缘起&#xff0c;由于google的文档翻译不支持SRT/TXT格式的字幕&#xff0c;因此…

利用Arthas+APM监控进行Java性能深度定位

大家可能都用过APM监控&#xff0c;包括开源的Skywalking、商用的卓豪&#xff08;ZOHO&#xff09;ManageEngine APM应用性能监控、以及云监控产品如听云&#xff08;Server监控&#xff09;&#xff0c;这些APM监控产品大大方便了我们实时监控应用性能&#xff0c;并实现性能…

04-6_Qt 5.9 C++开发指南_QListWidget和QToolButton

文章目录 1. 实例简介2. 源码2.1 混合式界面设计2.2 mainwindow.h2.3 mainwindow.cpp 1. 实例简介 Qt 中用于项 (Item)处理的组件有两类&#xff0c;一类是 Item Views&#xff0c;包括 QListView、QTreeView、QTableView、QColumnView 等;另一类是 Item Widgets&#xff0c;包…

用html+javascript打造公文一键排版系统14:为半角和全角字符相互转换功能增加英文字母、阿拉伯数字、标点符号、空格选项

一、实际工作中需要对转换选项细化内容 在昨天我们实现了最简单的半角字符和全角字符相互转换功能&#xff0c;就是将英文字母、阿拉伯数字、标点符号、空格全部进行转换。 在实际工作中&#xff0c;我们有时只想英文字母、阿拉伯数字、标点符号、空格之中的一两类进行转换&a…

FTP Server(二)

问题 使用Centos8通过命令行登录FTP服务器后发现乱码&#xff0c;无法显示中文 原因 FTP服务器是windows系统使用的是GBK编码&#xff0c;大多数Linux版本默认使用的是UTF-8 解决 在家目录下新建编辑 .lftprc 文件 [rootwenzi ~]#pwd /root [rootwenzi ~]#vim .lftprc debu…

狂神说-通俗易懂的23种设计模式

狂神说-通俗易懂的23种设计模式 文章目录 1、设计模式概述2、OOP七大原则4、工厂模式5、抽象工厂模式6、建造者模式7、原型模式8、适配器模式9、桥接模式10、静态代理模式11、静态代理再理解12、动态代理详解 1、设计模式概述 设计模式的基本要素&#xff1a; 1、模式名称 2、…

以太网DHCP协议(十)

目录 一、工作原理 二、DHCP报文 2.1 DHCP报文类型 2.2 DHCP报文格式 当网络内部的主机设备数量过多是&#xff0c;IP地址的手动设置是一件非常繁琐的事情。为了实现自动设置IP地址、统一管理IP地址分配&#xff0c;TCPIP协议栈中引入了DHCP协议。 一、工作原理 使用DHCP之…

2022 robocom 世界机器人开发者大赛-本科组(国赛)

RC-u1 智能红绿灯 题目描述&#xff1a; RC-u1 智能红绿灯 为了最大化通行效率同时照顾老年人穿行马路&#xff0c;在某养老社区前&#xff0c;某科技公司设置了一个智能红绿灯。 这个红绿灯是这样设计的&#xff1a; 路的两旁设置了一个按钮&#xff0c;老年人希望通行马路时会…

C语言预处理命令 #error 学习

#error命令是C/C语言的预处理命令之一&#xff0c;当预处理器预处理到#error命令时将停止编译并输出用户自定义的错误消息。 如下代码输出数字1000&#xff0c;如果加了 #error&#xff0c;构建时不会通过&#xff0c;提示出错如下&#xff1b; 这可能在大型项目中比较有用&am…

前端小练--社区主页

文章目录 前言首页结构固定导航栏左侧导航itemitem标志 头部推荐文章展示ITEM实现ToolTip完整实现 首页完整实现 前言 废话不多说&#xff0c;直接看到效果&#xff1a; 是的也许你已经发现了这个页面和某个网站长得贼像。没错是这样的&#xff0c;这个布局我确实看起来很舒服…

【Rust】Rust学习 第五章使用结构体组织相关联的数据

5.1 定义结构体并实例化结构体 定义结构体&#xff0c;需要使用 struct 关键字并为整个结构体提供一个名字。结构体的名字需要描述它所组合的数据的意义。接着&#xff0c;在大括号中&#xff0c;定义每一部分数据的名字和类型&#xff0c;我们称为 字段&#xff08;field&…