链表(Linked List)
物理结构
链表是有序列表,但是它在内存中的存储方式如下
- 链表是以节点的方式来存储
- 每个节点包含data域(存放数据),next域指向下一个节点
- 如图:链表的各个节点不一是连续存放
- 链表分带头节点的链表和没有头节点的链表,根据实际和需求来确定
逻辑结构
实现思路
创建单链表
-
先创建一个head节点,作用是表示单链表的头
-
后面每添加一个节点,就直接加入到链表的最后
遍历单链表
通过一个辅助遍历,帮助遍历整个链表
代码简单实现
//定义SingleLinkedList管理节点
class SingleLinkedList {
//初始化一个头节点, 不存放具体的思路
private HeroNode head = new HeroNode(0, "", "");
/**
* 添加节点到单链表
* 当不考虑编号顺序时
* 1.找到当前链表的最后节点
* 2.将最后的节点的next指向新的节点
*/
public void add(HeroNode heroNode) {
//因为head节点不能动,因此需要一个辅助遍历temp
HeroNode temp = head;
//遍历链表,找到最后
//找到链表的最后:next == null
while (temp.next != null) {
//如果没有到最后,就将temp后移
temp = temp.next;
}
temp.next = heroNode;
}
/**
* 显示链表,遍历
*/
public void list() {
//先判断链表是否为空
if (isEmpty()) {
throw new RuntimeException("链表是为空");
}
//临时节点,辅助遍历
HeroNode temp = head.next;
while (temp != null) {
//输出
System.out.println(temp);
//将temp后移
temp = temp.next;
}
}
/**
* 判断链表是否为空
* @return true/false
*/
public boolean isEmpty() {
return head.next == null;
}
}
//定义一个HeroNode,每个HeroNode对象就是一个节点
class HeroNode {
public int no;
public String name;
public String nickName;
public HeroNode next; //指向下一个节点
//构造方法
public HeroNode(int no, String name, String nickName) {
this.no = no;
this.name = name;
this.nickName = nickName;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickName='" + nickName + '\'' +
'}';
}
}
按顺序添加
需要按照编号的顺序添加
- 首先找到新节点添加的位置,通过辅助变量查找
- 新节点.next=temp.next
- 将temp.next = 新节点
修改之前的add方法
/**
* 按顺序添加节点到单链表,no不能重复
* 1.找到当前链表的最后节点
* 2.将最后的节点的next指向新的节点
*/
public void add(HeroNode heroNode) {
//因为head节点不能动,因此需要一个辅助遍历temp
HeroNode temp = head;
boolean flag = false; //节点编号已存在的标志
//遍历链表,找到最后
while (temp.next != null) {
//如果找到新元素应该插入的位置,temp的下一个节点比heroNode大的就是要找的位置
if (temp.next.no > heroNode.no) {
break;
} else if (temp.next.no == heroNode.no) {//要添加的编号已经存在
flag = true;
break;
}
//如果没有到最后,就将temp后移
temp = temp.next;
}
if (flag) {
System.out.printf("准备插入的节点编号 %d 已经存在,不能再插入\n", heroNode.no);
} else {
//将heroNode的next指向temp的next
heroNode.next = temp.next;
//将temp的next指向heroNode
temp.next = heroNode;
}
}
修改节点
-
通过遍历找到该节点
-
修改节点` temp.name = newHeroNode.name;`
temp.nickName = newHeroNode.nickName;
/**
* 修改节点的信息,根据no编号来修改,即no编号不能改
* 1. 根据newHeroNode的no
* 2.
*
* @param newHeroNode newHeroNode
*/
public void update(HeroNode newHeroNode) {
if (isEmpty()) {
System.out.println("链表为空!");
return;
}
HeroNode temp = head.next;
while (temp != null) {
//根据no找到需要修改的节点
if (temp.no == newHeroNode.no) {
temp.name = newHeroNode.name;
temp.nickName = newHeroNode.nickName;
return;
}
temp = temp.next;
}
System.out.printf("没有找到编号为 %d 的节点\n", newHeroNode.no);
}
删除节点
- 先找到要删除的节点的前一个节点
- temp.next = temp.next.next
- 被删除的节点没有其他引用,会被垃圾回收机制回收
/**
* 根据no删除节点
* @param no
*/
public void del(int no) {
HeroNode temp = this.head;
while (temp.next != null) {
if (temp.next.no == no) {
temp.next = temp.next.next;
return;
}
temp = temp.next;
}
System.out.printf("要删除的%d节点不存在\n", no);
}
链表反转
- 保存当前转移的节点
- 原链表跳过当前转移的节点
- 当前转移的节点的next指向反转头节点的next
- 反转头节点的next指向当前转移的节点的next
- 将反转头节点赋值给原节点的头节点
public static void reverse(SingleLinkedList linkedList) {
if (linkedList.head == null) {
System.out.println("链表为空");
return;
}
HeroNode reverseHead = new HeroNode(0, "", "");//反转后的头节点
HeroNode head = linkedList.head;
while (head.next != null) {
//保存当前转移的节点
HeroNode current = head.next;
//原链表跳过当前转移的节点
head.next = head.next.next;
//以下两步不可调换顺序
//当前转移的节点的next指向反转头节点的next
current.next = reverseHead.next;
//反转头节点的next指向当前转移的节点的next
reverseHead.next = current;
}
//将反转头节点赋值给原节点的头节点
linkedList.head = reverseHead;
}
逆序打印
/**
* 逆序打印
*
* @param linkedList
*/
public static void rePrint(SingleLinkedList linkedList) {
if (linkedList.head.next == null) {
System.out.println("链表为空");
return;
}
Stack<HeroNode> heroNodes = new Stack<>();
HeroNode temp = linkedList.head.next;
while (temp != null) {
heroNodes.push(temp);
temp = temp.next;
}
System.out.println("逆序打印");
while (!heroNodes.empty()) {
System.out.println(heroNodes.pop());
}
}