基本介绍
- 链表是有序的列表,但是各个节点在内存中不一定是连续存储;
- 链表是以节点的方式来存储,是链式存储;
- 每个节点包含 data 域和 next 域(指向下一个节点);
- 链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定;
- 单链表(带头结点) 逻辑结构示意图如下:
单链表
实现思路
实现逻辑
链表结点结构
public class SingleNode {
// 结点名称
private String name;
// 结点别名
private String alias;
// 结点排名
private int rank;
// 下一个结点
private SingleNode next;
public SingleNode(String name, String alias, int rank) {
this.name = name;
this.alias = alias;
this.rank = rank;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SingleNode singleNode = (SingleNode) o;
return rank == singleNode.rank &&
Objects.equals(name, singleNode.name) &&
Objects.equals(alias, singleNode.alias);
}
...get/set
}
创建空的头节点
private SingleNode head = new SingleNode("", "", 0);
添加结点
public void addNode(SingleNode singleNode) {
// 默认在头结点后增加结点
SingleNode temp = head;
// 循环遍历下一个结点,直到下一个结点为空,可以把新增结点插入到链表最末尾
while (true) {
if (null == temp.getNext()) {
break;
}
temp = temp.getNext();
}
// 链表最后一个结点的下一个结点指向新增结点
temp.setNext(singleNode);
}
将结点添加到指定位置(按正序插入)
public void addNodeByOrder(SingleNode singleNode) {
SingleNode temp = head;
boolean flag = false;
while (true) {
// 说明temp已经在链表结尾
if (temp.getNext() == null) {
flag = true;
break;
}
// 说明位置找到了,就在temp后面插入
if (temp.getNext().getRank() > singleNode.getRank()) {
flag = true;
break;
}
// 说明希望添加的node的编号已经存在
if (temp.getNext().getRank() == singleNode.getRank()) {
flag = false;
break;
}
// 后移,遍历链表
temp = temp.getNext();
}
if (flag) {
// 插入到链表中,temp的后面
singleNode.setNext(temp.getNext());
temp.setNext(singleNode);
}
}
更新结点
public void update(SingleNode newSingleNode) {
// 默认在头结点开始遍历,找到满足更新的结点
SingleNode temp = head;
// 设置临时变量,便于遍历链表
boolean flag = false;
while (true) {
// 只要排名值一致,就可以认为找到可以更新的结点,退出循环
if (temp.getRank() == newSingleNode.getRank()) {
flag = true;
break;
}
// 一直寻找下一个结点,直到该结点的下一个结点为空,说明已到链表末尾,退出循环
if (temp.getNext() == null) {
break;
}
temp = temp.getNext();
}
// 如果找到可以更新结点,就更新名称和别名
if (flag) {
temp.setName(newSingleNode.getName());
temp.setAlias(newSingleNode.getAlias());
}
}
删除结点
public void delete(SingleNode singleNode) {
// 默认在头结点开始遍历,找到满足更新的结点
SingleNode temp = head;
// 设置临时变量,便于遍历链表
boolean flag = false;
while (true) {
// 判断当前结点的下一个结点的排名是否满足,并退出循环
if (temp.getNext().getRank() == singleNode.getRank()) {
flag = true;
break;
}
// 一直寻找下一个结点,直到该结点的下一个结点为空,说明已到链表末尾,退出循环
if (temp.getNext() == null) {
break;
}
temp = temp.getNext();
}
// 如果找到可以删除的结点,就将当前结点的下一个结点 指向当前结点的下一个的下一个结点
if (flag) {
temp.setNext(temp.getNext().getNext());
}
}
遍历链表
public void show() {
SingleNode temp = head;
while (true) {
if (temp.getNext() == null) {
System.out.print(temp.getNext());
break;
}
temp = temp.getNext();
System.out.print(temp + " --> ");
}
System.out.println();
}
双向链表
实现思路
分析 双向链表的遍历,添加,修改,删除的操作思路 --->
-
遍历方式和单链表一样,可以向前,也可以向后查找;
-
添加 (默认添加到双向链表的最后)
2.1. 先找到双向链表的最后这个节点;
2.2. temp.next = newHeroNode;
2.3. newHeroNode.pre = temp;
-
修改思路和单向链表的一样
-
删除
4.1. 因为是双向链表,因此可以实现自我删除某个节点;
4.2. 直接找到要删除的这个节点,比如temp;
4.3. temp.pre.next = temp.next;
4.4. temp.next.pre = temp.pre;
实现逻辑
链表结点结构
public class DoubleNode {
private String name;
private String alias;
private int rank;
// 指向下一个节点, 默认为null
private DoubleNode next;
// 指向上一个节点, 默认为null
private DoubleNode pre;
public DoubleNode(String name, String alias, int rank) {
this.name = name;
this.alias = alias;
this.rank = rank;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DoubleNode doubleNode = (DoubleNode) o;
return rank == doubleNode.rank &&
Objects.equals(name, doubleNode.name) &&
Objects.equals(alias, doubleNode.alias);
}
@Override
public int hashCode() {
return Objects.hash(name, alias, rank);
}
}
创建空的头节点
private DoubleNode head = new DoubleNode("", "", 0);
添加结点
public void addNode(DoubleNode doubleNode) {
DoubleNode temp = head;
// 遍历链表,找到最后位置
while (true) {
// 为空,说明已经到链表结尾
if (Objects.isNull(temp)) {
break;
}
// 如果没有找到最后,就将temp后移
temp = temp.getNext();
}
// 当退出while循环时,temp就指向了链表的最后
// 形成一个双向链表
temp.setNext(doubleNode);
doubleNode.setPre(temp);
}
将结点添加到指定位置(按正序插入)
public void addNodeByOrder(DoubleNode doubleNode) {
DoubleNode temp = head;
boolean flag = false;
// 遍历链表,找到最后位置
while (true) {
// 为空,说明已经到链表结尾
if (Objects.isNull(temp.getNext())) {
flag = true;
break;
}
if (temp.getNext().getRank() > doubleNode.getRank()) {
flag = true;
break;
}
if (temp.getNext().getRank() == doubleNode.getRank()) {
flag = false;
break;
}
// 如果没有找到最后,就将temp后移
temp = temp.getNext();
}
// 当退出while循环时,temp就指向了链表的最后
// 形成一个双向链表
if (flag) {
doubleNode.setNext(temp.getNext());
temp.setNext(doubleNode);
doubleNode.setPre(temp);
}
}
更新结点
public void updateNode(DoubleNode doubleNode) {
DoubleNode temp = head;
// 表示是否找到该节点
boolean flag = false;
while (true) {
if (temp.getNext() == null) {
// 已经遍历完链表
break;
}
if (temp.getRank() == doubleNode.getRank()) {
// 找到匹配的结点
flag = true;
break;
}
temp = temp.getNext();
}
if (flag) {
temp.setName(doubleNode.getName());
temp.setAlias(doubleNode.getAlias());
}
}
删除结点
public void delNode(DoubleNode doubleNode) {
DoubleNode temp = head;
// 标志是否找到待删除节点的
boolean flag = false;
while (true) {
if (temp.getNext() == null) {
// 已经遍历完链表
break;
}
if (temp.getRank() == doubleNode.getRank()) {
// 找到匹配的结点
flag = true;
break;
}
temp = temp.getNext();
}
if (flag) {
temp.getPre().setNext(temp.getNext());
//如果是最后一个节点,就不需要执行下面这句话,否则出现空指针
if (Objects.nonNull(temp.getNext())) {
temp.getNext().setPre(temp.getPre());
}
}
}
遍历链表
public void list() {
DoubleNode temp = head;
if (Objects.isNull(temp)) {
return;
}
while (true) {
// 判断是否到链表最后
if (Objects.isNull(temp)) {
break;
}
// 输出节点的信息
System.out.println(temp);
// 将temp后移,一定小心
temp = temp.getNext();
}
}
环形单向链表
约瑟夫问题
设编号为 1,2,… n 的n个人围坐一圈,约定编号为 k(1<=k<=n) 的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
实现思路
用一个不带头结点的循环链表来处理Josephu问题:先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除,算法结束。
构建一个单向的环形链表
- 先创建第一个节点, 让 first 指向该节点,并形成环形;
- 后面当我们每创建一个新的节点,就把该节点,加入到已有的环形链表中即可。
遍历环形链表
- 先让一个辅助指针(变量)curBoy,指向 firs节点;
- 然后通过一个 while 循环遍历该环形链表即可,当 curBoy.next == first 结束。
实现逻辑
创建Boy节点
public class Boy {
private int id;
private Boy next;
public Boy(int id) {
this.id = id;
}
...get/set
}
创建首节点
private Boy first = null;//辅助节点
新增节点,构成环形链表
public void addBoy(int num) {
Boy curBoy = null;
for (int i = 0; i < num; i++) {
Boy boy = new Boy(i);
if (i == 0) {
first = boy;
// 构成环
first.setNext(first);
// 指向第一个节点
curBoy = first;
} else {
curBoy.setNext(boy);
boy.setNext(first);
curBoy = boy;
}
}
}
遍历环形链表
public void showBoy() {
if (first == null) {
System.out.println("没有节点");
return;
}
Boy curBoy = first;
while (true) {
System.out.println("节点编号为:" + curBoy.getId());
if (curBoy.getNext() == first) {
break;
}
curBoy = curBoy.getNext();
}
}
计算节点出圈顺序
public void countBoy(int startNo, int countNum) {
// 创建一个辅助指针(变量)helper, 事先应该指向环形链表的最后这个节点
Boy helper = first;
// 定位到最末尾节点
while (true) {
if (helper.getNext() == first) {
break;
}
helper = helper.getNext();
}
// 找到当前轮次第一个报数的节点
for (int i = 0; i < startNo - 1; i++) {
first = first.getNext();
helper = helper.getNext();
}
while (true) {
if (helper == first) {
break;
}
for (int i = 0; i < countNum - 1; i++) {
first = first.getNext();
helper = helper.getNext();
}
// 找到出圈节点
System.out.println(String.format("小孩:%s出圈", first.getId()));
// 以出圈节点的下一个节点重新开始计数
first = first.getNext();
// 辅助指针(变量)helper指向下一个开始节点
helper.setNext(first);
}
System.out.println("最后一个小孩:" + first.getId());
}