一、插入:
1、不带傀儡结点链表的插入
- 创建一个不带傀儡结点的链表
//创建一个链表
public static Node createList(){
//创建四个Node实例,再定义四个引用来分别指向这四个实例
//随着方法的结束,a,b,c,d也随之销毁
Node a=new Node(1);
Node b=new Node(2);
Node c=new Node(3);
Node d=new Node(4);
//给引用进行赋值
//"."操作为解引用操作,根据地址找到对象的本体,对对象本体进行操作
a.next=b;
//next和b类型相同,都是Node类型的引用,引用之间的"="赋值,就是把地址写过去
b.next=c;
c.next=d;
d.next=null;
//将a的地址返回到方法外面去了
return a;
}
1.1 往中间插入(eg:往1,2之间插入)
注意:head是引用
(1)让新结点的next指向2
(2)修改1的指向
// Node head=createList();
//获取到要插入的前一个位置
Node prev=head;
//创建新结点
Node newNode=new Node(10);
//往链表中间插入元素(1.2之间)
//(1)让新结点的next指向2
newNode.next=prev.next;
//(2)修改1的指向
prev.next=newNode; //引用间的相互赋值
注意:(1)、(2)顺序不能颠倒,否则新结点的指向为自身
插在中间任意两个结点间:通过遍历链表找到前一个元素
1.2 头插
(1)让新结点指向1
(2)让head引用指向新结点
//创建新结点
Node newNode=new Node(10);
//往头部插入
//(1)让新结点指向1
newNode.next=head;
//(2)让head引用指向新结点
head=newNode;
1.3 尾插
(1)通过遍历找到链表的最后一个结点
(2)让原链表的最后一个结点的next指向新结点
//通过遍历,找到链表的最后一个结点(即待插入位置的前一个结点)
Node prev=head;
Node newNode=new Node(10);
while(prev!=null&&prev.next!=null) {
prev=prev.next;
}
prev.next=newNode;
for(Node cur=head;cur!=null;cur=cur.next){
System.out.println(cur.val);
}
1.4 空链表的插入
注意:对于不带傀儡结点的链表来说,空链表的插入是一个特殊情况:
空链表中没有任何已有的结点,也就不存在“前一个位置(prev)”,针对空链表插入,就意味着新的结点就是head指向的结点
2、带傀儡结点链表的插入
- 创建一个带傀儡节点的链表
//创建一个带傀儡结点的链表
public static Node createListWithDummy() {
Node dummy = new Node(0);
Node a = new Node(1);
Node b = new Node(2);
Node c = new Node(3);
Node d = new Node(4);
dummy.next=a;
a.next = b;
b.next = c;
c.next = d;
d.next = null;
return dummy;
}
注意:(1)不带傀儡结点的链表头节点为链表的第一个结点,带傀儡结点的链表的头结点就是傀儡结点
(2)傀儡结点不算入正式结点中,所以头插是插在傀儡结点和1之间,head引用指向不变
2.1 往中间插入
2.2 头插
经画图可知,带傀儡结点头插和在中间插入代码是一样的
//获取到前一个元素
Node prev=head;
//让新结点的next指向后一个元素
newNode.next=prev.next;
//再让前一个结点指向新结点
prev.next=newNode; //引用间的相互赋值
2.3尾插
注意:带傀儡结点的尾插和不带傀儡结点的尾插只有在遍历链表找到最后一个结点时的代码有点差异,其余都相同
//通过遍历,找到链表的最后一个结点(即待插入位置的前一个结点)
Node prev=head.next;
Node newNode=new Node(10);
while(prev!=null&&prev.next!=null) {
prev=prev.next;
}
prev.next=newNode;
for(Node cur=head;cur!=null;cur=cur.next){
System.out.println(cur.val);
}
二、删除
1、不带傀儡结点链表的删除
1.1删除一般结点
(1)通过遍历,找到待删除结点的前一个位置的结点
(2)让前一个结点的next指向toDelete的next
(3)只要toDelete这个引用销毁了,随之对应的toDelete也就被销毁(即没有引用指向它了,就会被垃圾回收站回收)
1.1.1 按照值来删除---- O(N)
//此处是按照值来删除
public static void remove(Node head,int value){
//通过遍历,先找到val这个值对应的位置
//同时找到val的前一个位置
Node prev=head;
while(prev!=null&&prev.next!=null&&prev.next.val!=value){
prev=prev.next;
}
//循环结束时,prev引用所指向的就是待删除结点的前一个位置
//即prev.next.val==value
if(prev==null||prev.next==null){
//此时代表未找到值为value的结点
return;
}
//创建一个toDelete引用指向待删除结点
//toDelete里面存放的是待删除结点的地址
Node toDelete=prev.next;
prev.next=toDelete.next;
}
1.1.2 按照位置删除
1.1.2.1 基础删除----- O(N)
//此处是按照位置来删除
public static void remove(Node head,int index){
//通过遍历,先找到对应的位置
//同时找到index的前一个位置
Node prev=head;
for(int i=1;i<index-1;i++){
prev=prev.next;
}
//循环结束时,prev引用所指向的就是待删除结点的前一个位置
//即prev.next.val==value
if(prev==null||prev.next==null){
//此时代表未找到结点
return;
}
//创建一个toDelete引用指向待删除结点
//toDelete里面存放的是待删除结点的地址
Node toDelete=prev.next;
prev.next=toDelete.next;
}
1.1.2.2 高效删除-----O(1)------移形换影法
(1)时间复杂度为O(1)的删除法-----"移形换影法"
(2)相当于让待删除结点的后一个结点覆盖待删除结点
(3)该方法的局限性:无法删除链表的最后一个结点
public static void remove2(Node head,Node toDelete){
//设置一个next引用,里面存放后一个结点的地址
//即next指向后一个结点
Node nextNode=toDelete.next;
//将这个地址覆盖到待删除结点的位置上
toDelete.val=nextNode.val;
toDelete.next=nextNode.next;
}
传参(main方法中):
Node toDelete=head.next;
remove2(head,toDelete);
1.2 删除头结点
**注意:**直接修改此处方法中的head(形参)的指向无法对main方法中的实参造成影响,所以方法应返回新的head
public static Node removehead(Node head){
head=head.next;
return head;
}
main中:
head= removehead(head);
2.带傀儡结点链表的删除
经分析:
(1)带傀儡结点链表的头结点也有prev,所以删除头节点和一般结点的操作是一样的,都是通过遍历链表找到待删除结点的前一个位置,然后让前一个结点的next指向toDelete的next即可
(2)有了傀儡结点之后,就不必考虑head引用的指向发生变化的情况了,即head引用始终指向傀儡结点
//遍历操作同上
//核心操作:
prev.next=toDelete.next;