开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第十五天,点击查看活动详情
🤗前言:上文学习了队列,本文带领大家走进单链表~
链表
Linked List
- 带头节点的链表
- 没有头节点的链表
- 有序的列表,链表以节点方式存储,链式存储
- 每个节点包含 data 域 ,next 域:指向下一个节点
- 链表的节点之间不一定是连续的
单链表
带头结点(单链表)逻辑结构示意图
内存布局图
水浒传英雄排行榜管理
分析
-
完成CRUD功能
-
添加英雄方式
- 直接添加到链表尾部
- 根据排名将英雄插入到指定位置(英雄存在,则无效)
-
单链表创建示意图
单链表添加节点
- 先创建一个head头节点,单链表的头
- 每添加一个节点,就直接加入到链表的最后遍历
遍历
- 通过一个变量遍历,帮助遍历整个链表
package com.linkedlist;
/**
* @author Kcs 2022/8/7
*/
public class SingleLinkedListDemo {
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.add(hero1);
singleLinkedList.add(hero2);
singleLinkedList.add(hero3);
singleLinkedList.add(hero4);
//显示
singleLinkedList.list();
}
}
/**
* 定义一个SingleLinkedList管理英雄
*/
class SingleLinkedList {
/**
* 初始化一个头节点
*/
private HeroNode head = new HeroNode(0, "", "");
/**
* 添加节点到单向链表
* 找当前链表的最后节点
* 最后节点的next 指向新的节点
*/
public void add(HeroNode heroNode) {
//添加一个临时变量temp
HeroNode temp = head;
//遍历链表,找到最后
while (true) {
//找到链表的最后
if (temp.next == null) {
break;
}
//如果没有找到,temp后移
temp = temp.next;
}
//退出循坏,temp就指向链表的最后
temp.next = heroNode;
}
//显示链表,遍历
public void list() {
//判断链表是否为空
if (head.next == null) {
System.out.println("链表为空");
return;
}
//头节点不能动,辅助变量遍历
HeroNode temp = head.next;
while (true) {
//判断是否到链表最后
if (temp == null) {
break;
}
//输出节点信息
System.out.println(temp);
//将temp后移,不然就是死循环
temp = temp.next;
}
}
}
/**
* 定义一个HeroNode,每个heroNode对象就是一个节点
*/
class HeroNode {
/**
* 编号
*/
public int no;
/**
* 姓名
*/
public String name;
/**
* 昵称
*/
public String nickname;
/**
* 指向下一个节点
*/
public HeroNode next;
/**
* 构造器
* @param no
* @param name
* @param nickname
*/
public HeroNode(int no, String name, String nickname) {
this.no = no;
this.name = name;
this.nickname = nickname;
}
/**
* 重写toString
*/
@Override
public String toString() {
return "Heroes{" +
"no=" + no +
", name='" + name + ''' +
", nickname='" + nickname + ''' +
'}';
}
}
以上代码,添加的顺序改变是无序的,就按添加的代码进行排序(也就是第一种方式)
方式二添加英雄方式
- 根据排名(编号)将英雄插入到指定位置(英雄存在,则无效)
分析
- 首先找到新添加的节点的位置,通过辅助变量(指针),通过遍历
- 新的节点.next = temp.next
- temp.next = 新的接点
package com.linkedlist;
import com.sun.xml.internal.ws.util.StreamUtils;
/**
* @author Kcs 2022/8/7
*/
public class SingleLinkedListDemo {
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.add(hero1);
// singleLinkedList.add(hero2);
// singleLinkedList.add(hero3);
// singleLinkedList.add(hero4);
//根据编号添加
singleLinkedList.addHeroByOrder(hero4);
singleLinkedList.addHeroByOrder(hero1);
singleLinkedList.addHeroByOrder(hero3);
singleLinkedList.addHeroByOrder(hero2);
//显示
singleLinkedList.list();
}
}
/**
* 定义一个SingleLinkedList管理英雄
*/
class SingleLinkedList {
/**
* 初始化一个头节点
*/
private HeroNode head = new HeroNode(0, "", "");
/**
* 根据排名(编号)将英雄插入到指定位置(英雄存在,则无效)
*/
public void addHeroByOrder(HeroNode heroNode) {
//设置一个临时遍历,找到要添加的位置 ,temp位于添加位置的前一个节点,否则插入失败
HeroNode temp = head;
//标志编号是否存在
boolean flag = false;
while (true) {
//到链表的最后,为空
if (temp.next == null) {
break;
}
//确定添加位置
if (temp.next.no > heroNode.no) {
break;
} else if (temp.next.no == heroNode.no) {
//添加的编号已存在,无效
flag = true;
break;
}
//后移,继续遍历
temp = temp.next;
}
//判断flag
if (flag) {
//已存在编号
System.out.printf("该英雄编号%d已存在,不能添加\n", heroNode.no);
} else {
//插入链表中,temp的后面
heroNode.next = temp.next;
temp.next = heroNode;
}
}
/**
* 显示链表,遍历
*/
public void list() {
//判断链表是否为空
if (head.next == null) {
System.out.println("链表为空");
return;
}
//头节点不能动,辅助变量遍历
HeroNode temp = head.next;
while (true) {
//判断是否到链表最后
if (temp == null) {
break;
}
//输出节点信息
System.out.println(temp);
//将temp后移,不然就是死循环
temp = temp.next;
}
}
}
/**
* 定义一个HeroNode,每个heroNode对象就是一个节点
*/
class HeroNode {
/**
* 编号
*/
public int no;
/**
* 姓名
*/
public String name;
/**
* 昵称
*/
public String nickname;
/**
* 指向下一个节点
*/
public HeroNode next;
/**
* 构造器
* @param no
* @param name
* @param nickname
*/
public HeroNode(int no, String name, String nickname) {
this.no = no;
this.name = name;
this.nickname = nickname;
}
/**
* 重写toString
*/
@Override
public String toString() {
return "Heroes{" +
"no=" + no +
", name='" + name + ''' +
", nickname='" + nickname + ''' +
'}';
}
}
单链表修改节点
修改英雄的信息
/**
* 修改节点信息,根据编号修改,编号不能修改
* 根据newHeroNode 的 no 来修改信息
*/
public void update(HeroNode updateHeroNode) {
//判断链表是否空
if (head.next == null) {
System.out.println("链表为空!!!");
return;
}
//找到修改的节点,根据no
HeroNode temp = head.next;
//判断是否找到节点
boolean flag = false;
while (true) {
if (temp == null) {
break;
}
//找到
if (temp.no == updateHeroNode.no) {
flag = true;
break;
}
//后移
temp = temp.next;
}
//判断是否为要修改的节点
if (flag) {
temp.name = updateHeroNode.name;
temp.nickname = updateHeroNode.nickname;
} else {
//没有找到
System.out.printf("没有找到编号 %d的信息,不能修改\n", updateHeroNode.no);
}
}
单链表删除节点
- 找到要删除节点的前一个节点temp
- temp.next = temp.next.next (跳过的节点,就是直接删除掉,被垃圾回收了)
/**
* 删除节点,不能改变head 节点,通过temp找到待删除的节点的前一个节点
* temp.next.no 与删除节点的no比较
*/
public void delete(int no) {
HeroNode temp = head;
//标志是否找到待删除的节点
boolean flag = false;
while (true) {
//temp到链表最后
if (temp.next == null) {
break;
}
if (temp.next.no == no) {
//找到要删除节点的前一个节点
flag = true;
break;
}
//后移
temp = temp.next;
}
//判断flag
if (flag) {
//找到 ,删除
temp.next = temp.next.next;
} else {
System.out.println("要删除的节点%d不存在\n" + no);
}
}
单链表查询节点
/**
* 查找英雄信息
*/
public void search(int no) {
//判断链表是否为空
HeroNode temp = head;
if (temp.next == null) {
System.out.println("链表为空!!!");
return;
}
//判断是否找到节点
boolean flag = false;
while (true) {
//temp到链表最后
if (temp.next == null) {
break;
}
if (temp.no == no) {
//找到要查找节点的前一个节点
flag = true;
break;
}
//后移
temp = temp.next;
}
//判断flag
if (flag) {
//查找到
System.out.println(temp);
} else {
System.out.println("要查找的节点%d不存在\n" + no);
}
}
单链表面试题
-
单链表(节点)反转
- 定义一个节点 reverse = new HeroNode()
- 从头到尾遍历原来的链表,每遍历一个节点,并放在新的链表的最前端,最前端的next域指向上一个最前端的地方,原来的链表的第一个就没有next
- 原来的链表的 head.next = reverseHead.next
//将链表反转 public static void reverseList(HeroNode head) { //如果链表为空,或者只有一个节点,无须反转,直接返回 if (head.next == null || head.next.next == null) { return; } //辅助变量 HeroNode cur = head.next; //指向当前节点(cur节点)的下一个节点, HeroNode next = null; HeroNode reverseHead = new HeroNode(0, "", ""); //遍历原来的链表,每遍历一个新的节点,就将其取出,放在新的链表reverseHead 的最前端 while (cur != null) { //暂存当前节点的下一个节点 next = cur.next; //将cur的下一个节点的指向新的链表的最前端 cur.next = reverseHead.next; //cur链接到新的链表上 reverseHead.next = cur; //cur后移,不能是cur.next 因为反转后的next以及不存在了 cur = next; } //将原始链表的head.next指向reverseHead.next 实现反转 head.next = reverseHead.next; }