链表(LinkedList)
链表的基本概念
头结点
对于单链表,如果知道了第一个元素,就可以通过遍历访问整个链表,因此第一个结点最重要,一般称为头结点。
虚拟节点
创建链表
● JVM里有栈区和堆区,栈区主要存引用,也就是一个指向实际对象的地址,而堆区存的才是创建的对象。
● 结点
public class Node{
int val;
course next;
}
● 内存
这里通过对地址的引用,就可以找到val(1)
,然后val(1)
结点存了指向val(2)
的地址,而val(3)
又指向val(4)
的地址,构造出了一个链条访问结构。
这是一个简单的线性访问,链表是从head开始,逐个开始向后访问,而每次所访问的对象类型都是一样的。
● 在Java里规范的链表应该这么定义:
public class ListNode{
// 定义一个结点的数据域
private int data;
// 定义了一个Listnode对象,next
private ListNode next;
//构造初始化数据
public ListNode(int data){
this.data = data;
}
public int getData(){
return data;
}
public void setData(int data){
return next;
}
public void setNext(ListNode next){
this.next = next;
}
}
链表的增删改查
遍历链表
单链表不管进行什么操作,一定是从头开始逐个访问,所以操作之后是否还能找到表头非常重要。一定要注意,不能只顾当前位置而将标记表头的指针丢掉了。
● 遍历链表,求链表长度
public static int getListLength(Node head){
int length = 0;
Node node = head;
while(node != null){
length++;
node = node.next;
}
return length;
}
链表插入
单链表的插入需要考虑三种情况:首部、中部和尾部。
在链表的头部插入
链表表头容易出错的是经常会忘了head需要重新指向表头。我们创建一个新结点newNode
,怎么连接到原来的链表上?执行newNode.next = head
即可。之后遍历新链表需要从newNode
开始直至结束。我们习惯的让head = newNode
,来使用head来表示。
在链表中间插入
在中间位置插入,必须先遍历找到要插入的位置,然后将当前位置接入到前去结点和后继结点之间。
应当在目标结点的前一个位置停下来,应当使用cur.next
的值进行判断而不是使用cur来判断,这是链表最常用的策略。
示例:如果要在7前面插入,当cur.next = node(new)
了就应该停下来,此时cur.val = 4.
然后需要给newNode前后接两根线,此时就只能先让new.next = node(4).next
(这句是图中 (1) ),然后node(4).next = new
(这句是图中 (2) ),而且顺序不能颠倒。
如果颠倒了顺序,让node(4).next = new,就找不到node(6)的位置了,插入之前的node(4).next = node(6)就会丢失掉。
在单链表的结尾插入节点
表尾插入比较容易了,我们只将尾结点指向新结点。
链表删除
删除分为:删除头部元素,删除中间元素,删除尾部元素。
删除头结点
删除头结点:一般只要执行 head = head.next
就可以了。将head移动之后,原来的结点不可达,就会被JVM回收掉。
删除中间结点
删除中间结点时,同上也会用cur.next
来进行判断,找到位置后,执行cur.next = cur.next.next
;则会将cur.next
这个节点变得不可达。
删除最后一个结点
与插入相同,找到要删除结点的前驱结点,提前一个位置进行判断 cur.next = 要删除的结点值
,判断后执行cur.next = null
即可使得尾结点不可达,最终被JVM回收
注意:不管是插入还是删除,在判断中间结点是都要使用
cur.next
判断,而不是cur = val
否则将会丢失前驱,没有当前结点前驱又如何做修改呢?