单链表
概念
链表
由多个节点组成的线性结构,每个节点有值和指向后继元素的next指针,最后一个元素next指向null。就像一条链子一样,有头有尾,环环相扣。
注意:一个节点可以有多个前驱,但只能有一个后继。引用一下教程文档的比喻,好比一夫一妻,可以有多个人喜欢你,但你只能有一个妻子,你只能指向她一个。
节点、头结点、虚拟结点
在链表中,每个点都由值和指向下一结点的地址组成,组成的这些独立的单元,成为结点。
第一个结点称为**头结点。**头结点很重要,我们知道第一个元素是谁,就可以通过它访问遍历整个链表。
虚拟结点 dummyNode
我理解就是专门存储头结点地址的,方便我们找到和操作头结点。
即dummyNode.next = head。
不然获取head在很多时候会是一个麻烦事,而头结点又是如此的重要,所以拿一个结点来专门管理它。至于叫虚拟结点,我认为是因为它并不包含在链表中,不属于链表,只是我们为了方便而虚构出来的。
创建
根据链表的概念,我们在Java中创建一个相同结构的类来表示结点。即,两个属性,一个用于存放值,一个用于存放下一个结点。如下:
pubic class ListNode {
private int data;
private ListNode next;
public ListNode(int data) {
this.data = data;
}
// ...getter & setter 方法
}
简便一点的写法,可以把属性的修饰符改为public,这样可以直接操作属性,然后给一个构造器,给value赋值,next就给null(为了规范,不写的话也可以,操作的时候直接设置地址其实是一样的),如下:
pubic class ListNode {
public int val;
public ListNode next;
public ListNode(int x) {
val = x;
next = null;
}
}
增删改查
遍历链表
单链表不管什么操作,都是从头向后逐个访问。所以操作之后还能否找到表头非常重要。
pubilc static int getListLength(Node head) {
int length = 0;
Node node = head;
while(node != null) {
length++;
node = node.next;
}
return length;
}
插入链表
单链表的插入需要考虑三种情况:首部、中部和尾部。
表头插入
操作过程
创建新结点newNode
-> newNode = head
-> head = newNode
只要把结点指向表头即可。注意head要重新指向新的表头。
**打个比方:**老鹰捉小鸡,来了一个新的队首的同学当“头”,那我们就要叫这个新同学老鹰了,之前的老鹰就变成了小鸡。
中间插入
操作过程
遍历找到目标结点的前一个位置(这里叫它前驱结点)
-> newNode.next = 前驱结点.next
-> 前驱结点.next = newNode
**注意连接结点的顺序不能颠倒,因为你前驱结点指向newNode之后,他和原来的后继结点就断开了,就找不到了。
打个比方:可以把链表想象成一群盲人手拉着手,现在有一个新的盲人要加入进来。假设新人叫小新,要加入到小左和小右中间来。这时候如果小左松开手去拉小新,那就再也找不到小右了,只留小新在风中凌乱。那怎么办呢?小左先把小新的手小心翼翼交到小右手里,确保小新已经牢牢拉住小右后,小左就可以放心去拉住小新了。这样小左-小新-小右就安全的连在一起了。
末尾插入
只需要将尾结点指向新结点就可以。
尾结点 = newNode
因为不指定,newNode本来就是指向null的。
代码实现
/**
* 链表插入
*
* @param head 链表头节点
* @param nodeInsert 待插入节点
* @param position 待插入位置,取值从2开始
* @return 插入后得到的链表头节点
*/
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("位置参数越界");
return head;
}
//在链表开头插入
if (position == 1) {
nodeInsert.next = head;
// return nodeInsert;
// 上面return还可以这么写:
head = nodeInsert;
return head;
}
Node pNode = head;
int count = 1;
while (count < position - 1) {
pNode = pNode.next;
count++;
}
nodeInsert.next = pNode.next;
pNode.next = nodeInsert;
return head;
}
删除链表
表头删除
删除表头元素,只要执行 **head = head.next **就可以。原来的结点不可达,会被JVM回收掉。
中间删除
同样使用cur.next来比较,找到位置后,将 cur.next = cur.next.next
末尾删除
还是到尾结点的前一个位置,用cur.next来判断。执行cur.next = null,最后的尾结点不可达,会被JVM回收掉。