链表
链表是有序的列表,但是它在内存中是存储如下
- 链表是以节点的方式来存储的
- 每个节点包含 data 域,next 域:指向下一个节点
- 链表的各个节点不一定是连续存储的
- 链表分 带头结点 的链表和 不带头结点 的链表
单链表的创建
添加
- 先创建一个 head 头结点,作用就是表示单链表的头
- 后面我们每添加一个结点,就直接加入到链表的最后
遍历:
通过一个辅助变量,帮助来遍历整个链表
单链表的应用实例
第一种方式:给链表添加结点时,直接添加到链表的尾部
第二种方式:添加结点,根据结点编号将结点插入到指定位置
第一种方式:
1、定义一个结点类(HeroNode),每个类对象都是一个链表的结点
class HeroNode{
int no; // 编号
String name; // 姓名
String nickname; // 昵称
HeroNode next; // 下一个节点
public HeroNode(int no, String name, String nickname) {
this.no = no;
this.name = name;
this.nickname = nickname;
}
// 重写toString方法
@Override
public String toString() {
return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
}
}
2、构建一个单链表 SingleLinkedList
class SingleLinkedList{
// 先初始化一个头节点,头节点不要动,不存放具体的数据
HeroNode head = new HeroNode(0, "", "");
}
2.1、在单链表中添加结点
- 找到当前链表的最后结点
- 将最后这个结点 next 指向新的结点
public void add(HeroNode heroNode) {
// 头节点不可移动,先用一个变量给其保存起来。
HeroNode temp = head;
while(true) {
// 找到链表的最后
if(temp.next == null) {
break;
}
// 如果没有找到最后,就将 temp后移
temp = temp.next;
}
// 当退出 while 循环的时候,temp 就指向了链表的最后
// 将最后这个节点的 next 指向新的节点
temp.next = heroNode;
}
3、打印出链表
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;
}
}
4、进行测试
public static void main(String[] args) {
// 测试
// 创建几个节点
HeroNode hero1 = new HeroNode(1, "AA", "AAA");
HeroNode hero2 = new HeroNode(2, "BB", "BBB");
HeroNode hero3 = new HeroNode(3, "CC", "CCC");
HeroNode hero4 = new HeroNode(4, "DD", "DDD");
// 创建要给链表
SingleLinkedList singlelist = new SingleLinkedList();
// 加入
singlelist.add(hero1);
singlelist.add(hero2);
singlelist.add(hero3);
singlelist.add(hero4);
singlelist.list();
}
第二种方式
步骤:
- 首先找到新添加的结点的位置,是通过辅助变量(temp),通过遍历来保存
- 新的结点.next = temp.next
- 将 temp.next = 新的结点
添加规则:
因为头节点不能动,因此我们还需要一个辅助指针来帮我们找到添加的位置
待插入的位置应该是在辅助变量结点 temp 之后,temp.next 之前
写一个 addByOrder 的方法
public void addByOrder(HeroNode heroNode) {
// 因为头节点不能动,因此我们仍然通过一个辅助指针(变量)来帮助找到添加的位置
// 因为是单链表,我们找到 temp 是位于添加位置的前一个节点,否则插入不了
HeroNode temp = head;
boolean flag = false;
while(true) {
if(temp.next == null) { // 说明到了链表的最后,直接 break
break;
}
if(temp.next.no > heroNode.no) { // 说明希望添加的 heroNode 的编号存在
break;
}
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 是不是在最后一个结点
- 判断待插入的结点是不是已经存在了,如果存在了输出已经存在
链表结点的修改
修改结点通过结点的编号来修改,但是结点的编号是不可以改变的
public void update(HeroNode newHeroNode) {
// 判断是否为空
if(head.next == null) { //
System.out.println("链表为空");
return;
}
// 找到需要修改的节点,根据 no 编号
// 定义一个辅助变量
HeroNode temp = head.next; // 首先指向的 0 号节点
boolean flag = false;
while(true) {
if(temp == null) {
break; // 表示已经遍历完成
}
if(temp.no == newHeroNode.no) { // 找到
flag = true;
break;
}
temp = temp.next;
}
// 根据 flag
if(flag) {
temp.name = newHeroNode.name;
temp.nickname = newHeroNode.nickname;
}else {
System.out.printf("没有找对编号为 %d 的节点", newHeroNode.no);
}
}
删除单个结点
思路:
- head 不能用,找一个辅助结点 temp 用来指向待删除的前一个结点
- 在进行比较的时候是
temp.next 与 要删除的结点的 编号(no) 进行比较
public void del(int no) {
HeroNode temp = head;
boolean flag = false;
while(true) {
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("没有找到对应的节点");
}
}