文章借鉴于【尚硅谷】数据结构与算法(Java数据结构与算法),在内容方面会做更改,但是核心依然是他们的学习视频。在这里声明。
1. 线性结构和非线性结构
1.1 线性结构
数据结构包括两大部分,一个叫 线性结构,一个叫 非线性结构
-
线性结构作为最常用的数据结构,其特点是数据元素之间存在一对一的线性关系。 比如数组, var[0] = 20; 存在一对一关系。
-
线性结构有两种不同的存储结构,即顺序存储结构和链式存储结构。顺序存储的线性表称为顺序表,顺序表中的元素是连续的。该元素指的是内存地址。
-
链式存储结构的线性表称为链表,链表中存储的元素不一定是连续的, 元素节点中存放数据元素以及相邻元素的地址信息。
-
线性结构常见的有:数组,队列,链表,栈
1.2 非线性结构
- 非线性结构包括:二维数组,多维数组,广义表,树结构,图结构
2. 稀疏数组
定义: 当一个数组中,大部分元素都为0,或者大部分元素都为同一个数字的时候,可以使用稀疏数组来保存该数组。
2.1 稀疏数组的介绍
稀疏数组的处理方式是:
- 记录一个数组有多少行,多少列,有多少个不同的值。
- 把具有不同元素的行列记录在一个小规模的数组上(这个小规模的数组就是稀疏数组),从而缩小程序的规模。
左侧是原有的二维数组,右侧为压缩后的稀疏数组。在稀疏数组的第[0]索引位,第一个元素代表了这个原有的二维数组有几行,第二个代表了这个原有的数组有几列,第三个代表了除去非0以外的元素,还存在几个其他不同的元素。从第[1]号索引到第[8]号索引,他们分别记录的是该不同元素出现在二维数组上的位置所在。比如 元素22 出现在 索引为0 - 3 的地方,那么对应的具体坐标就为 1 - 4 也就是第一行第四个元素。那么,原有的 6行 7列的数组,就会变成 现在稀疏数组的 九行 三列,对应的元素分别是 6 * 7 = 42(压缩前) 和 3 * 9 = 27(压缩后),大大减少了使用空间。
2.2 稀疏数组的实操
那在具有上面的了解后,就可以来具体的学习一下怎么样把一个二维数组,转换为稀疏数组的思路。
- 遍历原始的二维数组,得到有效元素的个数 sum。
- 根据sum就可以创建稀疏数组sparseArray其中稀疏数组的大小为
int[] [] sparseArray = new int[sum + 1][3] - 将二维数组的有效数据,存入到稀疏数组中。
那么存入后,就需要还原二维数组。下面是稀疏数组转二维数组的思路。
- 先读取稀疏数组的第一行,第一行里面有二维数组的行和列。根据第一行的数据,创建原始的二维数组。 int[][] array = new int[sparse[0][1]] [sparse[0][2]]
- 然后读取稀疏数组后几行的数据,并且赋给原始的二维数组。即可。
现在就可以来写具体的代码实操。新建一个类,类名自定义,我这里的类名叫做SparseArray。接下来我直接粘贴代码。具体内容看代码,代码都具有注释。
package com.data_structure.sparse_array;
import java.util.Arrays;
/**
* 数据结构
* 稀疏数组的来回转换
*/
public class SparseArray {
/***
* 对二维数组的转置输出,观看更客观
* @param arrays 对应的需要转置的二维数组
*/
public static void Transpose_2D_Array(int[][] arrays) {
for (int[] array : arrays) {
System.out.println(Arrays.toString(array));
}
}
/**
* 遍历二维数组,得到非0元素的个数
* @param arrays 传入的二维数组
* @return 返回非0的个数
*/
public static Integer Get_Oon_Empty_Element(int[][] arrays) {
int sum = 0;
for (int[] array : arrays) {
for (int i : array) {
if (i != 0) {
sum ++;
}
}
}
return sum;
}
/**
* 稀疏数组转换二维数组
* @param sparseArray 传入稀疏数组
* @return 转换成功的二维数组
*/
public static int[][] SparseArrayTo2DArray(int[][] sparseArray) {
// 根据稀疏数组,还原对应二维数组的大小,当然,前提是判断稀疏数组是否可用
if (sparseArray[0][0] != 0 && sparseArray[0][1] != 0) {
// 创建一个新的二维数组
int[][] new2DArray = new int[sparseArray[0][0]][sparseArray[0][1]];
// 此时只需要循环遍历其他的值即可
for (int i = 1; i <= sparseArray[0][2]; i++) {
new2DArray[sparseArray[i][0]][sparseArray[i][1]] = sparseArray[i][2];
}
return new2DArray;
}
return null;
}
/**
* 二维数组转换为稀疏数组
*/
public static void Convert_2D_Array_To_Sparse_Array() {
// 创建一个原始的11 * 11 的 二维数组模拟五子棋 0 表示空值, 1表示黑子,2表示白子
int[][] arrays = new int[11][11];
// 初始化棋盘 假设下了两个棋子,第二行第三个(黑色), 第三行第四个(白色)
arrays[1][2] = 1;
arrays[2][3] = 2;
System.out.println("原始二维数组: ");
// 对二维数组进行转置输出
Transpose_2D_Array(arrays);
// 得到非0的元素个数
Integer integer = Get_Oon_Empty_Element(arrays);
// 创建对应的稀疏数组 他的行为 对应有元素的个数 + 1 列为固定3
int[][] sparseArray = new int[integer+1][3];
//给稀疏数组赋值
sparseArray[0][0] = 11;
sparseArray[0][1] = 11;
sparseArray[0][2] = integer;
// 遍历原始数组,然后把对应不为0的元素,存储到稀疏数组中。
int count = 0;
for (int i = 0; i < 11; i++) {
for (int j = 0; j < 11; j++) {
// 如果不为0 代表有数据
if (arrays[i][j] != 0) {
count ++;
sparseArray[count][0] = i;
sparseArray[count][1] = j;
sparseArray[count][2] = arrays[i][j];
}
}
}
// 输出稀疏数组的形式
System.out.println("稀疏数组: ");
Transpose_2D_Array(sparseArray);
// 稀疏数组转换为 二维数组。以及知道系数组的数据位置,可以直接通过稀疏数组转换二维数组。
int[][] newArrays = SparseArrayTo2DArray(sparseArray);
System.out.println("由稀疏数组转换为二维数组: ");
assert newArrays != null;
Transpose_2D_Array(newArrays);
}
public static void main(String[] args) {
Convert_2D_Array_To_Sparse_Array();
}
}
上述代码是可以直接拷贝运行的,但是注意包结构导入,需要更改。
3.队列
特性: 队列最大的特点就是 先进先出;
- 队列是一个有序列表,可以用数组或者链表来实现。
- 遵循先入先出的原则。即先存入的数据,先取出,后存入的数据,后取出。
3.1 队列的代码实现
package com.data_structure.queue;
/**
* 队列,且是用数组模拟队列
*/
public class ArrayQueueDemo {
public static void main(String[] args) {
ArrayQueue arrayQueue = new ArrayQueue(5);
arrayQueue.addQueue(5);
arrayQueue.addQueue(1);
arrayQueue.addQueue(7);
arrayQueue.addQueue(9);
arrayQueue.addQueue(4);
arrayQueue.addQueue(4);
System.out.println();
arrayQueue.show();
System.out.println();
System.out.println(arrayQueue.getQueue());
System.out.println(arrayQueue.getQueue());
System.out.println(arrayQueue.getQueue());
System.out.println(arrayQueue.getQueue());
arrayQueue.show();
}
}
// 新建一个数组模拟队列,编写一个ArrayQueue
class ArrayQueue {
// 表示数组的最大容量
private final int maxSize;
// 指向队列头
private int front;
// 指向队列尾
private int rear;
// 队列数组
private final int[] array;
/**
* 构造器,初始化数据
* @param arrSize 队列的大小
*/
public ArrayQueue(int arrSize) {
this.maxSize = arrSize; // 获取到队列的最大容量
this.array = new int[maxSize]; // 根据最大容量创建数组队列
this.front = -1; // 指针头,指向队列头部的前一个位置
this.rear = -1; // 指针尾,指向队列尾部的最后一个位置
}
/**
* 判断队列是否为空,只需要检查 指针头和指针尾是否相撞就可以了
* @return 空为true 反之false
*/
public boolean queueIsEmpty() {
return this.front == this.rear;
}
/**
* 判断队列是否已满,只需要检查队列尾部指针是否和最大容量 -1(下标) 相撞
* @return 满了返回true ,反之反之
*/
public boolean queueIsFull() {
return this.rear == this.maxSize - 1;
}
/**
* 添加队列
* @param num 向队列里添加的值
*/
public void addQueue(int num) {
// 判断队列是否已满
if (!queueIsFull()) {
this.rear ++;
array[rear] = num;
} else {
System.out.println("队列已满,无法添加数值。");
}
}
/**
* 获取队列元素
* @return 队列元素
*/
public int getQueue() {
// 判断队列是否为空
if (!queueIsEmpty()) {
front++;
return array[front];
}
throw new RuntimeException("队列为空。");
}
public void show() {
for (int i = this.front + 1; i <= this.rear; i++) {
System.out.println("数据: " + this.array[i]);
}
}
}
3.2 环形队列
如果按照上面的方式来模拟队列,就会出现用过一次的位置不能第二次去使用。没法达到复用效果。
我们希望取出的空间还可以重复的去使用
对比上面的基本队列,环形队列实现的基本思路做出了调整
- front变量的含义做出调整,front调整为指向队列的第一个元素,也就是说,array[front]时,获取到的是队列的第一个元素。front的初始值为 0;不再是 -1。
- rear变量的含义做出调整,rear调整为指向队列最后一个元素 + 1 的位置。因为希望空留出一个空间作为约定。rear的初始值也 = 0。
- 当队列满的情况下,原先的条件是, rear == maxSize - 1; 但是现在 front和 rear的条件做了调整,变为 (rear + 1) % maxSize == front; 这就是满的条件。
- 当队列为空的条件没有发生变化,如果 rear == front 就是空。
- 当按照上面走的时候,队列中有效的数据个数,永远是(rear + maxSize - front) % maxSize
3.3 环形队列代码的实现
package com.data_structure.queue;
public class CircleArrayQueue {
public static void main(String[] args) {
CircleQueue circleQueue = new CircleQueue(5);
circleQueue.addQueue(5);
circleQueue.addQueue(8);
circleQueue.addQueue(1);
circleQueue.addQueue(2);
circleQueue.addQueue(9);
circleQueue.addQueue(4);
System.out.print("\n展示此时的队列内容: ");
circleQueue.show();
System.out.println();
System.out.println("取出的内容: ");
System.out.println(circleQueue.getQueue());
System.out.println(circleQueue.getQueue());
System.out.println(circleQueue.getQueue());
System.out.println(circleQueue.getQueue());
System.out.println("添加三个队列: ");
circleQueue.addQueue(9);
circleQueue.addQueue(1);
circleQueue.addQueue(5);
circleQueue.show();
}
}
class CircleQueue {
private final int maxSize;
private final int[] array;
private int front;
private int rear;
public CircleQueue(int maxSize) {
this.maxSize = maxSize;
this.array = new int[maxSize];
this.front = 0;
this.rear = 0;
}
public boolean isEmpty() {
return this.front == this.rear;
}
public boolean isFull() {
// 队列已满情况下。 最大值 % (尾指针 + 1) 如果 == front 就说明元素已经还差一个就满了 ,这一个是约定需要留下来的
return ((this.rear + 1) % this.maxSize) == this.front;
}
public void addQueue(int number) {
if (!isFull()) {
// 因为它本身就是在元素的后一个位置,所以我们不需要对他先进行指针后移操作,在添加完成数据后,进行指针后移,保证空留一个,
// 这里必须考虑取模,如果rear到顶层,切前面有空间,就需要让rear到前面去
this.array[this.rear] = number;
// 假设最大容量为5。此时rear为4.我们必须约定空留一个元素空间,
this.rear = (this.rear + 1) % maxSize;
} else {
System.out.println("队列已满");
}
}
public int getQueue() {
// 不为空的情况下才能取数据
if (!isEmpty()) {
// front是指向队列的第一个元素,我们需要先把front的值存储到一个临时变量中,把front后移,考虑取模,然后将临时的变量返回
int value = this.array[this.front];
this.front = (front + 1) % maxSize;
return value;
}
throw new RuntimeException("已经空掉了");
}
public void show() {
if (!isEmpty()) {
// 从front开始遍历, 遍历多少个元素。
for (int i = this.front; i < front + (rear + maxSize - front) % maxSize; i++) {
System.out.printf("数据下标arr[%d] = %d", i % maxSize, this.array[i % maxSize]);
}
}
}
}
4. 链表 (Linked List)
head 为头指针,就是第一个数据,然后data域为数据,next域,就是指向下一个节点的地方。
- 链表是以节点的方式来存储数据的。
- 每个节点包含data域,next域
- 链表的各个节点并不一定是连续的。
- 链表分带头节点的链表和没有头节点的链表,根据实际需求来确定
4.1 单链表的代码实现(不带排序)
package com.data_structure.linkedList;
public class SingleLinkedListDemo {
public static void main(String[] args) {
HeroNode heroNode1 = new HeroNode(1, "宋江", "及时雨");
HeroNode heroNode2 = new HeroNode(2, "李逵", "黑旋风");
HeroNode heroNode3 = new HeroNode(3, "林冲", "及时雨");
HeroNode heroNode4 = new HeroNode(4, "鲁智深", "及时雨");
SingleLinkedList singleLinkedList = new SingleLinkedList();
singleLinkedList.add(heroNode1);
singleLinkedList.add(heroNode2);
singleLinkedList.add(heroNode3);
singleLinkedList.list();
}
}
class SingleLinkedList {
// 初始化头节点,头节点不动,不存放具体数据
private final HeroNode herd = new HeroNode(0, "", "");
// 添加节点
public void add(HeroNode heroNode) {
// 添加的原理就是,当有新节点时,找到原有链表的最后一个节点,让最后一个节点指向该新节点。
// 但是头节点不动,就可以使用一个引用来代替头节点
HeroNode tempHead = this.herd;
// 遍历链表,找到最后一个元素
while (tempHead.next != null) {
// 如果没有找到最后一个元素,就将temp后移
tempHead = tempHead.next;
}
// 当退出while循环时,tempHead就指向了链表的最后。
tempHead.next = heroNode;
}
// 显示链表
public void list() {
// 先判断链表是否为空。
if (herd.next == null) {
System.out.println("链表为空: []");
return;
}
// 因为头节点不能动,因此我们需要一个辅助变量来遍历
// 因为他不为空,所以说明他至少有一个数据
HeroNode temp = this.herd.next;
while (true) {
// 判断是否到链表的最后
if (temp.next == null) {
// 最后一个元素也是有数据的
System.out.println(temp);
break;
}
// 输出
System.out.println(temp);
// 将temp后移
temp = temp.next;
}
}
}
/**
* 创建英雄节点
* 每一个HeroNode就是一个节点
*/
class HeroNode {
public int no; // 英雄编号
public String name; // 英雄名称
public String nickname; // 英雄绰号
public HeroNode next; // 指向下一个节点
public HeroNode(int heroNo, String hName, String nickName) {
this. no = heroNo;
this.name = hName;
this.nickname = nickName;
}
/**
* 为了显示方便 重写toString
*/
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickname='" + nickname + '\'' +
", next=" + next +
'}';
}
}
4.1.1 – 4.1的代码拆解
首先我们需要创建英雄节点,每一个英雄除去他们基本的属性以外,还需要有一个节点属性,来对应下一个元素所处于的位置,如果next节点为null,则说明他这个英雄是最后一个节点。
所以我们只需要先实现这个英雄节点功能即可。
// 英雄节点类
class HeroNode {
// 直接使用public 方便访问
public int noId; // 英雄的编号
public String name; // 英雄名称
public String nickname; // 英雄绰号
// 存放下一个英雄的位置
public HeroNode next; // next域
// 使用构造器来构造Hero英雄
public HeroNode(int noId, String name, String nickname) {
this.noId = noId;
this.name = name;
this.nickname = nickname;
}
// 重写toString方法,方便查询
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickname='" + nickname + '\'' +
", next=" + next +
'}';
}
}
到这一步为止。我们的英雄节点就已经创造完成,接下来,就可以去实现我们的链表逻辑。
class SingleLinkedList {
// 因为头部节点不动, 所以我们先把头部节点创建好,以后使用一个节点引用到头部节点即可。
private final HeroNode herd = new HeroNod(0, "", "");
// 用于添加节点元素, 需要传入一个节点
public void add(HeroNode heroNode) {
// 创建一个引用指向头部节点, 因为头节点的信息是不变的
HeroNode temp = this.herd;
// 遍历循环,判断是否指向的是最后一个节点
// 判断最后一个节点的逻辑是 节点的 next域 == null
while(temp.next != null) {
// 如果不是最后一个元素,就让节点后移
temp = temp.next;
}
// 此时跳出循环的时候,temp指向的就是最后一个节点元素
temp.next = heroNode;
}
// 查询链表内容
public void list() {
// 查询前先判断链表是否为空
// 只需要判断头节点的next是否为null就可以
if (this.herd.next == null) {
System.out.println("链表为空");
return;
}
// 头节点是不允许动的。
HeroNode temp = this.herd;
while(true) {
if(temp.next == null){
// 虽然为null,但是他也是最后一个元素
System.out.println(temp);
// 跳出循环
break;
}
System.out.println(temp);
// 输出当前节点元素后,还需要把这个节点指向下一个内容。
temp = temp.next;
}
}
}
至此完成。测试内容自己写
4.2 单链表的代码实现(附带排序,按照顺序插入节点)
思路实现:
- 首先找到新添加的节点位置。是通过辅助指针来找的, 通过遍历
- 新的节点的.next域 = temp的.next域
- 然后将temp.next域指向新的节点
4.2.1 代码的实现
题目,在添加英雄时,根据排名将英雄插入到指定位置,如果有这个排名,则添加失败,并给出提示。
代码在原有基础上进行添加,也就是在上述代码中,在SingleLinkedList.class类中,新增加一个方法,叫addByOrder(HeroNode heroNode); 里面要传递的参数是新增的英雄节点。
package com.data_structure.linkedList;
public class SingleLinkedListDemo {
public static void main(String[] args) {
HeroNode heroNode1 = new HeroNode(1, "宋江", "及时雨");
HeroNode heroNode2 = new HeroNode(2, "李逵", "黑旋风");
HeroNode heroNode3 = new HeroNode(3, "林冲", "及时雨");
HeroNode heroNode4 = new HeroNode(4, "鲁智深", "及时雨");
SingleLinkedList singleLinkedList = new SingleLinkedList();
singleLinkedList.addByOrder(heroNode3);
singleLinkedList.addByOrder(heroNode1);
singleLinkedList.addByOrder(heroNode2);
singleLinkedList.addByOrder(heroNode4);
singleLinkedList.list();
}
}
class SingleLinkedList {
// 初始化头节点,头节点不动,不存放具体数据
private final HeroNode head = new HeroNode(0, "", "");
// 添加节点
public void add(HeroNode heroNode) {
// 添加的原理就是,当有新节点时,找到原有链表的最后一个节点,让最后一个节点指向该新节点。
// 但是头节点不动,就可以使用一个引用来代替头节点
HeroNode tempHead = this.head;
// 遍历链表,找到最后一个元素
while (tempHead.next != null) {
// 如果没有找到最后一个元素,就将temp后移
tempHead = tempHead.next;
}
// 当退出while循环时,tempHead就指向了链表的最后。
tempHead.next = heroNode;
}
// 根据编号按升序排序来排序链表节点。 如: 1, 2, 3, 4, 5, 6, 7
public void addByOrder(HeroNode heroNode) {
// 因为头节点是固定的,不可以改动的,所以我们需要创建一个引用对象来引用头节点
HeroNode temp = this.head;
// 定义一个boolean的变量,用来检查元素是否重复。默认为false
boolean flag = false;
// 开始遍历节点
while(true) {
// 判断链表是否为空,如果为空的话,可以直接的去做一个添加操作
if(temp.next == null) {
break;
}
// 如果不为空的情况下,判断节点应该添加在那个位置
// 逻辑就是,如果temp的下一个节点的编号,大于当前要添加的编号,说明找到位置了
// 我们就是应该添加到 比他大的前面, 比他小的后面位置。
else if(temp.next.no > heroNode.no) {
break;
} else if (temp.next.no == heroNode.no) {
// 说明当前被添加的节点的编号已经存在于链表中设置为false
flag = true;
break;
}
// 如果都不成立的话,仍然需要让节点元素后移。
temp = temp.next;
}
// 在跳出循环后,首先判断 flag是否为true
if (flag) {
// 如果为true的情况下,则说明已经有重复的编号。
System.out.println("该节点编号重复: " + heroNode.to);
} else {
// 则说明编号不重复,具体的添加逻辑是
// 让当前要添加的英雄的节点位置,指向比他编号大的节点位置,也就是原先 temp 所
// 指向的节点
heroNode.next = temp.next;
// 然后,让原先的temp的下一个节点指向当前的新添加的节点。
temp.next = heroNode;
}
}
// 显示链表
public void list() {
// 先判断链表是否为空。
if (head.next == null) {
System.out.println("链表为空: []");
return;
}
// 因为头节点不能动,因此我们需要一个辅助变量来遍历
// 因为他不为空,所以说明他至少有一个数据
HeroNode temp = this.head.next;
while (true) {
// 判断是否到链表的最后
if (temp.next == null) {
// 最后一个元素也是有数据的
System.out.println(temp);
break;
}
// 输出
System.out.println(temp);
// 将temp后移
temp = temp.next;
}
}
}
/**
* 创建英雄节点
* 每一个HeroNode就是一个节点
*/
class HeroNode {
public int no; // 英雄编号
public String name; // 英雄名称
public String nickname; // 英雄绰号
public HeroNode next; // 指向下一个节点
public HeroNode(int heroNo, String hName, String nickName) {
this. no = heroNo;
this.name = hName;
this.nickname = nickName;
}
/**
* 为了显示方便 重写toString
*/
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickname='" + nickname + '\'' +
", next=" + next +
'}';
}
}
他的核心内容依然是那个方法(addByOrder),其他的只是借用一下,不需要重复的去书写。
具体的解释,代码里的注释已经全部帮我做了。好吧虽然也是我自己写的。但是一边看注释一边看代码,要胜过看完代码之后看解析。(个人感觉)。
4.3 单链表节点的修改
链表内节点的修改,还是依赖在上述代码内,定义一个方法 update(HeroNode newHeroNode); 其中参数就是要修改的内容,节点的id是不变的,也就是说,是通过id来修改节点。
4.3.1 代码的实现
// 用于修改节点内容
public void update(HeroNode newHeroNode) {
// 首先判断头节点内是否有下一个节点
if (this.herd.next == null) {
// 链表内没有数据,没有办法更改,直接返回即可。
System.out.println("链表内没有数据,没有办法更改内容。");
return;
}
// 在链表不为空的情况下,定义一个辅助节点来操作,然后定义一个boolean变量来代表是否找到
// true为找到了no 相同的节点,false为没有找到no相同的节点
HeroNode temp = this.herd.next;
boolean flag = false;
// 开始遍历每一个节点
while(true) {
// 判断temp域是否为空,如果为空的话则说明遍历到了最后一个元素.
// 并且已经遍历完毕,因为指针永远指向下一个next temp = herd.next
if (temp == null) {
// 直接退出即可
break;
}
// 还有一种情况就是找到了no编号相对等的元素,仍然直接退出即可
// 因为此时 temp 指向的就是 那个对象
if (temp.no == newHeroNode.no) {
// 并且flag设为true
flag = true;
break;
}
// 最后如果两种条件都不成立,别忘记进行下一个节点的遍历
temp = temp.next;
}
// 跳出循环以后,无非就是两种可能性,一种flag为 true, 一种为false
// 在flag为true的情况下,说明找到了对应的节点元素。反之没有
if (flag) {
// 因为编号是不允许改变的,我们只需要改变这个节点的其他内容即可。指向仍然不变
temp.name = newHeroNode.name;
temp.nickname = newHeroNode.nickname;
} else {
System.out.println("没有找到对应匹配的节点元素, 无法更改");
}
}
4.4 单向链表的删除
这一块是我自己的思路,因为前面添加的时候规定过,首先,头节点不做添加的内容,也就是说 0 号no 也是一样可以进行添加的,第二,编号 no 是唯一的!, 那么现在就可以跟操作数据库一样,思路逐渐清晰,通过 no 编号来删除,就是因为他是唯一的。
4.4.1 代码的实现
依然是在以上的代码上做添加方法内容。 方法名称叫做 HeroNode deleteByNo(Integer ino); 参数就是通过ino来删除节点内容。 返回值是HeroNode,来返回一个被删除的元素内容。
// 通过该方法来进行节点的删除。
public HeroNode deleteByNo(Integer ino) {
// 首先必要的就是一个辅助指针。该指针指向头元素,并且不做一下一个指向
// 就仅仅是指向头元素本身
HeroNode temp = this.herd;
// 创建一个boolean类型的变量,后续判断是否找到对应匹配ino的节点,默认为fales;
boolean flag = fales;
// 创建一个对象,用于返回被删除的内容
HeroNode deleteNode = null;
// 遍历循环节点。因为while循环总是在为true的时候去执行,所以可以直接判断
// temp.next != null
while (temp.next != null) {
// 此时只需要在循环内判断 ino是否和 temp.next.no 匹配,如果匹配,直接跳出循环
if (temp.next.no == ino) {
// 设置flag为true
flag = true;
break;
}
// 如果没有,继续向下一个节点走去
temp = temp.next;
}
// 在跳出循环之后,只会存在两种结果,flag = true/false
if (flag) {
// 我们需要在修改节点内容以前,把被删除的节点内容赋值
deleteNode = temp.next;
// 说明找到了该节点内容,在这里可以做分析,因为此时的temp指针,指向的是被删除的节点的
// 前一个节点内容。也就是说,temp.next 指向的是被删除的节点元素,而
// temp.next.next 指向的是被删除节点指向的下一个节点。我们需要做的就是把当前
// temp.next 的节点,指向被删除的下一个节点
temp.next = temp.next.next;
} else {
System.out.println("没有找到对应编号的节点,无法删除");
}
return deleteNode;
}
4.5 链表面试题(新浪,腾讯,百度)
已有代码结构为
package com.data_structure.linkedList;
public class SingleLinkedListDemo {
public static void main(String[] args) {
HeroNode heroNode1 = new HeroNode(0, "宋江", "及时雨");
HeroNode heroNode2 = new HeroNode(2, "李逵", "黑旋风");
HeroNode heroNode3 = new HeroNode(3, "林冲", "及时雨");
HeroNode heroNode4 = new HeroNode(4, "鲁智深", "及时雨");
SingleLinkedList singleLinkedList = new SingleLinkedList();
singleLinkedList.addByOrder(heroNode3);
singleLinkedList.addByOrder(heroNode1);
singleLinkedList.addByOrder(heroNode2);
singleLinkedList.addByOrder(heroNode4);
System.out.println("-------------------------------");
System.out.println(singleLinkedList.deleteByNo(3));
singleLinkedList.list();
}
}
class SingleLinkedList {
// 初始化头节点,头节点不动,不存放具体数据
private final HeroNode head = new HeroNode(0, "", "");
public HeroNode getHead() {
return this.head;
}
// 添加节点
public void add(HeroNode heroNode) {
// 添加的原理就是,当有新节点时,找到原有链表的最后一个节点,让最后一个节点指向该新节点。
// 但是头节点不动,就可以使用一个引用来代替头节点
HeroNode tempHead = this.head;
// 遍历链表,找到最后一个元素
while (tempHead.next != null) {
// 如果没有找到最后一个元素,就将temp后移
tempHead = tempHead.next;
}
// 当退出while循环时,tempHead就指向了链表的最后。
tempHead.next = heroNode;
}
public void addByOrder(HeroNode heroNode) {
// 因为头节点不能动,因此我们仍然需要一个辅助指针来帮助我们添加位置。
// 因为是单链表,因此我们找的temp是处于新增节点的前一个节点。否则添加不了。
HeroNode temp = this.head;
boolean flag = false; // 表示添加的编号是否存在,默认为不存在。
while (true) {
// 如果temp.next == null 则说明 此时处于链表的末尾。说明链表为空
if (temp.next == null) {
break; // 不管找不找得到 都需要break
}
// 如果是这种情况,则说明,位置找到了,我们本来需要添加的就是比他大的前一位。temp的后一位
if (temp.next.no > heroNode.no) {
break;
} else if (temp.next.no == heroNode.no) {
// 说明希望添加的heroNode的编号已经存在了
flag = true; // 说明编号存在
break;
}
temp = temp.next;
}
// 退出循环后,我们首先需要判断flag的值
if (flag) {
// 不能添加,说明编号存在
System.out.println("准备插入的这个英雄的编号: " + heroNode.no + " 已经存在。");
} else {
// 插入到链表中,temp的后面
heroNode.next = temp.next;
temp.next = heroNode;
}
}
// 修改节点的信息, 根据编号来修改,即编号无法做出更改。
public void update(HeroNode newHeroNode) {
// 判断链表是否为空
if (this.head.next == null) {
System.out.println("链表为空");
return;
}
// 如果不为空,就找到需要修改的节点
// 定义一个辅助节点
HeroNode temp = this.head.next;
// 表示是否找到该节点
boolean flag = false;
while (true) {
if (temp == null) {
// 就说明,已经遍历完链表
break;
}
if (temp.no == newHeroNode.no) {
// 则说明找到了,直接设置好flag 然后返回
flag = true;
break;
}
temp = temp.next;
}
// 一共就两种可能,第一种是找放到了,退出了循环,此时flag是true,第二种是找不到,flag是false。
if (flag) {
// 这种情况说明找到了
temp.name = newHeroNode.name;
temp.nickname = newHeroNode.nickname;
} else {
// 说明没找到节点
System.out.println("没有找到该节点: " + newHeroNode.no);
}
}
// 删除节点, 通过编号 no 来删除, 因为no 是唯一的。
public HeroNode deleteByNo(Integer ino) {
// 创建一个辅助指针,来辅助循环
HeroNode temp = this.head;
// 创建一个boolean 来记录是否找到
boolean flag = false;
// 遍历循环temp.next 来寻找与之对应的节点
while (temp.next != null) {
// 这种情况下,则说明,下一个节点就是要被删除掉的节点。直接返回
if (temp.next.no == ino) {
flag = true;
break;
}
// 对指针进行后移
temp = temp.next;
}
// 创建一个节点元素,表示被删除的节点
HeroNode deleteNode = null;
// 判断flag是否为true,如果为true,说明找到了,此时temp的位置,处于被删除元素的上一个,那么被删除元素的下一个就是 temp.next.next 只
// 需要让 temp.next = temp.next.next
if (flag) {
deleteNode = temp.next;
temp.next = temp.next.next;
} else {
System.out.println("找不到对应的ino编号。无法删除。");
}
return deleteNode;
}
// 显示链表
public void list() {
// 先判断链表是否为空。
if (head.next == null) {
System.out.println("链表为空: []");
return;
}
// 因为头节点不能动,因此我们需要一个辅助变量来遍历
// 因为他不为空,所以说明他至少有一个数据
HeroNode temp = this.head.next;
while (true) {
// 判断是否到链表的最后
if (temp.next == null) {
// 最后一个元素也是有数据的
System.out.println(temp);
break;
}
// 输出
System.out.println(temp);
// 将temp后移
temp = temp.next;
}
}
}
/**
* 创建英雄节点
* 每一个HeroNode就是一个节点
*/
class HeroNode {
public int no; // 英雄编号
public String name; // 英雄名称
public String nickname; // 英雄绰号
public HeroNode next; // 指向下一个节点
public HeroNode(int heroNo, String hName, String nickName) {
this. no = heroNo;
this.name = hName;
this.nickname = nickName;
}
/**
* 为了显示方便 重写toString
*/
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickname='" + nickname + '\'' +
", next=" + next +
'}';
}
}
4.5.1 求单链表中节点的个数
这个很简单,直接上代码
/**
* 获取单链表节点的个数(如果是带头节点的链表,需求不统计。
* @param heroNode 链表的头节点
* @return 返回有效的节点个数。
*/
public int getLength(HerdNode herd) {
// 判断头节点内是否有下一个节点数据
if (herd.next == null) {
System.out.println("没有有效的数据个数。");
return 0;
}
// 创建记录节点
int count = 0;
// 开始遍历,设置代替变量。有效数据不包含头节点
HerdNode temp = herd.next;
while(temp.next != null) {
count ++;
temp = temp.next;
}
return count;
}
4.5.2 查找单链表中倒数第k个节点(新浪面试题)
思路:
- 编写一个方法,,接受head节点,同时接受一个index
- index表示的是倒数第index个节点
- 就可以得到如下算法
- 有效的节点数 - index = 正数的第几个节点
- 然后我们从第一个开始遍历,到正数的第几个节点即可。
/**
* 查找单链表中倒数第k个节点(新浪面试题)
* 1. 编写一个方法,,接受head节点,同时接受一个index
* 2. index表示的是倒数第index个节点
* 3. 就可以得到如下算法
* 4. 有效的节点数 - index = 正数的第几个节点
* 5. 然后我们从第一个开始遍历,到正数的第几个节点即可。
* @param head 需要遍历的节点
* @param index 倒数第几个
* @return 如果找到返回节点,找不到返回null
*/
public static HeroNode findLastNode(HeroNode head, int index){
// 在已经得到index的情况下,先不要急着去取值,先判断节点是否为空
if (head.next == null || index < 0) {
return null;
}
// 开始获取总有效的元素 - index 得到需要遍历到第几个元素
int length = getLength(head) - index;
// 创建一个辅助节点。
HeroNode temp = head.next;
for (int i = 0; i < length; i++) {
temp = temp.next;
}
return temp;
}
4.5.3 单链表的反转(腾讯面试题)
实现思路如下
- 先定义一个节点,resverseHead = new HeroNode();
- 从头到尾遍历,每遍历一个节点,就将其取出,并且放在新的链表的最前端。
- 原来链表的 head.next = resverseHead.next;
其实说白了就是头插法,下面上代码,注释一一标出。
/**
* 链表的逆转
* @param head 传入的头链表
*/
public static void singleLinkedListT(HeroNode head) {
// 首先判断链表是否为空,或者是否只存在一个节点
if(head.next == null || head.next.next == null) {
// 直接返回就可以了
return;
}
// 现在定义一个辅助指针,让他指向头部的下一个节点。
HeroNode temp = head.next;
// 定义一个next链表,用来暂时存储数据,防止链表丢失
HeroNode next = null;
// 定义一个新的链表,用来逆序节点, 也就是这里采用头插法, 带有一个默认的头指针
HeroNode reverseHead = new HeroNode(0, "", "");
// 开始遍历,只要辅助节点本身不等于null,就可以确定没有到链表尾部。
// 因为temp开始就是指向了 head.next
while(temp != null) {
// 首先,让next链表,指向temp的下一个节点, 需要把数据暂时保留
next = temp.next;
// 然后让temp的下一个节点,指向新链表的头节点的下一个,后续可以拼接到
// 新链表的头节点后面去(头插法)
temp.next = reverseHead.next;
// 然后让新链表的头节点位置的下一个节点等于这个temp本身
reverseHead.next = temp;
// 让temp进行下一次遍历, 因为实现存储了next中。
temp = next;
}
// 然后让头节点引用指向新的链表
head.next = reverseHead.next;
}
4.5.4 从尾到头打印单链表(百度面试题,要求方式1,反向遍历。方式2,Stack栈)
栈方式,可以利用栈的先入后出原则,完成逆序打印而不改变链表原有的结构。这里我们要借助一个类,Stack。也就是栈的意思。先上代码演示如何使用
package stack;
import java.util.Stack;
public class StackTest {
public static void main(String[] args) {
Stack<String > stack = new Stack<>();
// 入栈
stack.add("1");
stack.add("2");
stack.add("3");
stack.add("4");
// 出栈
while (stack.size() > 0) {
// pop() 就是将栈顶的数据取出
System.out.println(stack.pop());
}
}
}
实际逻辑的实现
/**
* 使用栈来逆序打印链表
*/
public static void reversePrint(HeroNode head) {
// 判断头节点内是否有数据
if (head.next == null) {
// 直接退出
return;
}
// 创建Stack栈
Stack<HeroNode> stackHero = new Stack<>();
// 创建辅助指针
HeroNode temp = head.next;
// 遍历到最后一个
while(temp != null) {
stackHero.push(temp);
// 添加完成后后移
temp = temp.next;
}
// 遍历取出栈内元素
while(staciHero.size() != 0) {
System.out.println(stackHero.pop());
}
}
4.5.5 合并两个有序的单链表,合并之后的链表依然有序(课后练习)
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
示例1:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
限制:
0 <= 链表长度 <= 1000
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
// 建立虚拟头节点
ListNode dummy = new ListNode(0);
// 把cur指针先放到dummy上
ListNode cur = dummy;
// 基于比较遍历两个链表
while(l1 != null && l2 != null) {
// 对l1和l2的值做一个比较,看看哪一个的值最小。
// 如果l1比l2小。走if,如果l2比l1小。走else
if(l1.val < l2.val) {
// 让cur的下一个节点指向l1,并且让cur移动一下
cur.next = l1; // 指向更小的
cur = cur.next; // cur起到串联作用
l1 = l1.next; // l1 是比较的基准
} else {
cur.next = l2;
cur = cur.next;
l2 = l2.next;
}
}
if(l1 != null) cur.next = l1;
else cur.next = l2;
return dummy.next;
}
}
4.6 双向链表
目标为 使用带head头的双向链表实现水浒传排行。
缺优点分析。
- 单向链表,查找的方向只能是一个方向,而双向链表可以从前向后找也可以从后向前找,因为他多了一个尾指针 pre。
- 单链表不可以自我删除,需要依靠辅助节点,而双向链表,则可以自我删除,所以前面我们单链表删除节点时,总是需要找到temp,temp是待删除节点的前一个节点。
在这里我们需要用到的最基础的代码为
package com.data_structure.linkedList;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 双向链表
*/
public class DoubleLinkedListDemo {
public static void main(String[] args) {
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class ListNode {
public int no;
public String name;
public String nickname;
public HeroNode next; // 指向下一个节点 默认为null
public HeroNode pre; // 指向前一个节点 默认为null
@Override
public String toString() {
return "ListNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickname='" + nickname + '\'' +
'}';
}
}
完结代码,所有的解释都存在于注释中。认真阅读注释
package com.data_structure.linkedList;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 双向链表
*/
public class DoubleLinkedListDemo {
public static void main(String[] args) {
ListNode listNode1 = new ListNode(1, "宋江", "及时雨");
ListNode listNode2 = new ListNode(2, "李逵", "黑旋风");
ListNode listNode3 = new ListNode(3, "林冲", "豹子头");
ListNode listNode4 = new ListNode(4, "鲁智深", "垂杨柳");
DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
System.out.println("------------打印空数据-------------");
doubleLinkedList.list();
System.out.println("---------------------------------");
System.out.println();
doubleLinkedList.add(listNode1);
System.out.println("------------添加一个节点-------------");
doubleLinkedList.list();
System.out.println("---------------------------------");
doubleLinkedList.add(listNode2);
doubleLinkedList.add(listNode3);
doubleLinkedList.add(listNode4);
System.out.println();
System.out.println("------------添加四个节点-------------");
doubleLinkedList.list();
System.out.println("---------------------------------");
ListNode updateList = new ListNode(4, "盖伦", "德玛西亚");
doubleLinkedList.update(updateList);
System.out.println("------------修改编号四-------------");
doubleLinkedList.list();
System.out.println("---------------------------------");
System.out.println();
System.out.println("------------删除一个不存在的节点-------------");
doubleLinkedList.delete(9);
System.out.println("---------------------------------");
System.out.println();
System.out.println("------------删除一个存在的节点-------------");
doubleLinkedList.delete(4);
doubleLinkedList.list();
System.out.println("---------------------------------");
}
}
// 链表
class DoubleLinkedList {
// 初始化头
private final ListNode head = new ListNode();
/**
* 返回头节点
* @return 头节点
*/
public ListNode getHead() {
return head;
}
/**
* 遍历双向链表,和单向链表无异,直接可以用上面的,但是还是建议手写一遍,加深记忆
*/
public void list() {
// 判断节点内是否有元素。
if (this.head.next == null) {
System.out.println("节点内没有数据");
return;
}
// 判断节点是否只存在于一个数据
if (this.head.next.next == null) {
System.out.println(this.head.next);
return;
}
// 创建辅助节点开始辅助遍历, 直接从具体的元素数据开始遍历
ListNode temp = this.head.next;
while (temp != null) {
System.out.println(temp);
temp = temp.next;
}
}
/**
* 添加链表,因为是一个双向链表,所以需要在原有的单向链表的基础上,进行整改。
* 在next指向下一个对象后,需要让该对象的pre指向该对象的前一个temp;
*/
public void add(ListNode listNode) {
// 判断链表是否为空。
if (this.head.next == null) {
// 可以直接做添加
this.head.next = listNode;
listNode.pre = this.head;
return;
}
// 创建辅助节点,遍历到最后一个
ListNode temp = this.head;
while (temp.next != null) {
temp = temp.next;
}
// 遍历出来后temp一定是指向最后一个节点元素的
temp.next = listNode;
listNode.pre = temp;
}
/**
* 修改一个节点的内容。和原先地修改是一样的
*/
public void update(ListNode listNode) {
// 先判断节点是否为空,不包含头节点
if (this.head.next == null) {
System.out.println("链表内没有数,没有办法修改对应的节点。");
return;
}
// 创建辅助节点
ListNode temp = this.head.next;
while (temp != null) {
// 逐步判断是否有编号配对的,如果有多个编号,则总是修改第一个遇到的
if (temp.no == listNode.no) {
temp.name = listNode.name;
temp.nickname = listNode.nickname;
return;
}
temp = temp.next;
}
System.out.println("没有找到对应编号的节点");
}
/**
* 删除一个节点来使用, 因为他是双向链表,我们可以直接找到待删除节点的本身。然后进行自我删除。
* 不管有几个重复的节点no,我们只删除第一个出现的no
*/
public void delete(int no) {
// 判断节点内是否有元素。
if (this.head.next == null) {
// 说明没有元素。
System.out.println("节点内没有数据无法删除.");
return;
}
// 创建复制节点直接遍历
ListNode temp = this.head.next;
while (temp != null) {
// 寻找节点是否匹配
if (temp.no == no) {
if (temp.next == null) {
// 说明在最后一个 ,直接删除即可。
temp.pre.next = null;
return;
}
// 说明找到该被删除的元素
// temp本身的上一个节点的下一个节点要指向temp本身的下一个节点。
temp.pre.next = temp.next;
// temp下一个节点的上一个节点需要指向temp的上一个节点
temp.next.pre = temp.pre;
return;
}
temp = temp.next;
}
System.out.println("找不到编号为: " + no + " 的节点。");
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class ListNode {
public int no;
public String name;
public String nickname;
public ListNode next; // 指向下一个节点 默认为null
public ListNode pre; // 指向前一个节点 默认为null
public ListNode(int no, String name, String nickname) {
this.no = no;
this.name = name;
this.nickname = nickname;
}
@Override
public String toString() {
return "ListNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickname='" + nickname + '\'' +
'}';
}
}
4.7 单向环形链表
4.7.1 单向环形链表的介绍
构建一个单向环形链表的基本思路
- 先创建第一个节点,让first指向该节点,形成环形。
- 后面我们每创建一个新的节点,就把该节点添加到环形链表中即可。
遍历环形链表
- 先让辅助指针 cur,指向first节点。
- 然后通过一个while循环遍历该环形链表即可。
- 在cur.next == first时,遍历一整个结束。
代码的实现
package com.data_structure.linkedList;
import lombok.Data;
import lombok.ToString;
/**
* 约瑟夫问题
*/
public class Josepfu {
public static void main(String[] args) {
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
circleSingleLinkedList.addBoy(10);
circleSingleLinkedList.showBoy();
}
}
// 创建一个环形的单向链表
class CircleSingleLinkedList {
// 创建第一个first节点。当前没有编号
private Boy first = null;
// 添加小孩节点。构成一个环形链表。传入你要添加几个小孩
public void addBoy(int nums) {
// 判断这个数是否具有效果,比如最少需要添加一个小孩
if (nums < 1) {
System.out.println("输入的数字不对, 没办法创建小于 1 的孩子数量!");
return;
}
// 创建辅助指针
Boy cur = null;
// 通过for循环来添加节点
for (int i = 1; i <= nums; i++) {
Boy boy = new Boy(i);
// 在添加第一个的时候,直接的让第一个的next指向它本身
if (i == 1) {
// 总是需要添加第一个小孩的,直接让first等于第一个创建的小孩。
this.first = boy;
// 然后把添加的第一个小孩的下一个指向它自己,形成一个人的环状
this.first.setNext(first);
// 然后让cur指向第一个小孩
cur = this.first;
} else {
// 如果是第二个小孩开始
// 让辅助指针先指向新的boy,也就是做后移操作
cur.setNext(boy);
// 然后把当前boy的下一个节点指向first,连接到头部
boy.setNext(this.first);
// 然后cur后移到boy本身的节点上
cur = boy;
}
}
}
// 遍历当前的环形链表
public void showBoy() {
// 判断当前链表内是否有内容。
if (this.first == null) {
System.out.println("链表为空。无法打印内容。");
return;
}
// 创建一个辅助节点。开始辅助遍历
Boy temp = this.first;
while (true) {
System.out.println("小孩的编号为: " +temp.getNo());
if (temp.getNext() == this.first) {
return;
}
temp = temp.getNext();
}
}
}
// 创建一个Boy类。表示一个节点。
@Data
class Boy {
private int no; // 编号
private Boy next; // 指向下一个节点。默认为空
public Boy(int no) {
this.no = no;
}
@Override
public String toString() {
return "Boy{" +
"no=" + no +
'}';
}
}
4.7.2 实现约瑟夫问题
思路
- 需要创建一个辅助指针helper,指向first节点的上一个节点。也就是整个链表的最后一个节点。
- 当开始报数的时候,让first指针开始移动,helper指针需要一直跟在first后面(也就是同时移动),移动m-1次。因为当前节点自己也需要报数一次。
- 这时就可以将first指向的节点,开始出圈。也就是,先让first向前移动一次。helper.next = first; 那么原来要被出圈的first就没有了任何引用,就会被垃圾回收机制给回收。
所有的注释注意看
public void main(String[] args) {
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
circleSingleLinkedList.addBoy(10);
circleSingleLinkedList.showBoy();
System.out.println(circleSingleLinkedList.getCountBoy());
System.out.println("约瑟夫取出问题: " + circleSingleLinkedList.countBoy(1, 3, 10));
}
/**
* 经典约瑟夫问题,根据用户的输入,计算出出圈的一个顺序。
* @param startNo 表示从第几个小孩开始数数
* @param countNumber 表示数几下
* @param nums 表示最初有多少小孩在圈内
*/
public List<Integer > countBoy(int startNo, int countNumber, int nums) {
// 首先判断first是否为空,然后判断开始人是否小于1 然后判断是否超出最大数
if (this.first == null || startNo < 1 || startNo > nums || nums != getCountBoy()) {
System.out.println("参数有误,请重新输入。");
return null;
}
// 首先创建一个辅助指针,然后让其遍历至最后一个。达到最后一个,也就是指向链表的尾部(虽然环形链表不存在尾部,但是也是意义上的尾部)
Boy helper = this.first;
while (helper.getNext() != this.first) {
helper = helper.getNext();
}
// 此时出来以后,helper所处的位置已经到了first的前一个位置。现在开始确定从哪一个小孩开始数数,for循环解决
for (int i = 1; i < startNo; i++) {
// 如果此时从1号开始数,则根本进不来该循环。无需担心会额外跳出的问题。
// 现在解决的就是指向问题。
this.first = this.first.getNext();
helper = helper.getNext();
}
// 跳出循环后,无论怎么样,都会解决掉从那个位置开始。两个指针都会放置到他们应该出现的位置。且 helper永远在first前一个。
// 当他指向这里之后,我们就开始看,每几下。出一个圈。然后仍然使用for 解决。
// 假设这里需要每五人出一下,但是,第一个数的人也包含在内。所以需要循环四次。因为指向的是next
//我们要做的是把他取空,所以使用while
// 创建集合,依次保存取出的值。ArrayList插入快。
List<Integer > josepfu = new ArrayList<>();
while (this.first != null) {
// 说明到了最后一个,直接添加并且返回
if (this.first.getNext() == this.first) {
System.out.println("被移除了节点元素: " + first.getNo());
josepfu.add(first.getNo());
break;
}
for (int i = 1; i < countNumber; i++) {
this.first = first.getNext();
helper = helper.getNext();
}
// 当出来for循环之后,他们会分别停在对应的位置上,first会停在被删除的节点的位置上,helper会停留在first的上一个节点上。
// 此时first后移,然后helper指向fist
josepfu.add(first.getNo());
System.out.println("被移除了节点元素: " + first.getNo());
first = first.getNext();
helper.setNext(first);
}
return josepfu;
}