这篇主要回顾下,单链表和双链表增删查改的实现,并对约瑟夫问题做了简单思路分析,链表操作一个基本点就是先连结再拆除。
单链表和双链表中用例都是将一个学生作为节点,包含姓名和分数两项属性,进行相关功能练习,完整代码可访问。https://gitee.com/flying-morning/data-structure/tree/master/src/com/datastructure/list
class StudentNode {
public String name; //姓名
public int score; //分数
public StudentNode next; //指向后一个节点
public StudentNode pre; //指向前一个节点,双链表的时候才有
public StudentNode(String name,int score) {
this.name = name;
this.score = score;
next = null;
pre = null;
}
@Override
public String toString() {
return "name: " + this.name + ", score: " + this.score;
}
}
1 单链表
head 节点:不存放具体数据,作用就是表示单链表头
1. 1 增加节点
在末尾插入节点其实没什么好说的,从头节点一直开始遍历,直到找到最后一个节点,然后将尾节点 next 指针指向新节点。
尾节点:temp.next == null
public void add(StudentNode student) {
StudentNode temp = head;
//next == null 就是尾节点
while (temp.next != null) {
temp = temp.next;
}
temp.next = student;
}
如果是在中间插入节点,比如说按照分数从高到低的顺序。因为单链表只有指向后一个节点的指针,所以我们需要找到插入位置的前一个节点,这样才能进行链表连接操作。
比如我们要插入的节点 a3 分数高于 a2,所以要插到 a2 之前,那么 a1 这个节点就是我们需要找到的,有如下两步操作:
- a3.next = a1.next,将新节点 a3 的 next 指向 a2,而 a2 = a1.next
- a1.next = a3,将 a1 的 next 指向 a3.
这两步顺序不能反,否则会丢失 a2 节点的访问
public void addByScore(StudentNode studentNode) {
StudentNode temp = head;
while (temp.next != null) {
//目标节点的分数大于后一个节点
if(temp.next.score < studentNode.score) {
break;
}
temp = temp.next;
}
//节点插入,先将目标节点的next指向temp.next
//再将 temp.next 指向目标节点
studentNode.next = temp.next;
temp.next = studentNode;
}
1. 2 删除节点
删除节点大概三步:
- delNode = a1.next,暂存 a3,不暂存的话,后面就找不到这个节点了。
- a1.next = a1.next.next,让 a1 的 next 指向 a2
- delNode.next = null,将 a3 的 next 置为 null,完全断开
public void del(String name) {
StudentNode temp = head;
while (temp.next != null) {
if (temp.next.name.equals(name)) {
break;
}
temp = temp.next;
}
if (temp.next != null) {
StudentNode delNode = temp.next;
temp.next = temp.next.next;
delNode.next = null;
System.out.println(name + "已被删除");
}else {
System.out.println(name + "不在链表中");
}
}
1.3 修改和查找
这两个其实没什么好说的,同增加和删除不同,这里因为不需处理跟前后节点的关系,所以只需要当前节点就行,我们在遍历的时候就让 StudentNode temp = head.next;
,不再记录前置节点的信息了。
2 双链表
2.1 增加节点
在末尾增加,和单链表类似,只是多了将新增节点的 pre 指向之前的尾节点的操作。
public void add(StudentNode studentNode) {
StudentNode temp = head;
while (temp.next != null) {
temp = temp.next;
}
temp.next = studentNode;
studentNode.pre = temp;
}
按分数插在中间,比单链表要简单,步骤如下
- a3.next = a2; a2.pre = a2.pre,这一步就处理了好了 a3 节点 pre 和 next 的值
- a2.pre.next = a3,将 a1 的 next 指向 a3;a2.pre = a3,将 a2 的 pre 指向 a3
代码中的 temp 就想当于 a2
public void addByScore(StudentNode studentNode) {
StudentNode temp = head.next;
while (temp != null) {
// 目标节点的分数大于当前节点
if(temp.score < studentNode.score) {
break;
}
temp = temp.next;
}
//节点插入
studentNode.next = temp;
studentNode.pre = temp.pre;
temp.pre.next = studentNode;
temp.pre = studentNode;
}
2.2 删除节点
删除 a3 节点,主要步骤如下:
- 先连结:a3.pre.next = a3.next; a3.next.pre = a3.pre,借助 a3 的信息将 a1、a2关联
- 断开a3:a3.next = null; a3.pre = null
public void del(String name) {
StudentNode temp = head.next;
while (temp != null) {
if (temp.name.equals(name)) {
break;
}
temp = temp.next;
}
if(temp != null) {
temp.pre.next = temp.next;
if (temp.next != null) {
temp.next.pre = temp.pre;
temp.next = null;
}
temp.pre = null;
System.out.println(name + "已被删除");
}else {
System.out.println(name + "不在链表中");
}
}
3 单向环形链表
环形链表的一个特点就是,尾节点的 next 指向第一个节点,形成一个环状的结构。我们定义第一个添加的节点为 first ,且只有一个节点时 first.next = first
成立。
这里主要围绕约瑟夫问题展开,每个节点只有 num 这一个属性,表示序号,方便问题求解。
class NumNode {
int num; //节点序号
NumNode next;
NumNode(int num) {
this.num = num;
this.next = null;
}
}
节点的操作在前面应该已经很熟悉了,这里主要讲讲约瑟夫问题怎么解。
对于如下的环形链表,first 指向 a1,startNum=3 表示从 3 号节点开始报数,countNum=3 表示数到 3 时停止,且该节点被删除。
下面是一个求解思路:
(1)该问题需要不断从链表中删除节点,对于单向链表,删除节点是需要前一个节点信息的,因此增加一个辅助节点 last ,始终表示 first 的前一个位置,这样在做删除 first 节点操作的时候,执行 last.next = first.next 就可以了。
private NumNode lastNode(NumNode first) {
NumNode curNode = first; //辅助遍历
while(curNode.next != first) { //next == first 时就是目标节点
curNode = curNode.next;
}
return curNode;
}
(2)根据 startNum 移动 first 到开始位置,last 也跟着移动。
//将first移动到开始报数的位置
while (startNum > 1) { //
lastNode = lastNode.next;
first = first.next;
--startNum;
}
(3)第二步完成后,从 first 所在的位置开始报数,数countNum 下,这一过程中向后移动 first、last 两个节点,然后对 first 节点做删除操作,并找到新的 first 节点。
while (first != lastNode) { //只有一个节点时退出
int mCountNum = countNum;
while (mCountNum > 1) { //开始报数,数 countNum下
lastNode = lastNode.next;
first = first.next;
--mCountNum;
}
//此时要删除的节点就是 first
System.out.print("移除节点:" + first.num);
lastNode.next = first.next;
first = first.next;
System.out.println(", 当前链表为:");
show();
}
(3)重复第(3)步中的过程,直到只剩下一个节点。
最后附上全部代码吧。
package com.datastructure.list;
public class JosepFu {
public static void main(String[] args) {
CycleLinkedList cycleLinkedList = new CycleLinkedList(5);
cycleLinkedList.show();
cycleLinkedList.solveJospFu(3,3);
}
}
class CycleLinkedList {
NumNode first;
CycleLinkedList(int totalNum) {
if (totalNum < 1) {
System.out.println("totalNum < 1,创建失败");
return;
}
//定义首节点
first = new NumNode(1);
first.next = first;
//其他节点
NumNode curNode =first;
for (int i=2; i <= totalNum; ++i) {
NumNode node = new NumNode(i);
curNode.next = node;
node.next = first;
curNode = curNode.next;
}
}
public void show() {
if (first == null) {
System.out.println("环形链表为空");
}
NumNode curNode = first;
do {
System.out.println("节点编号: " + curNode.num);
curNode = curNode.next;
} while (curNode != first);
}
/**
*
* @param startNum 从第几个节点开始报数
* @param countNum 数几下 countNum
*/
public void solveJospFu(int startNum,int countNum) {
if (first == null) {
return;
}
NumNode lastNode = lastNode(first); //辅助节点删除操作
//将first移动到开始报数的位置
while (startNum > 1) { //
lastNode = lastNode.next;
first = first.next;
--startNum;
}
//
while (first != lastNode) { //只有一个节点时退出
int mCountNum = countNum;
while (mCountNum > 1) { //开始报数,数 countNum下
lastNode = lastNode.next;
first = first.next;
--mCountNum;
}
//此时要删除的节点就是 first
System.out.print("移除节点:" + first.num);
lastNode.next = first.next;
first = first.next;
System.out.println(", 当前链表为:");
show();
}
}
/**
* 找到 first 节点的前一个节点
* @param first 第一个节点
* @return first 节点的前一个节点
*/
private NumNode lastNode(NumNode first) {
NumNode curNode = first; //辅助遍历
while(curNode.next != first) { //next == first 时就是目标节点
curNode = curNode.next;
}
return curNode;
}
}
class NumNode {
int num; //节点序号
NumNode next;
NumNode(int num) {
this.num = num;
this.next = null;
}
}