🔒文章目录:
1.❤️❤️前言~🥳🎉🎉🎉
2.ArrayList的缺陷
3.链表的概念及结构
4.无头单向非循环链表的实现
4.1成员属性
4.2成员方法
createList
display——打印链表
addFirst——头插
addLast——尾插
size——获取单链表长度
addIndex——在任意位置插入
contains——判定是否包含某个元素
remove——删除第一次出现关键字为key的结点
removeAll——删除所有值为key的结点
clear——清空单链表
4.3完整代码及使用
完整代码
完整代码的使用
5.总结
1.❤️❤️前言~🥳🎉🎉🎉
Hello, Hello~ 亲爱的朋友们👋👋,这里是E绵绵呀✍️✍️。
如果你喜欢这篇文章,请别吝啬你的点赞❤️❤️和收藏📖📖。如果你对我的内容感兴趣,记得关注我👀👀以便不错过每一篇精彩。
当然,如果在阅读中发现任何问题或疑问,我非常欢迎你在评论区留言指正🗨️🗨️。让我们共同努力,一起进步!
加油,一起CHIN UP!💪💪
🔗个人主页:E绵绵的博客
📚所属专栏:1. JAVA知识点专栏
深入探索JAVA的核心概念与技术细节
2.JAVA题目练习
实战演练,巩固JAVA编程技能
3.c语言知识点专栏
揭示c语言的底层逻辑与高级特性
4.c语言题目练习
挑战自我,提升c语言编程能力
📘 持续更新中,敬请期待❤️❤️
借鉴文章:Java【链表】详细图解/ 模拟实现+【LinkedList】常用方法介绍_java linkedlist方法-CSDN博客
2.ArrayList的缺陷
上篇文章已经熟悉了ArrayList的使用,并且进行了简单模拟实现。通过源码知道,ArrayList底层使用数组来存储元素
但由于其底层是一段连续空间,当在ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后 搬移,时间复杂度为O(n),效率比较低,因此ArrayList不适合做任意位置插入和删除比较多的场景。
所以:java 集合中又引入了LinkedList,即链表结构。
3.链表的概念及结构
链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。
注意:
1.从上图可看出,链式结构在逻辑上是连续的,但是在物理上不一定连续
2.其结点一般都是从堆上申请出来的
3.从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续
🎯🎯实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
1. 单向或者双向
2.带头或者不带头
3. 循环或者非循环
虽然有这么多的链表的结构,但是我们重点掌握两种:
1.无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如 哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2.无头双向非循环链表:在Java的集合框架库中LinkedList底层实现就是无头双向非循环链表。
4.无头单向非循环链表的实现
4.1成员属性
要模拟实现链表,也得自己实现一个类,首先要考虑这个类中的成员属性
已经说过,链表是由多个结点 “链接” 而成的,那么需要定义一个 静态内部类 Node:其中有两个域:
📌值域 value :来记录这个结点的值
📌指针域 next:来记录下一个结点的地址📌并且它还需要一个构造方法:在创建ListNode内部类的同时分配好value的值。
📌模拟的链表中还需要一个 成员变量:head 来记录当前链表的头结点。
public class SingleLinkedList {
static class ListNode{
public int value;
public ListNode next;
public ListNode(int value) {
this.value = value;
}
}
public ListNode head;
4.2成员方法
createList
该方法是我们自己为了方便独创的,实际链表方法中并不存在该方法。
public void createList(){ ListNode listNode1 = new ListNode(45); ListNode listNode2 = new ListNode(46); ListNode listNode3 = new ListNode(50); ListNode listNode4 = new ListNode(56); ListNode listNode5 = new ListNode(67); listNode1.next=listNode2; listNode2.next=listNode3; listNode3.next=listNode4; listNode4.next=listNode5; head=listNode1; }
该方法创建了一个内部节点为5个的无头单向非循环链表。(注意结尾head要指向ListNode1,否则该链表之后会自动被释放掉)
display——打印链表
注意:LinkedList 中不存在该方法,为了方便看测试结果
public void display(){ ListNode cur=head; while(cur!=null){ System.out.print(cur.value+" "); cur=cur.next; } System.out.println(); }
addFirst——头插
❗️❗️和顺序表不同,链表是由一个个结点组成的,而顺序表可以理解为一个数组
顺序表插入之前必须考虑数组是否以及满了,而链表只需要关心各个结点的next即可。因为我们还有一个成员属性:head,是用来记录头结点的
所以链表的头插操作就是:
1️⃣new 一个结点 node,类型是 Node
2️⃣链接:把头结点的地址( head 的值)赋给 node 的指针域 next
3️⃣head 记录新的头结点public void addFirst(int a){ ListNode listNode = new ListNode(a); listNode.next=head; head=listNode; }
addLast——尾插
尾插步骤:
1️⃣new 一个 node,类型是 Node
2️⃣找到尾结点
3️⃣链接:把 node 的值(也就是地址)赋给尾结点的指针域 next🚗🚗🚗
如何找到尾结点呢❓
需要 从头结点开始,遍历链表,找到一个结点的指针域 next 域为 null,它就是尾结点✅head 是用来标记头结点的,所以 head 不能随意更改
我们需要再定义一个 Node 类型的 cur,让 cur 遍历链表当 cur 找到尾结点后,需要让此时的尾结点和新结点 node 连接上
即如下图:并且我们还需要注意一个特殊情况:
cur 这个变量中存放的值 要更改为下一个结点的地址:cur = cur.next;但当cur == null 时,这里就会发生空指针异常❌,那么在实例中是否会出现这种情况?
如果 head 一开始就是 null ,也就是链表为空时,cur 就会被 head 赋值成 null,就会发生空指针异常。所以当链表为空时,就不需要遍历链表找尾结点,直接把 node 的值赋给 head 即可。
public void addLast(int a){ ListNode listNode = new ListNode(a); ListNode cur=head; if(head==null){ head=listNode; return; } while(cur.next!=null){ cur=cur.next; } cur.next=listNode; }
size——获取单链表长度
直接遍历链表即可
public int size(){ int count=0; ListNode cur=head; while(cur!=null){ cur=cur.next; count++; } return count; }
addIndex——在任意位置插入
官方规定第一个数据的位置是0,和数组的位置(下标)规则一致
所以我们首先要判断 index 的合法性:index<0 || index >链表长度是不合法❌的
index 合法的情况下,如何在index位置插入删除呢❓
📌index == 0就是头插
📌index = 链表长度就是尾插
❗️主要是链表中间位置的插入和删除要想在两个结点中间插入新结点,首先要找到这两个结点的地址
找到 index -1 结点的位置也就相当于找到了 index 结点的位置找到其位置很简单,cur 遍历链表 index-1 次即可
具体插入步骤:
1️⃣node.next = prevIndex.next;
2️⃣prevIndex.next = node;这两行不能交换位置❗️❗️❗️
如果先让 prevIndex.next = node;那么就会丢失 index 位置的那个结点↪️,此时 node.next = prevIndex.next 就相当于 node.next = node;代码会发生错误❌
注意还有一个特殊点当链表中无任何节点,为null时,无论index为何值,都会直接添加一个节点。
public void addIndex(int index,int a){ if(head==null){ ListNode listNode = new ListNode(a); head=listNode; return; } if(index<0||index>size()){ System.out.println("位置不合法"); //这里就不搞抛出异常了,我们简单点 return; } if(index==0){ addFirst(a); return; } if(index==size()){ addLast(a); return; } ListNode listNode = new ListNode(a); ListNode cur=head; for (int i = 0; i <index-1 ; i++) { cur=cur.next; } listNode.next=cur.next; cur.next=listNode; }
contains——判定是否包含某个元素
比较简单,遍历这个数组即可
public void contain(int key){ ListNode cur=head; while(cur!=null){ if(cur.value==key){ System.out.println(true); return; } cur=cur.next; } System.out.println(false); }
因为这里我们存放的是 int 类型的变量,但 LinkedList 当中是存放引用数据类型的
⚠️⚠️⚠️当表中是引用类型时,就不可以用“等号”比较,应该用 equals 方法
remove——删除第一次出现关键字为key的结点
1️⃣如果链表为空就不能再删了
2️⃣如果头结点就是要删除的 key 结点,直接 head 存放下一个结点的地址
3️⃣如果链表其他结点是要删除的 key 结点,要先找到 key 结点的前一个结点当 key 结点的前一个结点的 next 不再存放 key 结点地址时,key 结点此后不会再被使用,会被系统自动回收🗑️
所以完整代码如下:
public void remove(int key){ ListNode cur=head; if(head==null) { System.out.println("为空链表,不能进行删除操作"); return; } if(cur.value==key) { head=head.next; return; } while(cur.next!=null){ if(cur.next.value==key){ cur.next=cur.next.next; return; } cur=cur.next; } System.out.println("不存在该数"); }
removeAll——删除所有值为key的结点
这里我们将cur从head处开始检验:
当cur的下一个节点中的值等于key时: cur.next=cur.next.next,否则cur=cur.next。
最后检测完毕后还要看一下head处的值是否等于key,如果等于则将head=head.next。
完整代码如下:
public void removeAll(int key){ if(this.head == null) { System.out.println("为空链表,不能进行删除操作"); return; } ListNode cur = head; while(cur.next != null){ if(cur.next.value == key){ cur.next=cur.next.next; } else { cur = cur.next; }} if(head.value==key){ head = head.next; } }
clear——清空单链表
head 这个变量一直存放着链表的头结点位置,把head置空,就找不到此链表,那么链表中的所有结点都会被系统自动回收🗑️
public void clear() { head = null; }
4.3完整代码及使用
完整代码
public class SingleLinkedList {
static class ListNode{
public int value;
public ListNode next;
public ListNode(int value) {
this.value = value;
}
}
public ListNode head;
public void createList(){
ListNode listNode1 = new ListNode(45);
ListNode listNode2 = new ListNode(46);
ListNode listNode3 = new ListNode(50);
ListNode listNode4 = new ListNode(56);
ListNode listNode5 = new ListNode(67);
listNode1.next=listNode2;
listNode2.next=listNode3;
listNode3.next=listNode4;
listNode4.next=listNode5;
head=listNode1;
}
public void display(){
ListNode cur=head;
while(cur!=null){
System.out.print(cur.value+" ");
cur=cur.next;
}
System.out.println();
}
public int size(){
int count=0;
ListNode cur=head;
while(cur!=null){
cur=cur.next;
count++;
}
return count;
}
public void contain(int key){
ListNode cur=head;
while(cur!=null){
if(cur.value==key){
System.out.println(true);
return;
}
cur=cur.next;
}
System.out.println(false);
}
public void addFirst(int a){
ListNode listNode = new ListNode(a);
listNode.next=head;
head=listNode;
}
public void addLast(int a){
ListNode listNode = new ListNode(a);
ListNode cur=head;
if(head==null){
head=listNode;
return;
}
while(cur.next!=null){
cur=cur.next;
}
cur.next=listNode;
}
public void addIndex(int index,int a){
if(head==null){
ListNode listNode = new ListNode(a);
head=listNode;
return;
}
if(index<0||index>size()){
System.out.println("位置不合法");
//这里就不搞抛出异常了,我们简单点
return;
}
if(index==0){
addFirst(a);
return;
}
if(index==size()){
addLast(a);
return;
}
ListNode listNode = new ListNode(a);
ListNode cur=head;
for (int i = 0; i <index-1 ; i++) {
cur=cur.next;
}
listNode.next=cur.next;
cur.next=listNode;
}
public void remove(int key){
ListNode cur=head;
if(head==null) {
System.out.println("为空链表,不能进行删除操作");
return;
}
if(cur.value==key) {
head=head.next;
return;
}
while(cur.next!=null){
if(cur.next.value==key){
cur.next=cur.next.next;
return;
}
cur=cur.next;
}
System.out.println("不存在该数");
}
public void removeAll(int key){
if(this.head == null) {
System.out.println("为空链表,不能进行删除操作");
return;
}
ListNode cur = head;
while(cur.next != null){
if(cur.next.value == key){
cur.next=cur.next.next;
}
else {
cur = cur.next;
}}
if(head.value==key){
head = head.next;
}
}
public void clear(){
head=null;
}
}
完整代码的使用
public class Test {
public static void main(String[] args) {
SingleLinkedList singleLinkedList = new SingleLinkedList();
singleLinkedList.createList();
singleLinkedList.display();
System.out.println(singleLinkedList.size());
singleLinkedList.contain(45);
singleLinkedList.addFirst(12);
singleLinkedList.addLast(67);
singleLinkedList.addIndex(3, 22);
singleLinkedList.display();
singleLinkedList.remove(22);
singleLinkedList.remove(12);
singleLinkedList.display();
singleLinkedList.removeAll(67);
singleLinkedList.display();
System.out.println("=======================");
singleLinkedList.clear();//清空该链表
singleLinkedList.display();
System.out.println("已清空该链表");
System.out.println("=======================");
}}
5.总结
所以,我们在本文中详细讲述了链表的概念和结构,并成功模拟了无头非循环单链表的实现。在下篇文章中,我们将带来一些关于单链表的面试题。在此,我们诚挚地邀请各位大佬们为我们点赞、关注,并在评论区留下您宝贵的意见与建议。让我们共同学习,共同进步,为知识的海洋增添更多宝贵的财富!🎉🎉🎉❤️❤️💕💕🥳👏👏👏