文章目录
一、链表概述
1.1 链表介绍
链表( Linked List ),别名链式存储结构或单链表,是一种常见的基础数据结构,用于存储逻辑关系为 “一对一” 的数据。链表是线性表的一种,但是它并不像顺序表一样是连续存储在内存中的。链表的各个结点散布在各个内存区域,在每一个结点中都存放下一个结点的地址。
单链表在内存中是如下存储的:
其中:data 域存放的是本结点的数据,next 域存放的是下一个结点的地址。
通过上面的图,我们可以得到链表的几个特性:
-
链表是以结点的方式来存储的,是链式存储;
-
每一个结点包含 data 域、next 域。其中 next 域存放的是下一个结点的地址;
-
链表各个结点并不一定是连续(所谓连续指的是地址连续)存储的;
-
链表分为带头结点的链表和没有头结点的链表。
1.2 代码描述结点
从上面的定义可以知道,链表中的每个结点中存放的是本结点的数据以及下一个结点的地址。所以链表中的结点可以定义如下:
public ListNode{
public int age; // 本结点的信息
public String name;
public ListNode next; // 下一个结点的地址
}
二、单链表的基本操作
为了便于下面的演示,定义一个结点 HeroNode
如下,该结点可以理解为存储的是一个水浒英雄的信息:
class HeroNode {
private int no; // 本结点数据
private String name;
private String nickName;
public HeroNode next; // 指向下一个结点
public HeroNode(int no, String name, String nickName) {
this.no = no;
this.name = name;
this.nickName = nickName;
}
// getter and setter and toString ......
}
假设我现在有个需求,要求使用链表实现一个水浒英雄榜管理系统。这个系统的具体功能如下:
- 可以增加水浒英雄(直接在链表末尾追加)
- 可以根据水浒英雄的编号顺序来插入英雄
- 可以根据水浒英雄的编号删除英雄
- 可以根据编号更新水浒英雄的信息
根据以上的 4 个需求,我们下面将分 4 步来逐一解决。
2.1 增加结点
增加结点,对应的是需求 1,也就是直接在链表的末尾追加一个结点。
思路:
-
首先我们需要创建一个头结点,该结点的作用就是表示单链表的头,如果没有头结点,我们是无法知道链表的首个结点是谁、在哪;
-
单链表是单向的,所以我们需要从头结点开始遍历整个链表直到末尾,然后增加结点到链表的末尾;
-
需要注意的是,头结点是万万不能乱动的,所以我们最好将头结点复制到一个临时结点变量中,对临时变量进行遍历。
代码实现:
// 头结点中是不存储数据的,因此数据无所谓。只要存储下一个结点的地址即可
private static final HeroNode headNode = new HeroNode(0, "", "");
/**
* @Description 1. 插入结点到链表中(直接在末尾追加)
*/
private static void insertNode(HeroNode node) {
// 首先需要拷贝一份头结点
HeroNode tempNode = headNode;
// 插入结点到链表之中,需要首先遍历链表
while (true) {
// 判断有没有到达链表末尾
if (tempNode.next == null) {
// 如果当前结点的下一个结点为 null 时,说明当前结点是最后一个结点
tempNode.next = node;
node.next = null;
System.out.println("++++++++++++ 插入结点成功!");
break;
}
// 没有到达链表末尾,那就后移一个位置
tempNode = tempNode.next;
}
}
2.2 按顺序插入结点
按顺序插入结点,对应的是需求 2,也就是根据编号从小到大的顺序将英雄插入到链表中。
思路:
- 首先还是要创建一个头结点,然后拷贝一个头结点作为辅助变量,使用辅助变量来遍历整个链表;
- 如果出现某个结点(假设是 A 结点)的下一个结点(假设是 B 结点)的编号大于待插入结点的情况,那么就首先将 B 结点记录在待插入的结点中,然后再将这个待插入结点插入到 A 结点之后;
- 如果遍历到了链表末尾还没找到编号更大的,就直接插入到末尾即可。
代码实现:
/**
* @Description 2. 根据编号从小到大顺序插入结点
*/
public static void insertNodeByOrder(HeroNode node) {
// 头结点是代码的柱石,不能动,复制一份作为辅助变量
HeroNode tempNode = headNode;
// 遍历链表
while (true) {
if (tempNode.next == null) {
// 如果到了链表末尾,就直接插入
tempNode.n