【Java---数据结构】链表LinkedList
- 游戏开发
- 2025-09-12 00:09:01

1. 链表的概念
链表用于存储一系列元素,由一系列节点组成,每个节点包含两部分:数据域和指针域。
数据域:用于存储数据元素
指针域:用于指向下一个节点的地址,通过指针将各个节点连接在一起,形成一个链式结构。
注意:
链表在逻辑上是连续的,空间上并不是连续的
根据指针的连接方式,链表可以分为单向链表和双向链表
注意:
单向链表和双向链表的结点存在差异
2. 单向链表单向链表是链表的一种,它的每个节点除了存储数据外,还包含一个指针指向下一个节点地址
2.1 单向列表的节点每个节点只有一个指针,指向下一个节点。
最后一个节点的指针指向null,表示链表结束。
代码实现:
class ListNode{ int val; ListNode next;//next指向新的节点 public ListNode(int val) { this.val = val; } }注意:
结点一般都是从堆上申请出来,每次在堆上申请的空间,按照一种策略来进行分配,多次申请出来的空间,可能连续,也可能不连续
2.2 链表的创建1. 创建一个节点
2.创建第二个节点,让第一个节点的指针域存放第一个节点的地址,以此类推,可以创建出链表
代码实现:
public void createList(){ //创建节点 ListNode listNode = new ListNode(15); ListNode listNode1 = new ListNode(56); ListNode listNode2 = new ListNode(45); ListNode listNode3 = new ListNode(88); //节点连接 listNode.next = listNode1; listNode1.next = listNode2; listNode2.next = listNode3; this.head = listNode; }注意:
不要忘记标注链表的头节点,当输出头节点时,会输出整个链表的数据
2.3 链表的种类 2.4 链表的基本操作 2.4.1 增加 (1)头插法主要操作:
将新创建的节点的指针域更改为头节点的地址 将头节点的位置进行改变 public void addFirst(int data){ //代码不能交换 //必须先绑定信息 ListNode listNode = new ListNode(data); listNode.next = head; head = listNode; }注意:代码的先后顺序不能颠倒,否则获取不到listNode.next的位置
(2)尾插法主要操作:
将最后一个节点的指针域指向新节点的地址特殊情况:
链表内没有一个节点,那么让新节点成为头节点代码实现:
public void addLast(int data){ ListNode listNode = new ListNode(data); ListNode cur = head; if(cur==null){ head = listNode; return ; } while(cur.next!=null){//关注next的指向,找到最后一个节点 cur = cur.next; } cur.next = listNode; } (3)固定位置插入主要操作:
找到要插入位置的前一个位置(cur)让新节点的指针域指向要插入位置的旧节点让cur指针域指向新节点的地址注意:第二步和第三步不能交换位置,如果交换,会导致cur的位置发生改变,导致无法找到要插入位置的地址
代码实现:
public void addIndex(int index,int data){ if(index<0||index>size()){ System.out.println("位置有问题"); return; } if(index == 0){ addFirst(data); return; } if(index == size()){ addLast(data); return ; } ListNode cur = head; int count = 0; ListNode listNode = new ListNode(data); while(count<index-1){ cur =cur.next; count++; } //不能互换位置 listNode.next = cur.next; cur.next = listNode; }注意:
不要忘记检查,插入的位置是否合法
如果插入的位置为0;那么意味着头插法,位置为元素的长度,那么就是尾插法
2.4.2 删除 (1)删除第一个出现的元素主要步骤:
找到你要删除元素的前一个节点找到你要删除的节点进行删除操作代码实现:
public void remove(int data){ if(head ==null){ return; } if(head.val ==data){ head = head.next; return; } //1.找到你要删除的前一个节点 ListNode cur = head; int count = 0; while(cur.next!=null){ if(cur.next.val == data){ break; } count++; cur= cur.next; } //找到要删除的节点 ListNode del = head; int delindex = 0; while (del!=null){ if(del.val ==data){ break; } delindex++; del = del.next; } //删除操作 cur.next = del.next; }注意:删除的具体操作就是:删除节点的前一个节点的指向发生改变,指向删除元素的指向
(2)删除出现的所有元素主要步骤:
初始化两个节点,cur节点进行判断值是否相同,previous是cur节点的前一个结点,方便进行删除操作进行遍历链表,找到相同的值,则进行删除操作,反之将两个节点都向后移动一位代码实现:
if(head ==null){ return; } ListNode cur = head.next; ListNode previous = head; while(cur!=null){ if(cur.val==data){ previous.next = cur.next; cur = cur.next; }else{ previous = cur;//注意,小心写成 previous.next = cur;//错误 cur = cur.next; } } if(head.val ==data){ head = head.next; } }注意:不要忘记对头节点进行判断
2.4.3 查看 (1)查看链表是否存在元素 public boolean contains(int value){ ListNode cur = head; while(cur!=null){ if(cur.val==value){ return true; } cur = cur.next; } return false; }主要步骤:
采用遍历的形式,如果找到元素,则返回true,反之,返回false
2.4.4 获取链表长度 int size(){ int count = 0; ListNode cur = head; while (cur!=null){ cur = cur.next; count++; } return count; } 2.4.5 清空链表 void clear(){ ListNode cur = head; ListNode curNext ; while(cur!=null){ curNext = cur.next; cur.next = null; cur = curNext; } head = null; } }注意: 遍历链表逐个释放节点的引用,让每个节点不再被引用
3. 快慢指针思想:通过使用两个速度不同的指针(快指针和慢指针)来遍历数据结构,从而实现特定的功能
注意:本质就是利用指针的移动速度差异来解决问题
常见的解决情况:
(1)找出中间的节点 ListNode findListNode(Text_2 list){ ListNode mid = head; if(head == null){ return null; } if(head.next ==null){ return head; } int count = size(list); int midCount = count/2; while (midCount>0){ mid = mid.next; midCount--; } return mid; }这是解决这种问题,我们第一个想到的方法,需要遍历两次链表,获取长度和中间节点 ,复杂度高,下面是我们引用了快慢指针:
ListNode findListNode1(){ ListNode fast = head; ListNode slow = head; while(fast!=null&&fast.next!=null){//不能交换 fast = fast.next.next; slow = slow.next; } return slow; }核心思想:使用快慢双指针,快的是慢的二倍;那么快的到了,慢的一定就在中间
(2)找出倒数第k个节点 ListNode findListNode(int k){ if(head ==null){ return null; } if(k<=0||k>size()){ System.out.println("k取值不对"); return null; } ListNode slow = head; ListNode fast = head; int count = k-1; while (count>0){ fast = fast.next; count--; } while(fast!=null&&fast.next!=null){ fast =fast.next; slow =slow.next; } return slow; }核心思想:快的比慢的先走了k-1个,然后每次都移动一个, 那么快的到了,满的就是倒数第k个.
先走k-1个,因为下标从0开始
4. 双向链表双向链表是链表的一种,它的每个节点除了存储数据外,还包含两个指针:一个指向前一个节点,另一个指向下一个节点
4.1 双向列表的节点注意:
由于有前驱指针,删除和插入操作更高效。
每个节点需要额外存储一个指针,因此比单向链表占用更多内存。
可以从头节点向后遍历,也可以从尾节点向前遍历。
代码实现:
class ListNode{ int val; ListNode prev; ListNode next; public ListNode(int val) { this.val = val; } } 4.2 LinkedList在 Java 中,LinkedList是java.util 包中的一个类,LinkedList的底层使用了双向链表
4.3 LinkedList的使用 4.3.1 LinkedList的构造 (1)无参构造 //调用无参构造 List<Integer> list =new LinkedList<>(); (2)利用Collection构建 List<Integer> list1 =new LinkedList<>(); list1.add(1); list1.add(2); list1.add(3); System.out.println(list1); List<Integer> list2 = new LinkedList<>(list1); list2.add(2); System.out.println(list2);注意:会继承传入实例对象的所有元素,并且元素的顺序也会保持一致
4.3.2 LinkedList的常用APIboolean add(E e)
尾插
void add(int index, E element)
在index位置添加元素
boolean addAll(Collection<? 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<E> subList(int fromIndex, int toIndex)
截取顺序表的一部分
(1)增加 public class Add { public static void main(String[] args) { //尾插法 LinkedList<Integer> list =new LinkedList<>(); list.add(1); list.add(2); list.add(3); System.out.println(list); //插入固定位置 LinkedList<Integer> list1 = new LinkedList<>(); list1.add(0,1); list1.add(0,6); list1.add(1,9); System.out.println(list1); //尾插所有元素 Integer[] array = {9,99,999}; list.addAll(Arrays.asList(array)); System.out.println(list); } } (2)删除 public class Remove { public static void main(String[] args) { LinkedList<Integer> list = new LinkedList<>(); list.add(1); list.add(2); list.add(3); //删除下标的数 list.remove(2); System.out.println(list); //删除第一个出现的数 list.remove(new Integer(2)); System.out.println(list); } } (3)修改 public class Set { public static void main(String[] args) { LinkedList<Integer> list = new LinkedList<>(); list.add(1); list.add(2); list.add(3); list.set(1,999); System.out.println(list); } } (4)获取 public class Get { public static void main(String[] args) { LinkedList<Integer> list = new LinkedList<>(); list.add(1); list.add(2); list.add(3); int x = list.get(2); System.out.println(x); } } (5)清空 public class Clear { public static void main(String[] args) { LinkedList<Integer> list = new LinkedList<>(); list.add(1); list.add(2); list.add(3); list.clear(); System.out.println(list); } } (6)查找 public class Contains { public static void main(String[] args) { LinkedList<Integer> list = new LinkedList<>(); list.add(1); list.add(2); list.add(3); // 判断元素是否在表中 Boolean x = list.contains(2); System.out.println(x); // 返回第一个元素所在的下标 int a = list.indexOf(2); System.out.println(a); // 返回最后一个元素所在的下标 int b = list.lastIndexOf(2); System.out.println(b); } } (7)截取 public class Sublist { public static void main(String[] args) { LinkedList<Integer> list = new LinkedList<>(); list.add(1); list.add(2); list.add(3); list.add(6); list.add(9); // LinkedList<Integer> list1 = new LinkedList<>(list.subList(1,4));//创建新的对象,进行操作,不会影响原来列表的数据 //subList返回值是List类型 List<Integer> list1 = list.subList(1,4); list1.add(999); list1.add(888); list1.set(0,111); System.out.println(list1); System.out.println(list); } } 4.3.3 LinkedList的遍历 (1)for循环遍历 //1. for (int i = 0; i < list.size(); i++) { System.out.print(list.get(i)+" "); } System.out.println(); (2)for-each遍历 //2 for (int x : list){ System.out.print(x+" "); } (3)使用迭代器正向遍历
//3 Iterator<Integer> iterator = list.listIterator();//传数据 while (iterator.hasNext()){ System.out.print(iterator.next()+" "); } System.out.println();反向遍历
//4 ListIterator<Integer> listIterator = list.listIterator(list.size()); while (listIterator.hasPrevious()){ System.out.print(listIterator.previous()+" "); } System.out.println();注意:
(从前往后)while循环中的判断条件表示:是否存在下一个元素,如果存在,则获取迭代器的下一个元素并输出,因为 next 方法,所以在每次调用后进行移动1位
5. ArrayList和LinkedList的区别差别
ArrayList
LinkedList
底层实现
基于动态数组实现
基于双向链表实现
访问效率
随机访问效率高
随机访问效率低
插入和删除效率
尾部插入和删除效率高
中间或头部插入和删除效率低
任意位置插入和删除效率高
内存占用
内存占用较少,因为只需要存储数据和数组容量。
可能存在内存浪费,因为数组会预留一定的容量
内存占用较多,因为每个节点需要存储数据以及前后指针。
总结:
ArrayList 适合频繁访问元素的场景,例如随机读取数据,适合元素数量相对固定的场景。
LinkedList 适合频繁插入和删除的场景,例如实现栈、队列,适合元素数量动态变化的场景。
【Java---数据结构】链表LinkedList由讯客互联游戏开发栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“【Java---数据结构】链表LinkedList”
上一篇
C++编程:进阶阶段—1内存模型