目录
4.2在链表中间位置插入(最核心在于找到插入位置的前驱节点)
1.线性表的特点
只能从头部车厢开始遍历,依次走到尾部
也就是单向遍历,默认从前向后遍历
2.前言(引入自定义属性--引用)
这是一个自定义的节点类;可以通过它创建节点
3.介绍相关属性具体的含义
车厢类:(具体存储元素的类)
int data;//具体存储数据
Node next;//存储下一节车厢的地址
火车类(由一系列车厢拼起来)
class Node{
int size;//车厢个数
Node head;//第一节车厢的地址
}
4.单链表的详细操作
4.1头插法
//头插法 public void addFirst(int val) { Node node = new Node(val); //创建了一个新的节点 if (head==null) { head = node; } else { node.next = head; //这里的顺序不可以反 head = node; } size++;//添加了一个元素,所以要加1 }
4.2在链表中间位置插入(最核心在于找到插入位置的前驱节点)
/* * 在单链表的任意一个索引位置插入元素value */ public void addIndex(int index,int val) { if (index < 0 || index > size) { System.err.println("不合法"); return; } if(index==0){ addFirst(val); return; } Node node = new Node(val); //需要找到待插入位置的前驱节点 Node prev = head; for (int i = 0; i < index - 1; i++) { prev = prev.next; } //此时prev指向了待插入位置的前驱节点 node.next = prev.next; prev.next = node; size++; }
4.3查询操作
/*根据用户输入的index查找对应值*/ public int get(int index) { if (rangeCheck(index)) { Node node = head; for (int i = 0; i < index; i++) { //遍历节点 node = node.next; } return node.val; } else { System.err.println("index不合法"); return -1; } }
/*查询值为value的元素是否在单链表中存在*/ public boolean contains(int value){ for(Node temp=head;temp!=null;temp=temp.next){ if(temp.val==value){ return true; } } return false; }
4.4修改操作
/*修改index位置的值为newValue,返回修改前的值*/ public int set(int index,int newValue){ if(rangeCheck(index)){ Node node =head; for(int i=0;i<index;i++){ //这里注意不能等于 node=node.next; } //走完则走到了索引位置 int oldValue=node.val; node.val=newValue; return oldValue; }else{ System.err.println("Index不合法"); return -1; } }
4.5删除操作
removeIndex(int index);//这里包括头节点的删除、中间节点的删除、尾节点的删除。
removeValueOnce(int value);//删除单链表中第一个值为val的节点。
removeValueAll(int value);//删除单链表中所有值为val的节点。
按值删除代码基本一样,但是有一个小的细节不一样,写错的话就不会全部删除了
代码如下
public void removeIndex(int index){ if(rangeCheck(index)){ if(index==0){ //删除头节点 Node temp =head; head =head.next; temp.next =null; size--; }else { //index中间 Node prev = head; for (int i = 0; i < index - 1; i++) {//理解不了画个图 prev= prev.next ; } //待删除结点 Node cur =prev.next; prev.next=cur.next; cur.next=null; } }else{ System.out.println("index不合法"); } } //删除头节点 public void removeFirst(){ removeIndex(0); } //删除尾节点 public void removeLast(){ removeIndex(size-1); } /* * 按值删除 * */ public void removeValueOnce(int val){ //遍历链表找到值为val的节点 //找到后删除(正常都要找的前驱,只有头节点没有前驱) if(head!=null&&head.val==val){ //头节点就是但删除节点 removeFirst(); }else { Node prev =head; //此时head一定不是待删除的节点 while(prev.next!=null){ if(prev.next.val==val){ //当前节点是待删除节点 Node cur =prev.next; prev.next=cur.next; cur.next=null; size--; return; } //prev不是待删除结点的前驱,prev向后移动 prev=prev.next; } } } public void removeValueAll(int val) { //判断头节点是否为待删除节点 while (head != null && head.val == val) { head = head.next; size--; } if (head == null) { return ; } else { //此时head不是待删除结点,链表中还有节点 Node prev = head; while (prev.next != null) { if (prev.next.val == val) { //当前节点是待删除节点 Node cur = prev.next; prev.next = cur.next; cur.next = null; size--; } else { //prev不是待删除结点的前驱,prev向后移动 prev = prev.next; } } } }
5.无虚拟头节点单链表总结
在插入时应该注意结点之间代码转换的顺序
头插法时的顺序
尾插法时的顺序
以及除了添加类型之外所有的合法性都不能取到size,因为三个操作的相同点,我们可以直接整合一个方法,在方法内部直接调用即可
测试类:
单链表在插入和删除时,只要是中间节点,都会找前驱节点,只有头节点需要做特殊处理。
6.虚拟头节点(dummyHead)单链表
为了不用单独处理头节点,创建了一个虚拟头节点,不存储元素的值
虚拟头节点在找索引位置的时候,是i<index,而不是index-1,是因为加入了头节点,所以多走一步。
有了头节点所有的插入、删除操作就都当作中间节点来处理。
代码如下:
public class SingleLinkedWithHead { // 带头单链表 // 当前存储的元素个数 private int size; // 虚拟头节点 private Node dummyHead = new Node(-1); // 添加方法 public void addIndex(int index,int val) { // 判断index的合法性 if (index < 0 || index > size) { System.err.println("add index illegal!"); return; } // 插入全都是中间节点 Node node = new Node(val); // 找到待插入位置的前驱 Node prev = dummyHead;//刚开始前驱指向了虚拟头节点 for (int i = 0; i < index; i++) { //i<index是因为多走了一步虚拟头节点,而这个虚拟头节点不计算在index里面,单链表插入排序里面是index-1 prev = prev.next; //不理解画一下图 } // prev指向待插入位置的前驱 //node是插入的节点 node.next = prev.next; //这个和单链表一样,顺序注意 prev.next = node; size ++; //添加一次记得长度要加一 } //头插 public void addFirst(int val) {//插入在头部 addIndex(0,val); } //尾插 public void addLast(int val) {//插入在尾部 addIndex(size,val); } public String toString() { //用于把单链表以字符串的形式输出 String ret = ""; Node node = dummyHead.next;//从头节点之后的第一个节点开始遍历,这里是创建了一个 while (node != null) { ret += node.val; ret += "->"; node = node.next; } ret += "NULL"; return ret; } /* * 删除索引位置的节点 * */ public void removeIndex(int index) {//删除节点,有虚拟头节点所以都相当于中间插入 //判断索引的合法性,也可以合成一个方法 if (index < 0 || index >= size) { System.err.println("remove index illegal!"); return; } // 删除中间节点 Node prev = dummyHead; // 找到待删除位置的前驱 for (int i = 0; i < index; i++) { prev = prev.next; } // prev指向待删除位置的前驱 prev.next = prev.next.next; size --; } }
7.笔试题讲解
7.1力扣82删除链表中的所有重复节点
class Solution { public ListNode deleteDuplicates(ListNode head) { ListNode dummyHead =new ListNode(101); dummyHead.next=head; ListNode prev =dummyHead; ListNode cur =prev.next; while(cur!=null){ ListNode next =cur.next; if(next==null){ return dummyHead.next; }else{ //当前链表至少有俩个节点 if(cur.val!=next.val){ //三个引用同时向后走 prev=prev.next; cur=cur.next; next在循环里会走一步的 }else{ while(next!=null&&cur.val==next.val){ //让naxt一直向后走,直到cur与next不相等,或者next为空 //prev指向第一个重复节点的前驱 next=next.next; } //prev-指向重复节点的前驱 //next-指向重复节点的后继,然后把中间这一块全部删除 prev.next=next; //更新cur指向 cur=next; } } } return dummyHead.next; } }
7.2力扣83 删除排序链表中的重复元素
class Solution { public ListNode deleteDuplicates(ListNode head) { ListNode dummyHead =new ListNode(101); dummyHead.next=head; ListNode prev =dummyHead; ListNode cur=prev.next; while(cur!=null){ if(prev.val==cur.val){ prev.next=cur.next; }else { //prev和cur不是重复元素,都向后移动 prev=prev.next; } cur=prev.next; } return dummyHead.next; } }
7.3力扣203
class Solution { public ListNode reverseList(ListNode head) { //头插法 if(head==null||head.next==null){ return head; } ListNode dummyHead =new ListNode(5001); //遍历原数组,边头插新链表 while(head!=null) { ListNode node =new ListNode(head.val); node.next = dummyHead.next; dummyHead.next =node; head =head.next; } return dummyHead.next; } }
力扣21合并两个有序列表
public ListNode mergeTwoLists(ListNode l1,ListNode l2){ if(l1==null){ return l2; } if(l2==null){ return l1; } //此时l1、l2都不为空 ListNode dummyhead =new ListNode(-1); //尾插 //last是新链表的尾节点 ListNode last=dummyhead; //开始代码拼接的代码 while(l1!=null&&l2!=null){ if(l1.val<= l2.val){ last.next=l1; last=l1; l1=l1.next; }else{ last.next=l2; last=l2; l2=l2.next; } } if(l1==null){ last.next=l2; } if(l2==null){ last.next=l1; } return dummyhead.next; }
在这部分要记得多画图哦!!!
8.双向链表
双向链表中合成删除结点的方法:
引入了分治的思想,先处理左边的情况,处理完之后再处理右边的情况。
private void unlink(Node node){ //分治思想 Node prev =node.prev; Node next =node.next; //先处理前驱节点 if(prev==null){ //没有前驱,此时是个头节点 head=next; }else{ //有前驱 prev.next=next; node.prev=null; } //此时接着处理后半边的情况 if(next==null){ //此时node是个尾节点 tail=prev; }else{ next.prev=prev; node.next=null; } size--; } }
8.1removeVaueAll的方法
public void removeValueAll(int val){ for(Node x=head;x!=null;){ if(x.val==val){ //x是待删除结点 //暂存x(下一个结点的)的地址,方便下一次循环 Node next =x.next; //删除x这个节点 unlink(x); x=next; }else{ //x不是待删除节点,继续向后走 x=x.next; } } }
9.类和对象的作业题
因为hello是静态方法
test.hello()相当于是不规范的空对象调用静态方法
实际上,写为Test.hello();
所以可以正确编译,并且运行
局部变量是全部没有默认值的,所以编译不通过 ,选C
局部遍历必须初始化后使用,所以默认值必须定义在类中。
编译失败,不能在普通方法(必须由对象调用)中定义静态遍历(没对象也可使用),自相矛盾了
B、静态方法访问静态变量,是可以的
静态方法可以访问静态变量,不能调用成员变量(有对象才能用)
成员方法可以调用静态变量,但是不能定义静态变量
不能访问p.name,因为父类的name是私有属性,出了它的括号就不能被使用,会编译出错
最后是102
B,类加载只是把它加载到JVM中进来,并没有产生对象,只有new 的时候才会赋值
A、是反射, java中所有的类都有它的class对象
![]()
可以运行,最后输出88
这个问题比较复杂
变成了一个大的静态代码块,为什么下面的代码块先执行
因为静态代码块优先于主方法,在类加载的时候就执行
答案 5
输出aaabbb
bbb是主方法打印的
选D
n是静态变量,this表示当前对象,是矛盾的