1.顺序插入的单链表
代码组成:
HeroNode.java
SingleLinkedList.java
Main1.java
1.1 HeroNode.java(单链表中的节点类)
public class HeroNode {
public int no;
public String name;
public String nickname;
public HeroNode next;
public HeroNode(int no,String name,String nickname) {
this.no=no;
this.name=name;
this.nickname=nickname;
}
@Override
public String toString() {
return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
}
}
1.2 SingleLinkedList.java(单链表类,每个对象对应一个单链表)
package com.zhanglei.linkedlist;
public class SingleLinkedList{
private HeroNode head;
public SingleLinkedList() {
//根节点作为链表的起始位置,设定好了就不能动了,其具体内容无需设置
this.head=new HeroNode(0," "," ");
}
public void addLinkedList(HeroNode heronode) {
HeroNode temp1=head;
while(true) {//写死循环首先要考虑结束条件
if(temp1.next==null) {
temp1.next=heronode;
break;
}else if(temp1.next.no==heronode.no) {
System.out.println("编码"+heronode.no+"已存在,无法重复插入!");
break;
}else if(temp1.next.no>heronode.no) {
HeroNode temp2=temp1.next;
temp1.next=heronode;
heronode.next=temp2;
break;
}
temp1=temp1.next;
}
}
public void list() {
HeroNode temp1=head;
while(temp1.next!=null) {
System.out.println(temp1.next);
temp1=temp1.next;//不可忽略,否则无限打印
}
}
}
1.2.1 代码分析
一开始的addLinkedList()的写法:
public void addLinkedList(HeroNode heronode) {
HeroNode temp1=head;
while(true) {//写死循环首先要考虑结束条件
if(temp1.next==null) {
temp1.next=heronode;
break;
}else if(temp1.next.no==heronode.no) {
System.out.println("编码"+heronode.no+"已存在,无法重复插入!");
break;
}else if(temp1.next.no>heronode.no) {
HeroNode temp2=temp1.next;
temp1.next=heronode;
heronode.next=temp2;
break;
}else if(temp1.next.no<heronode.no){
//发现这里的逻辑好复杂
break;
}
temp1=temp1.next;
}
}
- 仔细看上面的while循环,发现if条件将4种情况都考虑了,
并且都各自有break,那如此temp1=temp1.next()永远都无法执行;
如果把复杂逻辑的代码去掉,这样temp1=temp1.next()就能替代复杂逻辑的情况
这样只用前三种情况就可以解决单链表的顺序插入问题(这个确实很妙!) - 在第三种情况下我一开始的代码是:
temp1.next=heronode;
heronode.next=temp1.next.next;
这里有两个问题:
1.根本用不到next.next
2.需要用额外的遍历来存储temp1.next,否则在temp1.next重新赋值后原来的指向关系就消失了
1.3 Main1.java(测试类)
package com.zhanglei.linkedlist;
public class Main1 {
public static void main(String[] args) {
// TODO Auto-generated method stub
SingleLinkedList st=new SingleLinkedList();
HeroNode h1=new HeroNode(1,"林冲","豹子头");
HeroNode h2=new HeroNode(7,"宋江","及时雨");
HeroNode h3=new HeroNode(2,"吴用","智多星");
HeroNode h4=new HeroNode(5,"鲁智深","花和尚");
HeroNode h5=new HeroNode(4,"卢俊义","玉麒麟");
HeroNode h6=new HeroNode(3,"柴进","小旋风");
HeroNode h7=new HeroNode(6,"武松","行者");
HeroNode h8=new HeroNode(8,"李逵","黑旋风");
HeroNode h9=new HeroNode(3,"关胜","大刀");
HeroNode h10=new HeroNode(2,"杨志","青面兽");
st.addLinkedList(h1);
st.addLinkedList(h2);
st.addLinkedList(h3);
st.addLinkedList(h4);
st.addLinkedList(h5);
st.addLinkedList(h6);
st.addLinkedList(h7);
st.addLinkedList(h8);
st.addLinkedList(h9);
st.addLinkedList(h10);
st.list();
}
}
1.3.1 结果分析
编码3已存在,无法重复插入!
编码2已存在,无法重复插入!
HeroNode [no=1, name=林冲, nickname=豹子头]
HeroNode [no=2, name=吴用, nickname=智多星]
HeroNode [no=3, name=柴进, nickname=小旋风]
HeroNode [no=4, name=卢俊义, nickname=玉麒麟]
HeroNode [no=5, name=鲁智深, nickname=花和尚]
HeroNode [no=6, name=武松, nickname=行者]
HeroNode [no=7, name=宋江, nickname=及时雨]
HeroNode [no=8, name=李逵, nickname=黑旋风]
- 一个SingleLinkedList对象就是一个单链表
- 单链表的组成是节点,故可以将节点单独作为一个节点类
- 启发就是碰到问题先看看这个问题由什么组成,将大问题转换成多个小问题来解决
- 当乱序插入链表时,打印链表得到的结果是有序的
1.3.2 原始代码(对象不可以作为左值,引用可以)
public class SingleLinkedList {
private int no;
private String name;
private String nickname;
private SingleLinkedList head;
public SingleLinkedList next() {
return new SingleLinkedList();
}
public void addLinkedList(SingleLinkedList st) {
SingleLinkedList temp1=head;
if(temp1.next()==null) {
//下句报错:the left-hand side of an assignment must be an variable
//原因:可以把对象赋值给引用,但是不能把引用或者对象赋值给对象
temp1.next()=st;
}
}
}
- 一开始自己写就写出来上面的代码,这里把单链表类当成节点类来写了
- 这里用next()方法来返回一个对象,此时却不能用于赋值语句的左值
- 正确的代码是将next设置为属性,而不是方法(好像现在我更倾向于使用方法而非属性)
2.添加额外功能(学会1,则2就水到渠成)
2.1 修改指定no的节点和删除指定no的节点
在SingleLinkedList.java中添加如下两个方法:
/*
* 修改链表中指定no的节点
*/
public void rectifyLinkedList(HeroNode heronode) {
HeroNode temp1=head;
while(true) {
if(temp1.next==null) {
System.out.println("修改失败!");
break;
}
if(temp1.next.no==heronode.no) {
temp1.next.name=heronode.name;
temp1.next.nickname=heronode.nickname;
break;
}
temp1=temp1.next;
}
}
/*
* 删除链表中指定no的节点
*/
public void deleteLinkedList(HeroNode heronode) {
HeroNode temp1=head;
while(true) {
if(temp1.next==null) {
System.out.println("删除失败!");
break;
}
if(temp1.next.no==heronode.no) {
temp1.next=temp1.next.next;
break;
}
temp1=temp1.next;
}
}
2.2 此时的Main1.java
package com.zhanglei.linkedlist;
public class Main1 {
public static void main(String[] args) {
// TODO Auto-generated method stub
SingleLinkedList st=new SingleLinkedList();
HeroNode h1=new HeroNode(1,"林冲","豹子头");
HeroNode h2=new HeroNode(7,"宋江","及时雨");
HeroNode h3=new HeroNode(2,"吴用","智多星");
HeroNode h4=new HeroNode(5,"鲁智深","花和尚");
HeroNode h5=new HeroNode(4,"卢俊义","玉麒麟");
HeroNode h6=new HeroNode(3,"柴进","小旋风");
HeroNode h7=new HeroNode(6,"武松","行者");
HeroNode h8=new HeroNode(8,"李逵","黑旋风");
HeroNode h9=new HeroNode(3,"关胜","大刀");
HeroNode h10=new HeroNode(2,"杨志","青面兽");
st.addLinkedList(h1);
st.addLinkedList(h2);
st.addLinkedList(h3);
st.addLinkedList(h4);
st.addLinkedList(h5);
st.addLinkedList(h6);
st.addLinkedList(h7);
st.addLinkedList(h8);
st.addLinkedList(h9);
st.addLinkedList(h10);
st.list();
System.out.println("修改指定元素后:");
HeroNode h11=new HeroNode(3,"小关胜","小大刀");
st.rectifyLinkedList(h11);
st.list();
System.out.println("删除指定元素后:");
HeroNode h12=new HeroNode(3,"小关胜","小大刀");
st.deleteLinkedList(h12);
st.list();
}
}
2.3 结果展示
编码3已存在,无法重复插入!
编码2已存在,无法重复插入!
HeroNode [no=1, name=林冲, nickname=豹子头]
HeroNode [no=2, name=吴用, nickname=智多星]
HeroNode [no=3, name=柴进, nickname=小旋风]
HeroNode [no=4, name=卢俊义, nickname=玉麒麟]
HeroNode [no=5, name=鲁智深, nickname=花和尚]
HeroNode [no=6, name=武松, nickname=行者]
HeroNode [no=7, name=宋江, nickname=及时雨]
HeroNode [no=8, name=李逵, nickname=黑旋风]
修改指定元素后:
HeroNode [no=1, name=林冲, nickname=豹子头]
HeroNode [no=2, name=吴用, nickname=智多星]
HeroNode [no=3, name=小关胜, nickname=小大刀]
HeroNode [no=4, name=卢俊义, nickname=玉麒麟]
HeroNode [no=5, name=鲁智深, nickname=花和尚]
HeroNode [no=6, name=武松, nickname=行者]
HeroNode [no=7, name=宋江, nickname=及时雨]
HeroNode [no=8, name=李逵, nickname=黑旋风]
删除指定元素后:
HeroNode [no=1, name=林冲, nickname=豹子头]
HeroNode [no=2, name=吴用, nickname=智多星]
HeroNode [no=4, name=卢俊义, nickname=玉麒麟]
HeroNode [no=5, name=鲁智深, nickname=花和尚]
HeroNode [no=6, name=武松, nickname=行者]
HeroNode [no=7, name=宋江, nickname=及时雨]
HeroNode [no=8, name=李逵, nickname=黑旋风]
3.单链表常见面试题
3.1 求单链表中的节点个数
3.1.1 思路
- 依旧是利用while()来遍历链表,并使用辅助变量来记录遍历次数
- 添加方法如下:
/*
* 计算链表中的节点数目(根节点不包括在内)
*/
public int calNodeNumber() {
HeroNode temp1=head;
int length=0;
while(temp1.next!=null) {
length++;
temp1=temp1.next;
}
return length;
}
3.2 查找单链表中的倒数第k个节点
3.2.1 思路
- 最直观的是先遍历一遍得到长度length,然后再遍历(length-k)次得到倒数第k个节点
- 如此把代码3.1.1稍加利用即可
/*
* 得到倒数第k个节点
*/
public HeroNode getKlast(int k) {
HeroNode temp1=head;
int length=0;
while(temp1.next!=null) {
length++;
temp1=temp1.next;
}
temp1=head;
if(k>length) {
System.out.println("链表节点数目达不到要求!");
}else {
for(int i=0;i<=length-k;i++) {
temp1=temp1.next;
}
}
return temp1;
}
3.3 单链表的反转
3.3.1 思路
- 自己想不出来什么思路
- 通用的思路是:创建一个新的根节点,然后遍历原来的链表,每遍历一个节点就把该节点放在新的根节点对应的链表的最前端
- 最后再将旧的根节点指向新的根节点的下一个节点
/*
* 反转单链表
*/
public SingleLinkedList reverseLinkedList() {
HeroNode temp1=this.head;
HeroNode auxiliary=new HeroNode(0," "," ");
HeroNode temp2=null;
while(temp1.next!=null) {
temp2=temp1.next.next;//temp2指向下下个地址,这里四个等式左右两边都是引用,即地址
temp1.next.next=auxiliary.next;//此时下下个地址被改变,所以temp2来提前保存
auxiliary.next=temp1.next;
temp1.next=temp2;
}
this.head.next=auxiliary.next;
return this;
}
- 引用即地址
- 当要改变链表节点的指向时,为了继续遍历原来的链表需要提前保存好节点原来的指向
- 当SingleLinkedList对象调用该方法时,返回的是对象本身,(对象会在函数内部得到修改)
- head为SingleLinkedList的私有属性,但由于reverseLinkedList()是SingleLinkedList的成员方法,故this.head可以访问该私有属性
- 这里的this的灵活使用挺好的
3.4 从尾到头打印单链表[方式1:先反转再遍历,方式2:栈]
- 方式1会改变原链表的结构,不可取
- 方式2调用内置Stack函数
3.5 合并两个有序的单链表,合并后的单链表仍旧有序
3.5.1思路
- 自己想不出来什么思路
- 通用的思路:与反转单链表类似,先创建一个新的根节点,然后同时对两个单链表进行遍历,此时两个单链表中较小的节点就会被连接到新的根节点的后面,然后进行下次遍历,依旧判断两个单链表中此时遍历到的节点中的最小者,将其放到新链表的最后
- 技巧点是必然会有一个单链表率先遍历结束,此时尚未遍历结束的链表无需继续遍历,可以直接全部移到新链表的后面
这个思路我没有做出来,原因就是同时遍历的时候耦合性太高,各种next都混了
然后我看了看别人写的代码:原文链接
// twoLinkedList方法
// 传入待合并的两个链表的头节点以及第三个单链表的头节点
public static void twoLinkedList(HeroNode head1, HeroNode head2, HeroNode head3) {
// 如果两个链表均为空,则无需合并,直接返回
if(head1.next == null && head2.next == null) {
return;
}
// 如果链表1为空,则将head3.next指向head2.next,实现链表2中的节点连接到链表3
if(head1.next == null) {
head3.next = head2.next;
} else {
// 将head3.next指向head1.next,实现链表1中的节点连接到链表3
head3.next = head1.next;
// 定义一个辅助的指针(变量),帮助我们遍历链表2
HeroNode cur2 = head2.next;
// 定义一个辅助的指针(变量),帮助我们遍历链表3
HeroNode cur3 = head3;
HeroNode next = null;
// 遍历链表2,将其节点按顺序连接至链表3
while(cur2 != null) {
// 链表3遍历完毕后,可以直接将链表2剩下的节点连接至链表3的末尾
if(cur3.next == null) {
cur3.next = cur2;
break;
}
// 在链表3中,找到第一个大于链表2中的节点编号的节点
// 因为是单链表,找到的节点是位于添加位置的前一个节点,否则无法插入
if(cur2.no <= cur3.next.no) {
next = cur2.next; // 先暂时保存链表2中当前节点的下一个节点,方便后续使用
cur2.next = cur3.next; // 将cur2的下一个节点指向cur3的下一个节点
cur3.next = cur2; // 将cur2连接到链表3上
cur2 = next; // 让cur2后移
}
// 遍历链表3
cur3 = cur3.next;
}
}
}
分析:
这段代码是正确的,然后我查看它的核心代码:
next = cur2.next; // 先暂时保存链表2中当前节点的下一个节点,方便后续使用
cur2.next = cur3.next; // 将cur2的下一个节点指向cur3的下一个节点
cur3.next = cur2; // 将cur2连接到链表3上
cur2 = next; // 让cur2后移
我感觉很妙,但是理解得很慢
然后我试着画图,就理解了
于是我利用画图这个工具,尝试按新的思路来写
新的思路:
遍历第二个链表,将第二个链表按顺序插入到第一个链表中去,遍历结束后将第三个待生成的链表指向第一个链表
/*
* 合并两个有序链表为一个新的有序链表:我写了一天!!!!!!!!!!!而且还是参考别人的才写出来!!总结技巧用自己的思路写出来。
*/
public static SingleLinkedList combine2LinkedList(SingleLinkedList st1,SingleLinkedList st2) {
HeroNode temp2=st2.head;
SingleLinkedList st=new SingleLinkedList();
HeroNode temp=st.head;
//第二种思路:遍历第二个链表,将其插入到第一个链表中去
while(temp2.next!=null) {
temp2=st1.addHelp(temp2);
// temp2=temp2.next;
}
temp.next=st1.head.next;
return st;
}
/*
* 辅助合并两个链表的函数
*/
public HeroNode addHelp(HeroNode heronode) {
HeroNode temp1=this.head;
while(true) {//写死循环首先要考虑结束条件
if(temp1.next==null) {
HeroNode temp5=heronode.next.next;
temp1.next=heronode.next;//当temp1.next得到新的赋值时,temp2对应链表的节点个数要减一,对应上下两行的操作
heronode.next=temp5;
break;
}else if(temp1.next.no==heronode.next.no) {
System.out.println("编码"+heronode.no+"已存在,无法重复插入!");
break;
}else if(temp1.next.no>heronode.next.no) {
HeroNode temp5=heronode.next.next;
// temp1.next=heronode.next;//与下行代码有先后顺序
heronode.next.next=temp1.next;
temp1.next=heronode.next;//当temp1.next得到新的赋值时,temp2对应链表的节点个数要减一
heronode.next=temp5;
break;
}
temp1=temp1.next;
}
return heronode;
}
- 启发:写链表相关题目是一定要画图!!!!
- 启发:写链表相关题目是一定要画图!!!!
- 启发:写链表相关题目是一定要画图!!!!
- 启发:写链表相关题目是一定要画图!!!!
- 启发:写链表相关题目是一定要画图!!!!