使用Java模拟链表
单向链表
单向链表的特点
单向链表也是有序的链表,但是这个有序指的是元素在逻辑上的顺序,而不是在物理内存中分布的顺序,物理内存中的顺序不一定是连续的.
值得一提的是链表以节点的方式来存储数据,每一个节点中既维护着存储的数据信息,有维护着下一个节点的地址信息.
单向链表还可以分为有头结点的单向链表和没有头节点的单向链表.
单向链表的各个节点不一定是连续分布的,只是在逻辑上是连续的.
实现思路
1 这个链表中需要有一个head属性来指向链表的第一个元素,这个head也是一个Node节点,只不过不让其存储任何数据,只是用来记录下一个节点的地址信息.
2 每一个Node节点都需要维护各自的数据和下一个节点的地址信息.
3 如果某一个节点的next属性为空,则该节点就是链表中的最后一个节点.
实现代码
package element.io.data.structure.d3;
import lombok.Data;
import java.io.Serializable;
import java.util.Comparator;
/**
* @author 张晓华
* @date 2022-12-17
* 模拟带有头节点的单向链表
* head 头结点,不存储数据,只保存实际的头节点元素的地址信息
*/
public class SingleLinkedList<T> {
// 头节点,并不会用来实际存储数据,只是保存链表头结点的地址信息
private Node head;
public SingleLinkedList() {
this.head = new Node(null);
}
public void add(final T data) {
Node temp = head;
while (true) {
// 找到最后一个节点
if (temp.next == null) {
break;
}
// 控制指针向后移动,指针需要不断地移动才能够实现遍历
temp = temp.next;
}
// 让最后一个节点的next指向新添加的节点
temp.next = new Node(data);
}
public void addInOrder(final T data, Comparator<T> comparator) {
Node temp = head;
boolean flag = false;
// 找到可以满足条件的节点的前一个节点位置,因为要执行的插入操作,所以需要在两个节点之间操作
while (true) {
if (temp.next == null) {
flag = true;
break;
} else if (comparator.compare(temp.next.data, data) > 0) {
flag = true;
break;
} else if (comparator.compare(temp.next.data, data) == 0) {
break;
} else {
temp = temp.next;
}
}
if (flag) {
// 找到了要插入的节点的位置后,
// 创建一个新的节点,让前一个节点的next指向新节点,新节点的next指向前一个节点的next
Node newNode = new Node(data);
newNode.next = temp.next;
temp.next = newNode;
}
}
public void list() {
Node temp = head;
if (temp.next == null) {
return;
}
while (temp.next != null) {
// 这里之所以要next.data是因为head节点并没有存储数据
System.out.println(temp.next.data);
// 指针后移
temp = temp.next;
}
}
public T getHead() {
if (head.next == null) {
return null;
}
return head.next.data;
}
public void update(final T data, Comparator<T> comparator) {
Node temp = head;
if (temp.next == null) {
return;
}
while (true) {
// 直接找到匹配的节点的前一个节点,然后创建一个新的节点,让新的节点的next指向原来数据的next指向的节点,
// 直接替换掉原来的节点,这个条件必须前置,否则就会出现空指针异常
if (temp.next == null) {
break;
} else if (comparator.compare(temp.next.data, data) == 0) {
Node newNode = new Node(data);
//temp.next.next 它为空并没有什么实质性的影响
newNode.next = temp.next.next;
temp.next = newNode;
break;
} else {
temp = temp.next;
}
}
}
public void remove(final T data, Comparator<T> comparator) {
Node temp = head;
if (head.next == null) {
return;
}
while (true) {
// 同样是需要找到删除的节点的前一个节点,直接将其前一个节点的next指向要删除指点的next指向的节点,
// 当前要删除的数据没有被任何变量所引用时JVM会就自动地回收内存空间
if (comparator.compare(temp.next.data, data) == 0) {
temp.next = temp.next.next;
break;
} else if (temp.next == null) {
break;
} else {
temp = temp.next;
}
}
}
public boolean isLast(Node node) {
return head.next == null;
}
@Data
public class Node implements Serializable {
private Node next;
private T data;
public Node(T data) {
this.data = data;
}
}
}
测试demo
public static void demo() {
SingleLinkedList<Hero> linkedList = new SingleLinkedList<>();
Hero song = Hero.builder().seq(1).name("宋江").nickName("及时雨").build();
linkedList.add(song);
Hero lin = Hero.builder().seq(2).name("林冲").nickName("豹子头").build();
linkedList.add(lin);
Hero wu = Hero.builder().seq(3).name("吴用").nickName("智多星").build();
linkedList.add(wu);
linkedList.list();
Hero wu2 = Hero.builder().seq(3).name("吴用").nickName("智多星").build();
wu2.setNickName("军师");
wu2.setSeq(10);
linkedList.update(wu2, Comparator.comparing(Hero::getSeq));
System.out.println("修改后的链表");
linkedList.list();
System.out.println("删除数据后的链表");
linkedList.remove(wu,Comparator.comparing(Hero::getSeq));
linkedList.list();
}
public static void demo2() {
SingleLinkedList<Hero> linkedList = new SingleLinkedList<>();
Hero song = Hero.builder().seq(1).name("宋江").nickName("及时雨").build();
linkedList.addInOrder(song,Comparator.comparing(Hero::getSeq));
Hero wu = Hero.builder().seq(3).name("吴用").nickName("智多星").build();
linkedList.addInOrder(wu,Comparator.comparing(Hero::getSeq));
Hero lin = Hero.builder().seq(3).name("林冲").nickName("豹子头").build();
linkedList.addInOrder(lin,Comparator.comparing(Hero::getSeq));
linkedList.list();
}
运行结果: