链表基础
1、什么是链表
1.1 基本定义
链表是一种基本的数据结构,对于普通的单链表而言,每一个节点内部由两部分组成,一个是当前节点的value,另一个则是指向下一个节点的next元素。但在具体的实际应用中,普通的单链表使用较少,使用较多的是带头节点的单链表和双向循环链表,甚至是多个链表的组合以此实现更丰富的功能。
1.2 单链表的特点
1、单链表不要求逻辑上相邻的两个元素在物理位置上也相邻,因此不需要连续的存储空间。
2、单链表是非随机的存储结构,不能直接找到表中某个特定的节点。当需要查找某个特定的节点时,需要从表头开始遍历,依次查找。
1.3 相关术语
- 节(结)点:在链表中,每个点都由值和指向下一个节点的地址组成的独立的单元被称为节点。
- 头节点:链表的第一个节点。
- 虚拟节点:本质上也是一个节点dummyNode,只是有些特殊,由dummyNode.next = head可知,它是虚拟在头结点之前的节点。虚拟节点的value值不会被使用,初始化为0或-1都是可以的。它的作用是方便我们处理首部节点。
2、单链表的构建(Java)
2.1 构建步骤
-
需要定义一个结点类,用来存放每一个结点。
-
链表的初始化,也就是结点的构建
2.2 代码实现
/**
* @author danhen
* @version 1.0
* @description 构建单链表
* @date 2023/8/2 15:53
*/
public class Create_linkList {
//1、节点定义
static class Node<T> {
//节点值
public T value;
//指向下一个节点的元素
public Node<T> next;
Node(T value) {
this.value = value;
next = null;
}
}
//2.链表初始化
private static <T> Node<T> initLinkList(T[] array) {
//head:初始化的头结点,cur:当前节点
Node<T> head = null, cur = null;
for (int i = 0; i < array.length; i++) {
Node<T> newNode = new Node<>(array[i]);
if (i == 0) {
head = newNode;
cur = head;
} else {
//当前节点的next指向下一个节点
cur.next = newNode;
//修改当前节点为新的newNode
cur = newNode;
}
}
return head;
}
public static void main(String[] args) {
Cat[] cats = new Cat[5];
cats[0] = new Cat("七七","白色");
cats[1] = new Cat("小包","黑色");
cats[2] = new Cat("皮皮","蓝色");
cats[3] = new Cat("小雷","紫色");
cats[4] = new Cat("花苞","粉色");
Node<Cat> headCat = initLinkList(cats);
System.out.println(headCat.value.getName());
}
}
2.3 运行结果
3、链表的crud
3.1 遍历(查)
基本思想:头结点不断向后偏移,直到下一个节点为null
private static void getList(Node<Cat> headCat){
while (headCat != null) {
System.out.println(headCat.value.getName());
headCat = headCat.next;
}
}
3.2 插入
3.2.1 表头插入
这里的坑在于:容易把新增节点next指向原来表头就完事,而忽略了头结点的重指向
Node<Cat> catNode = new Node<>(new Cat("小黄", "黄色"));
//新节点的next指向原先的表头
catNode.next = headCat;
//我们习惯于用head表示表头,所以把新增接点赋值给原先头结点,过程如下图
headCat = catNode;
System.out.println(headCat.value.getName());//七七
3.2.2 中间插入
注意点:新增过程的指向步骤一定要小心,先让新增节点指向原先的下一个节点,再让原先节点指向新增节点。步骤走反就会导致原先的下一个节点数据丢失!
指导思想:
代码实现:
Node<Cat> newCatNode = new Node<>(new Cat("玲花", "黑白相间"));
Node<Cat> traversalCat = headCat;
while(traversalCat!=null){
//在七七之后添加数据
if("七七".equals(traversalCat.value.getName())){
//1、新增节点的next指向原先traversalCat的next节点
newCatNode.next = traversalCat.next;
//2、traversalCat的下一个节点指向新增节点
//1,2步骤能反吗?不能,反过来原先的下一个节点数据就关联不上了,所以中间
// 新增节点一定要先让新增节点先指向原先的下一个节点数据
traversalCat.next = newCatNode;
break;
}
traversalCat = traversalCat.next;
}
getList(headCat);
数据测试输出:
3.2.3 表尾插入
Node<Cat> newLastCatNode = new Node<>(new Cat("老白", "白色"));
Node<Cat> traversalLatCat = headCat;
//首先让对象的引用指向最后一个数据
while (traversalLatCat.next != null) {
traversalLatCat = traversalLatCat.next;
}
//此时headCat指向了最后一个数据,新增那就把next指向新增数据
traversalLatCat.next = newLastCatNode;
//别忘了新的尾结点next指向null
newLastCatNode.next = null;
//遍历查看结果
getList(headCat);
3.3 节点删除
思想实现与节点添加类似,也可以分为删除头部元素,删除中间元素和删除尾部元素
//3.1 删除头部节点(name="小黄")
headCat = headCat.next;
//3.2 删除中间节点
Node<Cat> traversalDelMidCat = headCat;
while(traversalDelMidCat!=null){
//以删除名称为"小雷"的这只猫为例,那你怎么知道“小雷”上一个节点是哪个呢?
//这应该就是单链表的一个局限性了,对于删除中间节点,我们必须要指明需要被删除节点的上个节点是哪个。如果这里封装成方法,就需要传入name="皮皮"这一字段或者传其在链表中的位置
if("皮皮".equals(traversalDelMidCat.value.getName())){
traversalDelMidCat.next = traversalDelMidCat.next.next;
break;
}
traversalDelMidCat = traversalDelMidCat.next;
}
//3.3 删除尾部节点(name=老白)
//遍历,找到尾部节点的上一个节点
Node<Cat> traversalDelLastCat = headCat;
//获取链表的个数
int size = getLength(headCat);
//目标位置
int last = size;
//目前位置
int cur = 1;
//last-2:找到链表尾结点的上一个数
while(cur < last-2){
traversalDelLastCat = traversalDelLastCat.next;
cur++;
}
traversalDelLastCat.next = null;
getList(headCat);
运行结果:
3.4 节点修改
遍历找到该节点,修改数据即可
Node<Cat> traversalUpCat = headCat;
while (traversalUpCat!=null){
if("皮皮".equals(traversalUpCat.value.getName())){
traversalUpCat.value.setName("花皮");
System.out.println(traversalUpCat.value.getName());
break;
}
traversalUpCat =traversalUpCat.next;
}
4、总结
正像书本上所说的那样,每一种数据结构都离不开创建+增删查改。通过这5部分,我们能够很好的掌握这种结构的基础,之后的这方面的算法题在这个基础上延伸拓展,我们才能逐步提升。今天的链表基础梳理就到此为止,接下来就要处理链表相关的算法题!