基本介绍
链表是有序的列表,只包含一个指针域、由n个结点链接形成的链表,就称为线型链表或者单向链表。如图:
通过上图,可以总结出链表的特点:
①. 链表是以结点的方式来存储的,是链式存储的。
②. 每个结点包含data域,next域:指向下一个结点。
③. 链表的各个结点不一定是连续的。
④. 链表有带头结点和不带头结点的。
链表结构示意图
代码实现
博主作为一个英雄联盟的老粉,从S4开始就征战峡谷之巅了,每每想起曾经和自己一起玩游戏的伙伴,现在都分散在各地,心里便不是滋味。所以下面,通过对英雄联盟中的英雄进行管理,增加,删除,修改,以及排序等。来实现对链表的操作。
首先创建一个英雄对象
public class HeroNode {
/**
* 编号
*/
public final int number;
/**
* 英雄名称
*/
public String name;
/**
* 定位
*/
public String position;
/**
* 价钱
*/
public final double price;
/**
* 下一个结点
*/
public HeroNode next;
public HeroNode(int number, String name, String position, double price) {
this.number = number;
this.name = name;
this.position = position;
this.price = price;
}
@Override
public String toString() {
return "HeroNode{" +
"number=" + number +
", name='" + name + '\'' +
", position='" + position + '\'' +
", price=" + price +
'}';
}
}
然后创建一个操作英雄的商城(链表对象)。
public class SaleLinkList {
/**
* 链表的头
*/
public final HeroNode headNode = new HeroNode(0, "", "", 0);
/**
* TODO 添加数据(不考虑英雄价钱和重复的情况)
*/
public void add(HeroNode node) {
// 将头结点存入临时变量
HeroNode temp = headNode;
// 遍历链表,找到链表最后一个结点
while (temp.next != null) {
// 如果没有找到,就将当前结点后移
temp = temp.next;
}
// 在链表最后添加新的结点
temp.next = node;
}
/**
*TODO 显示链表
*/
public void list() {
if (headNode.next == null) {
System.out.println("链表为空");
return;
}
HeroNode temp = headNode.next;
// 判断链表是否到最后
while (temp != null) {
System.out.println(temp);
// 将temp后移
temp = temp.next;
}
}
}
基本上,添加链表的操作就完成了,下面编写测试代码
public static void main(String[] args) {
HeroNode node1 = new HeroNode(1, "薇恩", "ADC", 6300);
HeroNode node2 = new HeroNode(2, "冰凤凰", "法师", 4800);
HeroNode node3 = new HeroNode(3, "阿卡丽", "刺客", 3150);
HeroNode node4 = new HeroNode(4, "努努", "打野", 450);
HeroNode node5 = new HeroNode(5, "小炮", "ADC", 1350);
HeroNode node6 = new HeroNode(1, "薇恩", "ADC", 6300);
// 创建链表(商城)
SaleLinkList saleLinkList = new SaleLinkList();
// 添加结点(添加英雄到商店)
saleLinkList.add(node1);
saleLinkList.add(node2);
saleLinkList.add(node3);
saleLinkList.add(node4);
saleLinkList.add(node5);
saleLinkList.add(node6);
// 显示链表(显示所有英雄)
saleLinkList.list();
}
打印结果是:
HeroNode{number=1, name='薇恩', position='ADC', price=6300.0}
HeroNode{number=2, name='冰凤凰', position='法师', price=4800.0}
HeroNode{number=3, name='阿卡丽', position='刺客', price=3150.0}
HeroNode{number=4, name='努努', position='打野', price=450.0}
HeroNode{number=5, name='小炮', position='ADC', price=1350.0}
HeroNode{number=1, name='薇恩', position='ADC', price=6300.0}
可以看到输出结果是正常的。证明添加和显示操作正确,但是这里出现了重复数据,在英雄联盟商城中,可不会出现重复英雄的。而且还有一点:英雄联盟中默认按照英雄的价钱降序排列,所以这里需要进行优化。
针对上面代码进行改进。
添加操作(思路分析):
1. 首先找到新添加结点的位置,通过变量 temp(指针)
2. 新的结点的 next(newNode.next = temp.next)
3. 将 temp.next 指向新的结点(temp.next = newNode)
因为链表还涉及修改、删除操作,所以这里先分析,后面再用代码依次实现
修改操作(思路分析):
1. 这里不能对 编号 以及 价钱 修改,不然就相当于添加新的数据
2. 找到需要修改的这个结点,根据变量 temp 进行查找
3. 满足条件: temp.number = newNode.number
4. 将新的值赋值给以前的值(temp.name = newNode.name;temp.position = newNode.position)
删除操作(思路分析):
1. 首先找到需要删除的这个结点的前一个结点 temp
2. 直接将前一个结点的 next 指向下下个结点(temp.next = temp.next.next)
3. 被删除的结点,没有其他引用,就会被垃圾回收机制回收
思路分析完,下面就完成代码的编写。
public class SaleLinkList {
/**
* 链表的头
*/
public final HeroNode headNode = new HeroNode(0, "", "", 0);
/**
* TODO 添加数据(不考虑英雄价钱和重复的情况)
*/
public void add(HeroNode node) {
// 将头结点存入临时变量
HeroNode temp = headNode;
// 遍历链表,找到链表最后一个节点
while (temp.next != null) {
// 如果没有找到,就将当前节点后移
temp = temp.next;
}
// 在链表最后添加新的节点
temp.next = node;
}
/**
* TODO 添加数据(根据英雄的价格进行排序,并对编号进行去重)
*/
public void addByNumber(HeroNode node) {
// 将 头结点 赋给 中间值
HeroNode temp = headNode;
// 判断 当前英雄的编号是否存在
boolean flag = false;
while (true) {
// 说明 temp 已经是 链表的最后
if (temp.next == null) {
break;
}
// 找到满足条件(按照价格插入,并且编号不能相等),在 temp 之后插入
if (temp.next.price < node.price && temp.next.number != node.number) {
break;
// 编号已经存在
} else if (temp.next.number == node.number) {
flag = true;
break;
}
// 后移,即遍历链表
temp = temp.next;
}
if (flag) {
System.out.printf("添加失败,插入当前的英雄的编号 %d 已经存在。\n", node.number);
} else {
// 将之前 temp 中的 下一个指针 赋给 当前的 node的 next
node.next = temp.next;
// 新的结点 指向当前 temp 的 next
temp.next = node;
}
}
/**
* TODO 修改结点信息,根据 number 来修改(编号和价钱不能修改)
*/
public void update(HeroNode node) {
if (headNode.next == null) {
System.out.println("链表为空");
return;
}
HeroNode temp = headNode.next;
// 是否找到这个结点
boolean flag = false;
while (true) {
if (temp == null) {
break;
}
if (temp.number == node.number) {
flag = true;
break;
}
temp = temp.next;
}
// 如果找到 对应的 编号,进行修改
if (flag) {
temp.name = node.name;
temp.position = node.position;
} else {
System.out.printf("修改失败,没有找到 编号 为 %d 的节点 \n", node.number);
}
}
/**
* TODO 需要找到待删除结点之前的结点, 比较temp.next.number = number(需要删除的结点)
*/
public void delete(int number) {
HeroNode temp = headNode;
// 是否找到删除结点
boolean flag = false;
while (true) {
// 判断是否为链表的尾部
if (temp.next == null) {
break;
}
if (temp.next.number == number) {
flag = true;
break;
}
// 链表后移
temp = temp.next;
}
if (flag) {
// 删除,改变 next 的指向
temp.next = temp.next.next;
}else {
System.out.printf("删除失败,没有找到 编号 为 %d 的节点 \n", number);
}
}
/**
* 显示链表
*/
public void list() {
if (headNode.next == null) {
System.out.println("链表为空");
return;
}
HeroNode temp = headNode.next;
// 判断链表是否到最后
while (temp != null) {
System.out.println(temp);
// 将temp后移
temp = temp.next;
}
}
}
编写测试类
public class Test {
public static void main(String[] args) {
HeroNode node1 = new HeroNode(1, "薇恩", "ADC", 6300);
HeroNode node2 = new HeroNode(2, "冰凤凰", "法师", 4800);
HeroNode node3 = new HeroNode(3, "阿卡丽", "刺客", 3150);
HeroNode node4 = new HeroNode(4, "努努", "打野", 450);
HeroNode node5 = new HeroNode(5, "小炮", "ADC", 1350);
HeroNode node6 = new HeroNode(1, "薇恩", "ADC", 6300);
SaleLinkList saleLinkList2 = new SaleLinkList();
// 添加节点(添加英雄到商店:价格排序,并进行重复校验)
saleLinkList2.addByNumber(node1);
saleLinkList2.addByNumber(node2);
saleLinkList2.addByNumber(node3);
saleLinkList2.addByNumber(node4);
saleLinkList2.addByNumber(node5);
saleLinkList2.addByNumber(node6);
System.out.println("修改之前:");
saleLinkList2.list();
saleLinkList2.update(new HeroNode(1, "大嘴", "ADC", 6300));
System.out.println("修改之后:");
saleLinkList2.list();
// 删除
saleLinkList2.delete(6);
saleLinkList2.delete(1);
System.out.println("删除后:");
saleLinkList2.list();
}
}
输出结果:
添加失败,插入当前的英雄的编号 1 已经存在。
修改之前:
HeroNode{number=1, name='薇恩', position='ADC', price=6300.0}
HeroNode{number=2, name='冰凤凰', position='法师', price=4800.0}
HeroNode{number=3, name='阿卡丽', position='刺客', price=3150.0}
HeroNode{number=5, name='小炮', position='ADC', price=1350.0}
HeroNode{number=4, name='努努', position='打野', price=450.0}
修改之后:
HeroNode{number=1, name='大嘴', position='ADC', price=6300.0}
HeroNode{number=2, name='冰凤凰', position='法师', price=4800.0}
HeroNode{number=3, name='阿卡丽', position='刺客', price=3150.0}
HeroNode{number=5, name='小炮', position='ADC', price=1350.0}
HeroNode{number=4, name='努努', position='打野', price=450.0}
删除失败,没有找到 编号 为 6 的节点
删除后:
HeroNode{number=2, name='冰凤凰', position='法师', price=4800.0}
HeroNode{number=3, name='阿卡丽', position='刺客', price=3150.0}
HeroNode{number=5, name='小炮', position='ADC', price=1350.0}
HeroNode{number=4, name='努努', position='打野', price=450.0}
Process finished with exit code 0
基本上单向链表(带头结点)的实现就完成了,数据结构学起来还是挺有意思的,就是有的时候自己理解的慢,就容易产生烦躁的情绪。因此,需要多多的学习,多多的练习,才能克服这种抵触情绪。
参考资料:
作者:韩顺平
课程:《Java数据结构与算法》
天气因你逆转,世界因你天晴。