目录
1. 链表介绍
(1)链表是以节点的方式来存储,是链式存储。
(2)每个节点包含data域:存放数据,next域:指向下一个节点。
(3)最后一个节点的next为null,表示链表结束。
(4)链表是有序的列表,但是它在内存中不一定是连续存储。如下图:
(5)链表分为:带头节点的链表 和 不带头节点的链表,根据实际需求确定。
(6)单链表(带头结点)逻辑结构示意图:
2. 单链表应用实例
使用带有head头的单向链表实现“角色排行榜”,完成对角色排行榜的增删改查操作。
2.1 添加节点到链表尾部
(1)思路分析
1.先创建一个head头节点,表示单链表的头部。
2.之后每添加一个节点,就直接加入到链表的最后。
3.通过一个辅助变量遍历整个链表。
4.示意图:
(2)代码实现
节点类
//定义节点,每个HeroNode对象就是一个节点
class HeroNode {
public int id; //角色的编号
public String name; //角色的名字
public String nickname; //角色的昵称
public HeroNode next; //指向下一个节点
public HeroNode(int id, String name, String nickname) {
this.id = id;
this.name = name;
this.nickname = nickname;
}
//重写toString,方便显示信息
@Override
public String toString() {
return "id=" + id + ", name=" + name + ", nickname=" + nickname;
}
}
链表
//定义链表
class SingleLinkedList {
//初始化头节点,不存放具体数据,不能改变头节点
private HeroNode head = new HeroNode(0, "", "");
public HeroNode getHead() {
return head;
}
//添加节点到单向链表尾部,不考虑顺序
public void add(HeroNode heroNode) {
HeroNode temp = head; //指针,表示添加位置的前一个节点
//遍历链表,找到尾节点
while (true) {
//找到链表的尾节点
if (temp.next == null) {
break;
}
//没有找到,就将temp后移
temp = temp.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.next; //temp后移
}
}
2.2 按照顺序添加节点
(1)思路分析
1.先通过遍历,找到新添加节点的前一个位置,通过指针记录位置。
2.让新节点指向下一个节点。
3.让上一个节点指向新节点。
4.示意图:
(2)代码实现
(直接在SingleLinkedList类中添加方法)
//按照顺序,插入节点到指定位置
public void addByOrder(HeroNode heroNode) {
HeroNode temp = head; //指针,表示添加位置的前一个节点
boolean flag = false; //表示添加的编号是否存在
//遍历链表,找添加的位置
while (true) {
//判断是否到链表最后
if (temp.next == null) {
break;
}
//判断位置找到的情况
if (temp.next.id > heroNode.id) {//找到位置,直接插入
break;
} else if (temp.next.id == heroNode.id) {//已经存在
flag = true;
break;
}
temp = temp.next; //temp后移
}
//插入到链表中
if (flag) {//判断编号是否存在
System.out.println("当前id" + heroNode + "已经存在!");
} else {
heroNode.next = temp.next; //新节点指向下一个节点
temp.next = heroNode; //当前节点指向新节点
}
}
2.3 修改节点信息
(1)思路分析
1.先通过遍历,找到指定节点的位置,通过指针记录位置。
2.修改相关信息。
(2)代码实现
(直接在SingleLinkedList类中添加方法)
//修改节点信息,根据id编号查找
public void update(HeroNode newHeroNode) {
//判断链表是否为空
if (head.next == null) {
System.out.println("链表为空!");
return;
}
HeroNode temp = head.next; //指针,指向修改的节点
boolean flag = false; //表示是否找到该节点
//遍历链表,找指定节点
while (true) {
//判断是否到链表最后
if (temp == null) {
break;
}
//判断位置找到的情况
if (temp.id == newHeroNode.id) {//找到指定id
flag = true;
break;
}
temp = temp.next; //temp后移
}
//修改节点信息
if (flag) { //找到
temp.name = newHeroNode.name;
temp.nickname = newHeroNode.nickname;
} else { //没找到
System.out.println("没有找到" + newHeroNode.id + "编号");
}
}
2.3 删除节点
(1)思路分析
1.先通过遍历,找到待删除节点的前一个位置,通过指针记录位置。
2.将这个节点的next直接指向下下个节点
3.被删除的节点不会有任何引用指向,会被垃圾回收机制回收。
4.示意图:
(2)代码实现
(直接在SingleLinkedList类中添加方法)
//删除节点,根据id进行删除
public void del(int id) {
HeroNode temp = head; //指针,表示删除节点的前一个节点
boolean flag = false; //表示是否找到待删除的节点
遍历链表,找指定节点
while (true) {
//判断是否到链表最后
if (temp.next == null) {
break;
}
//找到待删除节点
if (temp.next.id == id) {
flag = true;
break;
}
temp = temp.next; //temp后移
}
//删除节点
if (flag) { //找到
temp.next = temp.next.next; //指向删除节点的后一个节点
} else {
System.out.println("要删除的" + id + "号节点不存在");
}
}
2.4 单链表常见题目
(1)求单链表中有效节点的个数
//获取单链表中节点的个数,不算头节点
//head 头节点
public static int getLength(HeroNode head) {
//判断链表是否为空
if (head.next == null) {
return 0;
}
int length = 0;//记录节点个数
HeroNode pointer = head.next; //指针,指向第一个节点
//遍历链表
while (pointer != null) {
length++;
pointer = pointer.next;
}
return length;
}
(2)查找单链表中的倒数第 k 个结点
//查找倒数第k个节点
//head 头节点,k 倒数第几个节点
public static void findLastIndex(HeroNode head, int k) {
//判断链表是否为空
if (head.next == null) {
System.out.println("链表为空");
return;
}
int length = 0;//记录节点个数
HeroNode pointer = head.next; //指针,指向第一个节点
while (pointer != null) {//遍历链表
length++;
pointer = pointer.next;
}
if (k <= 0 || k > length) {//校验
System.out.println("找不到");
return;
}
int sum = length - k; //表示整数第几个节点
pointer = head.next; //重置指针
while (sum > 0) {//遍历链表,找指定节点
sum--;
pointer = pointer.next;
}
System.out.println("倒数第"+ k + "个节点为" + pointer);
}
(3)单链表的反转
思路分析:
1.先定义一个新链表的头节点。
2.从头遍历原链表,每遍历一个节点,就放在新链表的第一个位置。
3.让原链表的头结点指向新链表头节点。
//单链表的反转
public static void reverseList(HeroNode head) {
//判断链表是否为空,或只有一个节点
if (head.next == null || head.next.next == null) {
return;
}
HeroNode pointer = head.next; //指针,指向第一个节点
HeroNode next = null; //存放指针的下一个节点
HeroNode reverseHead = new HeroNode(0, "", "");//新的链表
//遍历原链表,每遍历一个节点,就放在新链表的前面
while (pointer != null) {
next = pointer.next;//先暂时保存当前节点的下一个节点
pointer.next = reverseHead.next;//下一个节点指向新链表的第一个节点
reverseHead.next = pointer;//新头节点指向当前节点
pointer = next;//指针后移
}
head.next = reverseHead.next; //原头节点指向新头节点
}
(4)逆序打印单链表
思路分析:
1.可以使用上面的反转链表再打印,但这样会破坏原本的链表结构。
2.使用栈(先进后出)的特点进行逆序打印。
3.先遍历链表,将每个节点加入到栈中,然后遍历栈,进行打印。
//逆序打印单链表,使用栈(先进后出)完成
public static void reversePrint(HeroNode head) {
//判断链表是否为空
if (head.next == null) {
System.out.println("链表为空");
return;
}
Stack<HeroNode> stack = new Stack<HeroNode>();//创建栈
HeroNode pointer = head.next; //指针,指向第一个节点
//遍历链表,将每个节点加入到栈中
while (pointer != null) {
stack.add(pointer); //使用add方法入栈
pointer = pointer.next; //指针后移
}
//遍历栈,打印节点信息
while (stack.size() > 0) {
System.out.println(stack.pop()); //使用pop方法出栈
}
}