单链表的增删改查
1. 什么是单链表
单链表是一种常见的数据结构,它由一个节点序列组成,每个节点只包含一个指向下一个节点的指针。相比于其他链表结构,单链表的节点体积较小,因此常用于存储小型数据集,如栈、队列和链表等。
单链表的节点结构通常包含一个数据域和一个指向下一个节点的指针。节点之间的链接是通过指针实现的,也就是说,第一个节点只有一个指向它的指针,而最后一个节点则只有一个指向第一个节点的指针。
单链表的操作比较简单,主要包括添加节点、删除节点和遍历节点等。在添加节点时,只需要在末尾添加一个新节点即可。在删除节点时,可以根据指针找到需要删除的节点,然后将其指针指向下一个节点或者原始节点。在遍历节点时,可以使用一个变量来跟踪当前遍历的节点,然后依次遍历每个节点。
优点是节点体积较小,因此存储数据的效率较高。另外,单链表的操作也比较简洁,易于实现和维护。然而,单链表也有一些缺点,例如插入和删除节点时需要遍历整个链表,因此操作时间较长。
在实际应用中,单链表通常用于小型数据集的存储和处理,例如栈、队列和链表等。同时,单链表也是计算机科学中经典的数据结构之一,对于学习数据结构和算法的人来说,是一个必须掌握的数据结构之一。
1.1 带头结点的单链表
带头的单链表:是指链表的头部节点,只包含一个指向下一个节点的指针。也就是说,头部节点包含一个指向下一个节点的指针如下图所示:
接下来的题目我们全以带头结点的单链表举例。
1.2 不带头结点的单链表
不带头的单链表则是指链表的第一个节点存放数据,如下图所示:
2. 单链表的基本操作
2.1 节点类的定义
链表是由节点组成,所以我们先来构建节点,下面是我定义了一个英雄联盟的节点:
主要做了两件事:
1.定义了英雄节点的属性和构造方法
2.重写toString方法,方便输出查看英雄的信息
/**
* @author 176
* 英雄联盟节点
*/
public class HeroNode {
public int nid; //英雄id
public String name; //英雄名称
public String nickName; //英雄昵称
public HeroNode next; //指向下一个节点
public HeroNode(int nid, String name, String nickName) {
this.nid = nid;
this.name = name;
this.nickName = nickName;
}
//重写了tosring方法方便输出效果
//这里删除了next的输出,因为每次循环都会重复输出
@Override
public String toString() {
return "HeroNode{" +
"nid=" + nid +
", name='" + name + '\'' +
", nickName='" + nickName + '\'' +
'}';
}
}
2.2 头插法
我们全带头结点的单链表举例:定义好头结点之后,我们先来试试相对简单的头插法
思路:
1.head头节点不能动,所以我们定义一个辅助指针temp用于代替头指针进行相应操作
2.赋值辅助指针temp等于初始链表head指针的下一个节点
3.head指针的指向需要添加的节点
4.添加的节点再指向temp节点
备注:head的next是节点2,把节点2取名为temp(不是新创建和节点2一样的节点,想不通的同学可以多想想,这里需要多动动脑子)
//头插法
public void addHead(HeroNode newHeroNode){
HeroNode temp= head.next; //赋值辅助指针temp等于初始链表head指针的下一个节点
head.next=newHeroNode;//head指针的指向需要添加的节点
newHeroNode.next=temp;添加的节点再指向temp节点
}
2.3尾插法
尾插法也是相对比较简单,来让我们一起试试吧
思路:
1.同样定义一个temp辅助节点,因为head节点不能动
2.遍历单链表,找到最后一个节点
3.在最后一个节点的next指向添加的新节点
//添加节点:尾插法
//1.找到最后一个节点
//2.将最后一个节点的next域指向添加节点的data域
public void addEnd(HeroNode newHeroNode) {
//因为要从第一个节点查起,所以temp指向head而不是他的next
HeroNode temp = head;
//遍历链表找到最后一个节点
while (true) {
//找到链表最后的一个节点
if (temp.next == null) {
break;
}
temp = temp.next;
}
//当循环结束后temp就指向了最后的节点
temp.next = newHeroNode;//直接将temp指向添加的节点即可
}
2.4 升序插法(进阶)
&nubp;&nubp;升序插法相对较难,但是如果完全理解了头尾插法,就比较简单了
举个使用例子:用户的id从客户端传入到服务器端,再通过服务器查询数据库来对用户进行排序,如果使用了单链表升序插法,则可以直接在内存中解决排序问题,不需要通过数据库排序,大大的减少了等待时间。
思路:
1.定义一个辅助指针temp指向head节点用于遍历链表
2.遍历链表,查询temp的下一个节点的nid是否大于新增节点的nid,如果大于直接在temp这个位置插入节点
3.如果遍历查到有相同的nid则提示已存在
4.如果遍历完还没有匹配的结果,则直接在队尾插入即可
备注:注意的点为用temp的下一个节点的nid(temp.next.nid)和新增的nid(newHeroNode.nid)对比,因为是单链表,temp指向下一个节点后就回不来了,所以不能用temp.nid和newHeroNode.nid对比
//按id顺序添加节点
public void addOrderBy(HeroNode newHeroNode) {
//定义一个辅助指针指向head节点用于遍历链表
HeroNode temp = head;
Boolean flag = false;//用于判断节点编号是否已存在
while (true) {
if (temp.next == null) { //说明链表已经在最后了
break;
}
if (temp.next.nid > newHeroNode.nid) { //如果temp的下一个节点的id大于需要插入的id
break; //说明位置找到 直接在temp上插入
} else if (temp.next.nid == newHeroNode.nid) {
flag = true; //如果id相等说明已存在
break;
}
//如果上列条件都没满足,接着遍历下一个
temp=temp.next;
}
if(flag){
System.out.println("节点"+newHeroNode.nid+"编号已存在,不可再次插入");
}else{
newHeroNode.next=temp.next; //插入节点的next等于temp的原本的下一个节点
temp.next=newHeroNode; //temp的下一个节点改为插入的节点
}
}
2.5 打印单链表
这里比较简单,就是对单链表的遍历,还是用辅助指针temp遍历一遍,当temp为空时,则说明遍历完成。
//显示所有节点
public void list() {
if (head.next == null) {//先判断链表是否为空
return;
}
HeroNode temp = head.next;//因为头结点不能动 定义辅助指针变量进行遍历
while (true) {
if (temp == null) { //循环结束条件为temp为空
break;
}
System.out.println(temp);//输出链表信息
temp = temp.next;//记住一定要后移
}
}
2.6修改节点
思路:用辅助指针temp的nid和需要修改节点的nid进行对比(因为nid不会发生改变)
//修改节点
public void update(HeroNode newHeroNode){
HeroNode temp=head.next; //定义辅助指针temp,用于遍历寻找需要修改的节点
boolean flag=false;//用于判断有没有找到需要修改的节点
while (true){
//判断是否到链表最后
if(temp == null){
break;//已经遍历完此列表,结束循环
}
//判断修改的id是否相等
if(temp.nid==newHeroNode.nid){
flag=true;
break;
}
temp=temp.next;
}
//如果找到这进行更新
if(flag){
temp.name= newHeroNode.name;
temp.nickName= newHeroNode.nickName;
System.out.println("修改成功!!!");
}else{
System.out.println("修改失败,未查到此id"+newHeroNode.nid+"的信息!!!");
}
}
2.7删除节点
思路: 找到需要删除节点的前一个节点(temp),然后将temp的next域指向temp.next.next
//删除节点
//思路 找到需要删除节点的前一个节点(temp)
//直接temp.next==temp.next.next
public void delete(int nid) {
HeroNode temp = head;//定义辅助指针temp,用于遍历寻找需要删除的节点
boolean flag = false;//用于判断有没有找到需要删除的节点
//判断是否到链表最后
while (true) {
if (temp.next == null) {
break;
}
//如果temp的下一个节点的nid等于要删除的nid则说明找到了
if (temp.next.nid == nid) {
flag = true;
break;
}
temp = temp.next;//没有则继续往下遍历
}
if (flag) {
temp.next = temp.next.next;
System.out.println("节点已经成功删除!!!");
} else {
System.out.println("没有找到此节点!!!");
}
}
3.测试
3.1测试主方法
public class SingleLinkedDemo {
public static void main(String[] args) {
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.addEnd(hero1);
// singleLinkedList.addEnd(hero2);
// singleLinkedList.addEnd(hero3);
// singleLinkedList.addEnd(hero4);
System.out.println("修改前的信息");
singleLinkedList.addOrderBy(hero3);
singleLinkedList.addOrderBy(hero1);
singleLinkedList.addOrderBy(hero4);
singleLinkedList.addOrderBy(hero2);
singleLinkedList.list();
System.out.println("修改后的信息");
HeroNode h1 = new HeroNode(3,"mw","mw");
singleLinkedList.update(h1);
singleLinkedList.list();
System.out.println("删除节点操作后的数组");
singleLinkedList.delete(2);
singleLinkedList.list();
}
}
3.2测试结果
D:\javazl\javaWeb\jdk1.8\bin\java.exe "-javaagent:D:\develop\idea\IntelliJ IDEA 2021.2.1\lib\idea_rt.jar=60000:D:\develop\idea\IntelliJ IDEA 2021.2.1\bin" -Dfile.encoding=UTF-8 -classpath D:\javazl\javaWeb\jdk1.8\jre\lib\charsets.jar;D:\javazl\javaWeb\jdk1.8\jre\lib\ext\access-bridge-64.jar;D:\javazl\javaWeb\jdk1.8\jre\lib\ext\cldrdata.jar;D:\javazl\javaWeb\jdk1.8\jre\lib\ext\dnsns.jar;D:\javazl\javaWeb\jdk1.8\jre\lib\ext\jaccess.jar;D:\javazl\javaWeb\jdk1.8\jre\lib\ext\jfxrt.jar;D:\javazl\javaWeb\jdk1.8\jre\lib\ext\localedata.jar;D:\javazl\javaWeb\jdk1.8\jre\lib\ext\nashorn.jar;D:\javazl\javaWeb\jdk1.8\jre\lib\ext\sunec.jar;D:\javazl\javaWeb\jdk1.8\jre\lib\ext\sunjce_provider.jar;D:\javazl\javaWeb\jdk1.8\jre\lib\ext\sunmscapi.jar;D:\javazl\javaWeb\jdk1.8\jre\lib\ext\sunpkcs11.jar;D:\javazl\javaWeb\jdk1.8\jre\lib\ext\zipfs.jar;D:\javazl\javaWeb\jdk1.8\jre\lib\jce.jar;D:\javazl\javaWeb\jdk1.8\jre\lib\jfr.jar;D:\javazl\javaWeb\jdk1.8\jre\lib\jfxswt.jar;D:\javazl\javaWeb\jdk1.8\jre\lib\jsse.jar;D:\javazl\javaWeb\jdk1.8\jre\lib\management-agent.jar;D:\javazl\javaWeb\jdk1.8\jre\lib\resources.jar;D:\javazl\javaWeb\jdk1.8\jre\lib\rt.jar;E:\java\DateStructures\out\production\DateStructures com.itcy.linked.SingleLinkedDemo
修改前的信息
HeroNode{nid=1, name='德玛西亚之力', nickName='盖伦'}
HeroNode{nid=2, name='诺克萨斯之手', nickName='诺手'}
HeroNode{nid=3, name='蛮族之王', nickName='蛮王'}
HeroNode{nid=4, name='熔岩巨兽', nickName='石头'}
修改后的信息
修改成功!!!
HeroNode{nid=1, name='德玛西亚之力', nickName='盖伦'}
HeroNode{nid=2, name='诺克萨斯之手', nickName='诺手'}
HeroNode{nid=3, name='mw', nickName='mw'}
HeroNode{nid=4, name='熔岩巨兽', nickName='石头'}
删除节点操作后的数组
节点已经成功删除!!!
HeroNode{nid=1, name='德玛西亚之力', nickName='盖伦'}
HeroNode{nid=3, name='mw', nickName='mw'}
HeroNode{nid=4, name='熔岩巨兽', nickName='石头'}
Process finished with exit code 0
谢谢的大家的观看!!!
若有误,亲指教!!!