1.链表的概念
链表(linked list)是一种在物理上非连续、非顺序的数据结构,由若干节点(node)所组成。
链表的第1个节点被称为头节点,最后1个节点被称为尾节点,尾节点的next指针指向空。
注意:单链表只指向一个结点,不能指向多个
两个结点值相同,引用也相同的,不一定是同个结点
2.链表的相关概念
节点
每个点都由值和指向下一个结点的地址组成的独立的单元,称为一个结点,有时也称为节点,含义都在链表中,是一样的
头节点
对于单链表,如果知道了第一个元素,就可以通过遍历访问整个链表,因此第一个结点最重要,,一般称为头结点
虚拟节点
虚拟结点其实就是一个结点dummyNode,其next指针指向head,也就dummyNode.next=head.
因此,如果在算法里使用了虚拟结点,则要注意如果要获得head结点,或者从方法(函数)里返回的时候,则应使用dummyNode.next.
另外,dummyNode的val不会被使用,初始化为0或者-1等都是可以的。既然值不会使用,那虚拟结点有啥用呢?简单来说,就是为了方便处理首部结点,否则需要在代码里单独处理首部结点的问题。在链表反转里,该方式可以大大降低解题难度。
3.链表的创建和结点CRUD
3.1链表的创建
JVM中有栈区和堆区,栈区主要存引用,也就是一个指实际对象的地址,而堆区存的是创建的对象,一个结点存储的就是value和next引用(下一个结点的地址值)
//链表的初始化
private static Node initLinkedList(int[] array) {
Node head = null, cur = null;
for (int i = 0; i < array.length; i++) {
Node newNode = new Node(array[i]);
newNode.next = null;
//头节点
if (i == 0) {
head = newNode;
cur = newNode;
//添加后续节点,让当前节点指向新节点,当前节点再变为新节点
} else {
cur.next = newNode;
cur = newNode;
}
}
return head;
}
//结点
static class Node {
public int val;
public Node next;
Node(int x) {
val = x;
next = null;
}
}
3.2获取链表长度
对于单链表,不管进行什么操作,一定是从头开始逐个向后访问,所以操作之后是否还能找到表头非常重要。一定要注意”狗熊掰棒子"问题,也就是只顾当前位置而将标记表头的指针丢掉了。
/**
* 获取链表长度
*
* @param head 链表头节点
* @return 链表长度
*/
public static int getLength(Node head) {
int length = 0;
Node node = head;
while (node != null) {
length++;
node = node.next;
}
return length;
}
3.3链表插入
链表插入结点分为三种情况:头部插入、中间插入,尾部插入
1)头部插入
1.让新结点指向头结点
2.把新结点变成链表的头结点
2)中间插入
1.先让插入元素指向插入元素后的结点
2.让插入元素前的结点指向插入元素
3)尾部插入,将尾部结点指向新插入结点即可
/**
* 链表插入
*
* @param head 链表头节点
* @param nodeInsert 待插入节点
* @param position 待插入位置,取值从2开始
* @return 插入后得到的链表头节点
*/
public static Node insertNode(Node head, Node nodeInsert, int position) {
//首先,链表如果为空,传进去的就是第一个节点
if (head==null){
return nodeInsert;
}
//已经存放的元素个数,position==length+1时,代表可以在位置为null处添加一个结点
int length = getLength(head);
if (position>length+1||position<1){
System.out.println("位置参数越界");
return head;
}
//1.插入的是头节点情况
if (position==1){
nodeInsert.next=head;
head=nodeInsert;
return head;
}
//2.插入的是中间元素,找出要插入的前一个节点的位置
int count=1;
Node cur=head;
while (position-1>count){
cur=cur.next;
count++;
}
//3.如果是最后一个元素,也是走这个,找到最后一个节点之前的元素,然后让要插入的元素指向这个元素的next,让这个元素再指向它
nodeInsert.next=cur.next;
cur.next=nodeInsert;
return head;
}
3.4链表删除
链表删除结点也分为三种情况:头结点删除、中间结点删除、尾结点删除
1)头结点删除,让链表头节点的next指向原先头节点的next
2)中间结点删除,让删除元素前的结点next指向删除后结点的next
3)尾结点删除,让删除元素前的结点next指向null
/**
* 删除节点
*
* @param head 链表头节点
* @param position 删除节点位置,取值从1开始
* @return 删除后的链表头节点
*/
public static Node deleteNode(Node head, int position) {
if (head==null){
return null;
}
int length = getLength(head);
//todo 这里为什么是position>length,因为大于length的结点为null,不能删
if (position<1||position>length){
System.out.println("位置越界");
return head;
}
//1.删除头节点
if (position==1){
head=head.next;
return head;
}
//2.删除某个位置的节点
Node cur=head;
int count=1;
while (position-1>count){
cur=cur.next;
count++;
}
cur.next=cur.next.next;
return head;
}