一、链表的定义
可以把链表理解为一种加了地址的数组,只是说这个地址并不是连续的。
二、链表的内部结构
1.结构
说到链表也就会想到地址了吧这个地址也就是链表最大的特点,也是链表独有的一方,也就是出题的地方。(只要解决了链表地址问题绝大多数的问题也就解决了)
所以我们先来看看链表的内部结构:
2.代码实现
我们应该怎么用代码定义一个链表呢?
🍎链表里面的每一个节点都是一个对象,所以我们可以用一个内部类去表示一个节点,节点的属性有值(val)和地址(next)。但是next的类型是什么呢,我们刚说节点也是一个引用对象嘛,可以类比于
🌰
person p =new person();
//p是一个引用对象把地址给了person,所以类型是person
所以很容易得出next的类型就是ListNode。
然后再进行初始化赋值,由于地址并不知道下一个是谁就不用初始化地址。
头节点是属于链表里的对象,所以是外部类而不是属于节点的对象。
public class MySingleList {
static class ListNode {
public int val;//节点的值域
public ListNode next;//下一个节点的地址
public ListNode(int val) {
this.val = val;
}
public ListNode head;//表示当前链表的头节点
}
可以看出,链表和顺序表最大的区别就是有了地址。根据这一特点链表的优势就是可以根据地址快速进行增加和删除,而顺序表就比较适合于遍历查找。
三、链表的功能实现
熟悉每个数据结构功能的实现,更有助于我们了解这个数据结构,也可以帮助我们更好的使用。
①遍历
💡链表的遍历和数组的遍历很相似,可以思考数组的遍历:
数组是用一个整形下标i遍历每一个数字,而链表我们也可以用一个节点类的变量来遍历每一个节点。那数组是用i++的方式遍历。
那链表肯定不能用head++的方式来遍历啦(因为节点类型不是整形不能++)。要做到行如i++的遍历方式也可以:那就是让head下标指向下一个节点的位置,就可以移动head下标了。行如:
head=head.next;
//head下标指向head的next区域,也就是下一个节点的地址。
当然这里不能使用头节点 定义一个变量把head赋值给它即可。
🍓tips小技巧:.next在=左边表示要给next属性赋值,当.next位于=右边相当于要使用这个引用变量。
②头插(逆序)
头插也就是找到头的位置,然后把数据插入进去。头是很容易找到,插入的操作却很复杂。
⭐️保存后面链表的信息
⭐️修改指针指向
📸这两者操作顺序不可改变🙅🏻♂️
首先要保存后面链表的信息,所以需要先绑定新节点与后面节点信息(要不然就会丢失),然后再修改指针到头节点。
public void addFirst(int data){
ListNode node = new ListNode(data);
node.next = head;
head = node;
}
③尾插(顺序)
尾插相对于头插入就没那么复杂了,首先遍历找到链表尾巴,然后插入修改指针指向就可以了。
public void addLast(int data){
ListNode node = new ListNode(data);
ListNode cur = head;
if(cur == null) {
head = node;
return;
}
//找到链表的尾巴 注意是cur.next 不是cur
while (cur.next != null) {
cur = cur.next;
}
cur.next = node;
}
④插入(任意位置)⭐️⭐️⭐️
🍉结合前两个插入操作,这个操作已经不是很难了。我们需要知道目标节点的前一个和后一个节点,才能对目标节点进行操作。
Q:那我们怎么知道目标节点的前一个和后一个节点呢?
💡当链表进行增加操作时,我们需要知道增加节点的前一个节点,和下一个节点才能进行增加。如果遍历两边链表 ,这样效果太慢了。我们这时可以由链表的内部结构可知,我们可以找到链表的前驱节点往前走一步就是目标节点的下一个节点。
🍓tips小技巧:在链表任意地增加操作之前都要先保留后面链表的信息,再进行节点的增加。
⭐️找到前驱节点
⭐️保存后面链表信息(目标节点连接后面节点)
⭐️目标节点连接前驱节点
//任意位置插入,第一个数据节点为0号下标
public void addIndex(int index,int data){
if(index < 0 || index > size()) {
System.out.println("index 不合法");
return;
}
if(index == 0) {
addFirst(data);
return;
}
if(index == size()) {
addLast(data);
return;
}
//核心代码
ListNode cur = findIndexSubOne(index);
ListNode node = new ListNode(data);
node.next = cur.next;
cur.next = node;
}
private ListNode findIndexSubOne(int index) {
ListNode cur = head;
while (index-1 != 0) {
cur = cur.next;
index--;
}
return cur;
}
⑤删除(值)⭐️⭐️⭐️
在上面的增加操作完成之后,这个删除操作当然很简单了。也要知道目标节点的前一个节点,和下一个节点才能进行删除。区别就是:不用去保留链表后面的信息,直接改变引用的指向即可删除。
⭐️找到前驱节点
⭐️找到你要删除的节点
⭐️改变引用指向
public void remove(int key){
if(head == null) {
return;
}
//单独删除头节点
if(head.val == key) {
head = head.next;
return;
}
ListNode cur = searchPrev(key);
if(cur == null) {
System.out.println("没有你要删除的数字");
return;
}
//核心代码
ListNode del = cur.next;
cur.next = del.next;
}
private ListNode searchPrev(int key) {
ListNode cur = head;
while (cur.next != null) {
if(cur.next.val == key) {
return cur;
}
cur = cur.next;
}
return null;
}