1,单链表是一种线性数据结构,但是它不一定是连续的,是一种链式存储结构(如下图)
单链表的每个节点分为data域(值域)和next域(指向下一个节点)
单链表分为有头节点和没有头节点,有头结点,头结点只有next域而没有data域
2,单链表的应用场景:举例水浒英雄排行榜(尚硅谷官方讲解):
定义:
no:英雄编号
name:英雄名称
nikeName:英雄昵称
next:指向下一个英雄节点
特别注意:
插入的时候判断的条件是新插入节点的编号与temp.next.no做比较,即temp.next.no>NewHeroNode.no,所以temp=head
显示链表的时候是从head.next显示的,所以temp=head.next
更新的时候是判断输入的点的no与每个节点的no是否相等,即temp.no==heroNode.no.所以temp=head.next
删除的时候是让删除节点的前一个节点的next.next与删除节点的next相等,即temp.next==temp.next.next,所以temp=head
换句话说:
遍历链表,执行操作时,判断条件有时候是 temp ,有时候是 temp.next ,Why?
对于插入、删除节点来说,需要知道当前待操作的节点(heroNode)前一个节点的地址(指针),如果直接定位至当前待操作的节点 heroNode ,那没得玩。。。因为不知道heroNode 前一个节点的地址,无法进行插入、删除操作,所以 while 循环中的条件使用 temp.next 进行判断
对于更新、遍历操作来说,我需要的仅仅就只是当前节点的信息,所以 while 循环中的条件使用 temp进行判断
public class singleList {
public static void main(String[] args) {
System.out.println("没有按照英雄序号的单链表如下");
// 进行测试
// 先创建节点
HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
// 创建要给链表
SingleLinkedList singleLinkedList = new SingleLinkedList();
// 加入
singleLinkedList.add(hero2);
singleLinkedList.add(hero1);
singleLinkedList.add(hero3);
singleLinkedList.add(hero4);
//删除
singleLinkedList.del(1);
singleLinkedList.list();
System.out.println("修改过后的单链表为");
HeroNode newHeroNode=new HeroNode(2,"小卢~~~","玉麒麟~~~");
singleLinkedList.update(newHeroNode);
singleLinkedList.list();
System.out.println("按照英雄序号的单链表如下");
// 创建要给链表
SingleLinkedList singleLinkedList2 = new SingleLinkedList();
// 加入
singleLinkedList2.addByOrder(hero2);
singleLinkedList2.addByOrder(hero1);
singleLinkedList2.addByOrder(hero3);
singleLinkedList2.addByOrder(hero4);
singleLinkedList2.list();
}
}
//定义 HeroNode类,里面定义了每一个英雄节点
class HeroNode{
public int no;
public String name;
public String nikeName;
public HeroNode next;
//定义构造器,输入每一个英雄的编号,姓名,绰号
public HeroNode(int no, String name, String nikeName) {
this.no = no;
this.name = name;
this.nikeName = nikeName;
}
//重写toString方法,吧我们输入的节点值输出出来
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nikeName='" + nikeName + '\'' +
'}';
}
}
//定义一个单链表SingleLinkedList来管理我们的英雄们
class SingleLinkedList{
//创建一个头结点,头结点是单链表的入口,不能动,也不春芳具体的数据,只指向下一个节点
private HeroNode head=new HeroNode(0,"","");
//我们定义一个现实该单链表的方法list()
//首先我们定义辅助变量temp,相当于一个指针,指向当前节点的下一个节点
//当temp==null的时候表示到底了
//遍历的时候我们只需要temp=temp.next
public void list(){
if (head.next==null){
System.out.println("链表为空");
return;
}
//因为我们不出书head,所以我们遍历链表的时候把temp首先指向head.next
HeroNode temp=head.next;
while (true){
//判断是否到链表的最后
if (temp==null){
break;
}
System.out.println(temp);
temp=temp.next;
}
}
//我们在向链表中插入数据时候,先不考虑英雄的编号问题,我们用尾部插入法来插入数据
//找到当前链表的最后一个节点
// 然后差这个节点的next指向新插入的节点
public void add(HeroNode heroNode){
//因为head节点不能动,因此我们需要一个辅助遍历temp
//这时我们是从head的下一个节点开始插入数据,而我们的思路是temp.next=heroNode.所以我们定义temp=head,即从第一个开始插入
//如果我们定义temp=head.next.这时我们插入的一个节点heroNode是第二个节点,第一个节点空了,就会显示链表为空
HeroNode temp=head;
//这个循环就是找到链表的最后
while (true){
//找到了链表的最后,遍历结束
if (temp.next==null){
break;
}
//如果我们没有找到链表的最后,将temp后移
temp=temp.next;
}
//当退出这个循环的时候,temp就是链表的最后
//这是我们将temp的next指向我们插入的新节点即可
temp.next=heroNode;
}
//我们如何按照英雄的序号来进行单链表的插入呢?
//1,定义一个辅助变量temp,指向当前节点
//2,如果temp.next.no>heroNode.no,我们便可以把新的节点插入到temp.next节点之前
//3.heroNode.next=temp.next; temp.next=heroNode;
public void addByOrder(HeroNode heroNode){
//因为是单链表,我们head不能动,所以我们创建一个辅助指针
HeroNode temp=head;
boolean flag=false;//flag标志添加的编号是否存在,默认为false
while (true){
if (temp.next==null){//说明temp已经是链表的最后
break;
}
if (temp.next.no>heroNode.no){//位置找到,就在temp的后面插入
break;
}else if (temp.next.no==heroNode.no){//说明添加新的英雄的编号已经存在
flag=true;//说明编号存在
break;
}
temp=temp.next;
}
if (flag){
System.out.printf("编号%d已经存在,不能插入\n",heroNode.no);
}else {
heroNode.next=temp.next;
temp.next=heroNode;
}
}
//修改节点信息
//定义一个辅助变量temp指向当前节点,当temp.no=newHeroNode.no的时候,改变temp节点的值
public void update(HeroNode heroNode){
if (head.next==null){//判断是否为空
System.out.println("链表为空");
return;
}
//遍历,这个时候我们要从head.next来判断这个点和我们新输入的点的序号no是否一致
HeroNode temp=head.next;
boolean flag=false;//表示是否找到该节点
while (true){
if (temp==null){
break;//已经遍历结束
}
if (temp.no==heroNode.no){//找到
flag=true;
break;
}
temp=temp.next;
}
//根据flag判断是否能找到要修改的节点
if (flag){
temp.name=heroNode.name;
temp.nikeName=heroNode.nikeName;
}else {//没有找到
System.out.printf("没有找到编号$d的节点,不能修改\n",heroNode.no);
}
}
//删除节点
//1,我们首先找到要删除的节点的前一个节点temp,如果我们找要删除的节点,单向链表无法操作
//2,然后我们把temp.next=temp.next.next
//3,被删除的节点.将不会有其他引用指向,会被垃圾回收机制回收
public void del(int no){
//因为第一个节点也有可能被删除,所以我们把temp=head
HeroNode temp=head;
boolean flag=false;//标志着是否找到删除的节点
while (true){
if (temp==null){//已经找到看了链表的最后
break;
}
if (temp.next.no==no){//找到删除的节点
flag=true;
break;
}
temp=temp.next;//temp后移,遍历
}
//判断flag
if (flag){//知道到
//可以删除
temp.next=temp.next.next;
}else{
System.out.printf("要删除的%d节点不出在\n",no);
}
}
}
参考文献:第 4 章 链表_Oneby的博客-优快云博客;尚硅谷Java数据结构与java算法(Java数据结构与算法)_哔哩哔哩_bilibili