当前位置: 首页 > news >正文

Java链表

提示:多练才是王道,加油ヾ(◍°∇°◍)ノ゙

Java链表

    • 1. 链表的引入
    • 2. 链表的分类
    • 3. 单链表模拟实现
      • 3.1 典型题目
    • 4. LinkedList
      • 4.1 LinkedList的模拟实现
      • 4.2 LinkedList常见方法总结
        • 4.2.1 声明及实例化
        • 4.2.2 构造方法
        • 4.2.3 遍历
        • 4.2.4 其他常用方法
    • 5. ArrayList和LinkedList比较


1. 链表的引入

在上篇博客中,我们讲解了ArrayList,它是基于动态数组实现的,虽然它支持随机访问且效率高(O(1)),但是插入/删除的效率低,需要大量元素的整体搬移,时间复杂度为O(n),且当容量不够时的扩容会产生很大的性能开销.

而LinkedList的出现解决了这些问题,LinkedList基于双向链表,插入/删除更高效(O(1)):只要找到对应位置节点,修改前后指针即可,不需整体搬移元素;且没有扩容问题,LinkedList节点动态分配,不存在数组容量不足的问题.

当然LinkedList也带来了新的问题,比如随机访问性能差(O(n)),额外内存开销大等.

2. 链表的分类

根据三个指标:单向/双向,带头/不带头,循环/非循环,可以将链表分出8种(2x2x2):

在这里插入图片描述

其中单向不带头非循环双向不带头非循环两种在题目中用得最多.接下来也只介绍这两个.

但是需要强调的是,Java标准集合框架(Java.util包)中,官方只提供了双向链表的实现,即LinkedList,因为它提供了功能性和性能的最佳平衡.

那为什么还要将链表分为8种,还要学单链表?

答:链表分为8种是从数据结构与算法的抽象层面来讨论的,追求的是对空间问题的全覆盖,从单/双,循环/非循环,带头/不带头这几个维度进行组合,从理论上是完整且严谨的,它告诉你所有可能的选择.而学习单链表是理解链表概念的基础,它最简单,最能清晰展现"节点"和"指针"的核心思想.

标准库的设计目标是为大多数开发者解决大多数常见问题提供通用,高效的工具,因此功能优先,双向链表的功能远比单链表强大,虽然多耗费一点内存,但是开销是能接受的.整体来说,提供了功能性和性能的最佳平衡.

生动比喻:

  • 理论上的8种链表:就像汽车工程师学习发动机的原理。他们有直列、V型、水平对置、转子、电动等多种模型。他们需要了解每一种的优缺点和适用场景。
  • 标准库提供的双向链表:就像普通人去4S店买车。市面上大部分家用车都使用一种经过市场验证、在成本、性能和可靠性上取得最佳平衡的发动机(比如直列四缸或V6)。你不会要求4S店为你提供所有8种发动机选项,因为那既不经济,也不实用。

3. 单链表模拟实现

  1. 链表定义和各方法实现:
public class MySingleLinkedList {//节点定义为内部类class ListNode {public int val;public ListNode next;public ListNode(int val) {this.val = val;}}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);ListNode node5=new ListNode(56);head=node1;node1.next=node2;node2.next=node3;node3.next=node4;node4.next=node5;node5.next=null;}public void display() {ListNode node=head;while(node!=null) {System.out.print(node.val+" ");node=node.next;}System.out.println();}public int size() {ListNode node=head;int count=0;while(node!=null) {count++;node=node.next;}return count;}public void addAtHead(int val) {ListNode node=new ListNode(val);node.next=head;head=node;}public void addAtTail(int val) {ListNode node=new ListNode(val);ListNode cur=head;if(head==null) {    //若head为空,不能访问head.nexthead=node;return;}while(cur.next!=null) {cur=cur.next;}cur.next=node;}public void addAtIndex(int index,int val) {//判断index的合法性try {checkPos(index);}catch(IndexNotLegalException e) {e.printStackTrace();}//头插尾插if(index==0) {addAtHead(val);return;}if(index==size()-1) {addAtTail(val);return;}//找到cur的前一个节点ListNode node=new ListNode(val);ListNode cur=head;int count=0;while(count!=index-1){    //cur要走index-1步,才能走到cur前面的节点cur=cur.next;count++;}//连接node.next=cur.next;cur.next=node;}private void checkPos(int pos) throws IndexNotLegalException{if(pos<0 || pos>size()) {throw new IndexNotLegalException("Index不合法");}}public boolean contains(int val) {ListNode cur=head;while(cur!=null) {if(cur.val==val) return true;cur=cur.next;}return false;}//删除第一次出现的valpublic void remove(int val) {if(head==null) return;if(head.val==val) {head=head.next;return;}ListNode cur=head;while(cur.next!=null) {if(cur.next.val==val) {cur.next=cur.next.next;return;}cur=cur.next;}}//删除所有valpublic void removeAllKey(int val) {//1.判空if(head==null) return;//2.定义prev和curListNode prev=head;ListNode cur=head.next;//3.开始判断并删除while(cur!=null) {if(cur.val==val) {prev.next=cur.next;} else {prev=cur;}cur=cur.next;}//4.处理头结点if(head.val==val) {head=head.next;}}public void clear() {
//        head=null;//最直接的方式ListNode cur=head;while(cur!=null) {ListNode curN=cur.next;cur.next=null;cur=curN;}head=null;}
}
  1. addAtIndex方法抛出的异常:
public class IndexNotLegalException extends RuntimeException {public IndexNotLegalException() {}public IndexNotLegalException(String message) {super(message);}
}
  1. 测试类:
public class Test {public static void main(String[] args) {MySingleLinkedList msl=new MySingleLinkedList();
//        msl.createList();
//        msl.display();
//        System.out.println(msl.size());
//        System.out.println("======测试头插=====");
//        msl.addAtHead(12);
//        msl.addAtHead(23);
//        msl.addAtHead(34);
//        msl.addAtHead(56);
//        msl.display();
//        System.out.println("======测试尾插=====");
//        msl.addAtTail(1);
//        msl.addAtTail(2);
//        msl.addAtTail(3);
//        msl.display();
//        System.out.println("========测试在index插入节点========");
//        msl.addAtIndex(0,100);
//        msl.addAtIndex(1,100);
//        msl.addAtIndex(5,100);
//        msl.display();
//        System.out.println("=====测试contains方法=====");
//        System.out.println(msl.contains(100));
//        System.out.println(msl.contains(99));
//        System.out.println("=====测试remove方法=====");
////        msl.remove(100);
//        msl.removeAllKey(100);
//        msl.display();
//        System.out.println("=====测试clear方法=====");
//        msl.clear();
//        msl.display();msl.addAtTail(10);msl.addAtTail(10);msl.addAtTail(10);msl.addAtTail(10);msl.removeAllKey(10);msl.display();}
}

比较基础,就不再一个一个讲了~~

3.1 典型题目

206. 反转链表 - 力扣(LeetCode)

876. 链表的中间结点 - 力扣(LeetCode)

面试题 02.02. 返回倒数第 k 个节点 - 力扣(LeetCode)

21. 合并两个有序链表 - 力扣(LeetCode)

链表分割_牛客题霸_牛客网

链表的回文结构_牛客题霸_牛客网

160. 相交链表 - 力扣(LeetCode)

141. 环形链表 - 力扣(LeetCode)

142. 环形链表 II - 力扣(LeetCode)

4. LinkedList

4.1 LinkedList的模拟实现

  1. LinkedList定义和各方法实现:
public class MyLinkedList {static class ListNode {public int val;public ListNode prev;public ListNode next;public ListNode(int val) {this.val = val;}}public ListNode head;public ListNode tail;//得到单链表的长度public int size(){int count=0;ListNode cur=head;while(cur!=null) {count++;cur=cur.next;}return count;}public void display(){ListNode cur=head;while(cur!=null) {System.out.print(cur.val+" ");cur=cur.next;}System.out.println();}//查找是否包含关键字key是否在单链表当中public boolean contains(int key){ListNode cur=head;while(cur!=null) {if(cur.val==key) {return true;}cur=cur.next;}return false;}//头插法public void addFirst(int data){ListNode node=new ListNode(data);if(head==null) head=tail=node;else {node.next=head;head.prev=node;head=node;}}//尾插法public void addLast(int data){ListNode node=new ListNode(data);if(head==null) head=tail=node;else {tail.next=node;node.prev=tail;tail=node;}}//任意位置插入,第一个数据节点为0号下标public void addIndex(int index,int data){try {checkIndex(index);}catch(IndexOutOfBoundsException e){e.printStackTrace();}if(index==0) {addFirst(data);return;}if(index==size()) {addLast(data);return;}//处理中间位置,首先要先找到indexListNode cur=head;ListNode node=new ListNode(data);int count=index;while(count!=0) {cur=cur.next;count--;}node.next=cur;cur.prev.next=node;node.prev=cur.prev;cur.prev=node;}private void checkIndex(int index) throws IndexNotLegalException{if(index<0 || index>size()) {throw new IndexNotLegalException("add时index不合法");}}//删除第一次出现关键字为key的节点public void remove(int key){ListNode cur=head;while(cur!=null) {if(cur.val==key) {  //找到了要删除的那个节点//若该节点是headif(head.val==key) {head=head.next;if (head != null) {//这个if处理的是,该链表只有一个节点,删除完头之后,链表为空的情况(若此时还继续下去的话,就空指针异常了)head.prev=null;}else {tail=null;//空链表,尾结点也要置为空}} else {    //该节点不是headcur.prev.next=cur.next;if(cur.next==null) {    //该节点是tailtail=cur.prev;}else { //该节点不是head也不是tailcur.next.prev=cur.prev;}}return;//删除一个节点后就返回}cur=cur.next;//没找到,继续往后走}}//删除所有值为key的节点public void removeAllKey(int key){ListNode cur=head;while(cur!=null) {if(cur.val==key) {  //找到了要删除的那个节点//若该节点是headif(head.val==key) {head=head.next;if (head != null) {//这个if处理的是,该链表只有一个节点,删除完头之后,链表为空的情况(若此时还继续下去的话,就空指针异常了)head.prev=null;}else {tail=null;//空链表,尾结点也要置为空}} else {    //该节点不是headcur.prev.next=cur.next;if(cur.next==null) {    //该节点是tailtail=cur.prev;}else { //该节点不是head也不是tailcur.next.prev=cur.prev;}}
//                return;//删除一个节点后就返回}cur=cur.next;//没找到,继续往后走}}public void clear(){ListNode cur=head;while(cur!=null) {ListNode curN=cur.next;
//            cur.val=null;cur.prev=null;cur.next=null;cur=curN;}head=tail=null;}
}
  1. add时抛出的异常:
public class IndexNotLegalException extends RuntimeException {public IndexNotLegalException() {}public IndexNotLegalException(String message) {super(message);}
}
  1. 测试类:
public static void main1(String[] args) {//模拟实现LinkedListMyLinkedList list=new MyLinkedList();list.addFirst(1);list.addFirst(2);list.addFirst(3);list.display();list.addLast(10);list.addLast(20);list.addLast(30);list.display();list.addIndex(0,100);list.addIndex(2,100);list.addIndex(8,100);list.display();list.remove(1);list.display();System.out.println("删除所有的100");list.removeAllKey(100);list.display();}

4.2 LinkedList常见方法总结

4.2.1 声明及实例化

在这里插入图片描述

查看LinkedList在源码中的第一可以看到,它实现了List和Deque接口,意味着它既能当做List使用,又能当做队列使用:

在这里插入图片描述

当做List使用,同样有两种写法,按需求选取:

在这里插入图片描述

特性LinkedList list1=…List list2=…
引用类型具体类(LinkedList)接口(List)
可调用的方法所有LinkedList的方法+所有List接口的方法仅限List接口中定义的方法
灵活性低,代码与LinkedList强绑定,难以更换实现高,只需修改new后面的实现即可,前面代码通常无需改动
推荐程度不常用,除非你明确需要用到LinkedList的特有功能更常用,符合面向对象设计原则
4.2.2 构造方法

同ArrayList一样,除了不含参数的构造方法外,还能创建包含指定集合中所有元素的新链表:

在这里插入图片描述

例如:

在这里插入图片描述

上述代码创建了一个包含Integer类型元素的ArrayList,并以其为数据源初始化了一个LinkedList.

4.2.3 遍历

几种遍历方法和ArrayList一样一样的

  1. for循环

在这里插入图片描述

  1. for-each循环

在这里插入图片描述

  1. 迭代器

在这里插入图片描述

4.2.4 其他常用方法
方法解释
boolean add(E e)尾插e
void add(int index,E element)将e插入到index位置
boolean addAll(Collecction<?extends E>c)尾插c中的元素
E remove(int index)删除index位置的元素
boolean remove(Object o)删除遇到的第一个o
E get(int index)获取index位置元素
E set(int index,E element)将下标index位置元素设置为element
void clear()清空
boolean contains(Object o)判断是否包含o
int indexOf(Object o)返回第一个o所在的下标
int lastIndexOf(Object o)返回最后一个o的下标
List subList(int fromIndex,int toIndex)截取部分list

5. ArrayList和LinkedList比较

核心差别对比表:

特性ArrayListLinkedList
底层数据结构动态数组(Object[] elementData)双向链表(Node first,last)
内存占用较小(仅存储数据本身和数组开销)较大(每个节点都需要额外的内存存储前后节点的引用)
访问性能极快O(1)
通过索引直接计算内存地址来访问
慢O(n)
必须从头部或尾部开始遍历链表
插入/删除性能平均较慢O(n)
需要移动后续元素
快O(1)
前提是已经定到了操作位置
尾部插入很快O(1)头尾插入时定位过程很快O(1)
中间/头部插入很慢(需要移动元素)中间插入时定位过程慢O(n)
扩容机制需要动态扩容(复制数组)无需扩容,按需动态增加节点

如何选择:

选择ArrayList的场景:

  • 需要频繁随机访问元素
  • 元素数量大致可知(避免频繁扩容)
  • 追求更好的内存效率和CPU缓存局部性(数组在内存中是连续的)

选择LinkedList的场景:

  • 需要频繁在列表头部或中间进行插入和删除操作
  • 不确定元素数量
  • 需要实现Deque(双端队列)的功能

文章转载自:

http://cnOGOiEw.fwwkr.cn
http://udwSkj2E.fwwkr.cn
http://TdUylQsI.fwwkr.cn
http://PvnQ0ar3.fwwkr.cn
http://2ZpiNvcd.fwwkr.cn
http://Mndtj5m2.fwwkr.cn
http://OUUeJ20d.fwwkr.cn
http://ADYN2go7.fwwkr.cn
http://Xe0pTo7T.fwwkr.cn
http://g6aCtD0s.fwwkr.cn
http://v2yswOxV.fwwkr.cn
http://GnQvkV4T.fwwkr.cn
http://OJohm7XS.fwwkr.cn
http://0VPbx9qZ.fwwkr.cn
http://0qo6j9tI.fwwkr.cn
http://fhFmVw6P.fwwkr.cn
http://rax7Tq20.fwwkr.cn
http://yWIqvvKD.fwwkr.cn
http://5Sy9csXz.fwwkr.cn
http://P1UhkWh5.fwwkr.cn
http://1n3FW9PF.fwwkr.cn
http://iMEhQr7i.fwwkr.cn
http://yAW7Ov5P.fwwkr.cn
http://QkV9e2uK.fwwkr.cn
http://q4ruieYw.fwwkr.cn
http://sZc3Hnm5.fwwkr.cn
http://nDkaTavw.fwwkr.cn
http://Iju0DrXC.fwwkr.cn
http://yyXEGwbS.fwwkr.cn
http://JRu2sJD6.fwwkr.cn
http://www.dtcms.com/a/380985.html

相关文章:

  • 【PostgreSQL内核学习:表达式】
  • 步骤流程中日志记录方案(类aop)
  • React.memo 小练习题 + 参考答案
  • Java 的即时编译器(JIT)优化编译探测技术
  • 《计算机网络安全》实验报告一 现代网络安全挑战 拒绝服务与分布式拒绝服务攻击的演变与防御策略(4)
  • 综合体EMS微电网能效管理系统解决方案
  • ARM2.(汇编语言)
  • 从“插件化“到“智能化“:解密Semantic Kernel中Microsoft Graph的架构设计艺术
  • TDengine 特殊函数 MODE() 用户手册
  • 导购类电商平台的安全架构设计:防刷单与反作弊系统实现
  • 阿里云可观测 2025 年 8 月产品动态
  • 阿里云监控使用
  • 九识智能与北控北斗合作研发的L4级燃气超微量高精准泄漏检测无人车闪耀服贸会,守护城市安全
  • vulhub漏洞复现-redis-4-unacc (redis未授权访问)
  • 数据库分库分表是考虑ShardingSphere 还是Mycat?
  • CSP认证练习题目推荐 (3)
  • R geo 然后读取数据的时候 make.names(vnames, unique = TRUE): invalid multibyte string 9
  • Linux:线程封装
  • 电动指甲刀技术方案概述
  • 机器人巡检与巡逻的区别进行详细讲解和对比
  • 程序内存中堆(Heap)和栈(Stack)的区别
  • 提高软件可靠性的思路
  • (1-10-2)MyBatis 进阶篇
  • ZedGraph库里实现坐标拖动图形的背景显示
  • SpringBoot应用开发指南:从入门到高级配置与自动装配原理
  • 怎么快速规划好旅行
  • 一带一路经济走廊及其途经城市图件
  • k8s的设计哲学
  • 城市污水管网流量监测方法
  • 计算机视觉进阶教学之特征检测