针对链表内容初学者而写的博文
一、链表的概念
什么是链表?先以单向链表举例说明,单向链表像一个铁链一样相互连接,包含着多个节点(或结点),一个节点由节点中的值和指向下一个节点名为的next指针(地址)
(注意:不是指向下个节点中的值)。链表中最后一个元素的next指向null。如下图所示(图中的表头我们稍后再说):
深入思考
观察下面两张图,他们是否都满足单链表的要求?给出理由。
第一张图:
第二张图:
解读:
第一张图片满足单链表要求,链表的一个核心是一个节点只能有一个后续节点,但是可以有多个被指向(例如第一张图的c1, c1被a2 和 b3 同时指向)
第二张图片则不满足单链表要求,因为c1有两个后续节点a5 和 b4。
另外在做题时要注意比较的是值还是节点,有时候可能两个节点的值相等,但并不是同一个节点,例如下图,有两个节点的值都是1,但并不是同一个节点。
二、链表的相关概念
节点和头节点
在链表中,每个点都由值和指向下一个节点的地址组成的独立的单元,称为一个节点。
对于单链表,如果知道了第一个元素,就可以通过遍历访问整个链表,因此第一个节点最重要,一般称为头节点。(如果不知道,我们无法从头开始访问,因此注意这里说的是访问整个链表)
虚拟节点()
三、创建链表
接下来是如何构造一个链表
首先需要理解JVM是如何构建出链表的,JVM里有栈区和堆区,栈区主要存引用,也就是一个指向实际对象的地址,而堆区存的才是创建的对象,例如我们定义一个这样的类
public class Course {
Teacher teacher;
Student student;
}
teacher
和student
是指向堆的引用,再定义:
public class Course {
int val; //存放值
Course next; // 存放下一个节点的地址
这里的next指向了下一个同为Course类型的对象,如图:
栈中的引用course(存放val(1)的地址)可以找到节点val(1),然后根据val(1)节点中存放的地址指向了val(2),以此类推直到尾节点val(5),val(5)中存放的地址为null,不再指向任何节点。
四、链表的增删改查(CRUD)
遍历链表
单链表,一定注意访问顺序!!!从头开始逐个向后访问,因此在操作之后是否能找到表头非常重要。
代码:
public static int getListLength(Node head) {
int length = 0;
Node node = head; // 将head节点位置给予node
while (node != 0) {
length++; // 说明这一节点node此时不为空 length增加
node = node.next; // node获得下一个节点的地址
}
return length;
}
表头的插入
表头的插入大致分为三种
1. 在链表表头插入
此时我们有一个新节点new 我们只需要让 new.next = head
即可此时new指向了head节点
易错点:忘记head重新指向表头 习惯于让head来表示头,因此让head = new
即可
2. 在链表中间插入
在中间位置插入,先遍历链表找到插入位置,找目标节点的前一个位置,以图举例,要插入的位置在15和7之间,遍历应该在cur.next = node(7)
时停止,此时cur(val) = 15。然后给newNode前后连接到链表中,注意顺序,先让newNode.next = node(15).next
,然后再让node(15).next = newNode
注意:如果先让node(15).next = newNode
此时没有指向node(7)的指针,因为每个节点只能有一个后续原则。
3. 在单链表的结尾插入节点
表尾插入就比较容易,只需要将尾节点指向新节点即可
插入代码实现:
public static Node insertNode(Node head, Node nodeInsert, int position) {
//查看头节点
if (head == null) {
//这里可以认为待插入的节点就是链表的头节点,也可以抛出不能插入的异常
return nodeInsert;
}
//获取存放的元素个数
//查看是否越界
int size = getLength(head);
if (position > size + 1 || position < 1) {
System.out.println("out range of list");
return head;
}
//表头插入
if (position == 1) {
nodeInsert.next = head;
head = nodeInsert;
return head;
}
Node pNode = head;
int count = 1;
// 这里position已经被上面size限制住,不用考虑pNode == null
while (count < position - 1) {
// 找到插入位置前一个节点的位置
pNode = pNode.next;
count++;
}
nodeInsert.next = pNode.next;
pNode.next = nodeInsert;
return head;
}
链表删除
删除表头节点
执行head = head.next
,JVM会自动回收不可达到的节点
删除尾节点
删除尾节点类似于插入中间节点,都要找到目标节点的前一个节点,如果所示,让cur.next == null
,此时40不再可达,会被JVM自动回收
删除中间节点
删除中间节点时,仍然用插入中间节点的思路,找到目标节点的前一个节点,找到位置后,让cur.next = cur.next.next
即可。如图所示: