-
4.2.2 普通队列代码实现
-
4.2.3 循环队列
-
4.2.4 循环队列代码实现
-
5 栈
-
- 5.1 概念
-
5.2 利用数组实现栈结构
-
- 5.2.1 思路分析
-
5.2.2 代码实现
-
5.3 利用栈结构实现计算器的计算
-
5.3.1 需求
-
- 5.3.2 思路分析
-
5.3.3 代码实现
[](()1 概论
=======================================================================
表是一种表示数据元素之间存在一个对一个的关系的数据结构,对应Java中的数组(顺序表)、队列、链表、栈。
参考【传送门】
[](()2 数组
=======================================================================
[](()2.1 定义
| 属性 | 特征 |
| — | — |
| 特点 | ① 使用连续分配的内存空间;②一次申请一大段连续的空间, 需要事先声明最大可能要占用的固定内存空间。 |
| 优点 | 设计简单,读取与修改表中任意一个元素的时间都是固定的。 |
| 缺点 | ① 容易造成内存的浪费;② 删除或插入数据需要移动大量的数据。 |
| 总结 | 适合查询操作远多于插入、删除和修改操作的场景,同时是后续其它数据结构的基石。 |
[](()2.2 操作
| 属性 | 特征 |
| — | — |
| 查(读内存) | 每个元素都有一个数值下标, 可以通过下标瞬间定位到某个元素; |
| 增(写内存) | 可以在数组的任意位置添加元素,但是插入元素会引起部分元素后移,代价非常高; |
| 删(写内存) | 去掉元素,引起部分元素前移,代价非常高; |
| 改(写内存) | 只需要修改对应位置的元素的内容,不需要申请或者删除空间。 |
[](()2.3 稀疏数组
[](()2.3.1 定义
当一个数组中大部分元素为0,或者为同一个值的数组 Java开源项目【ali1024.coding.net/public/P7/Java/git】 时,可以使用稀疏数组来保存该数组。
稀疏数组的处理方法是:
-
记录数组一共有几行几列,有多少个不同的值;
-
把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模。
如下所示,可以将左边稀疏数组转换为右边的数组:
|
[](()2.3.2 代码实现
利用代码实现上述稀疏数组与二维数组的转换。
/**
- 稀疏数组与二维数组的转换
*/
public class SparseArray {
public static void main(String[] args) {
/*
1 从二维数组转化到稀疏数组
*/
//1.1 首先定义一个二维数组
//11行,12列
int[][] chessArr = new int[6][7];
chessArr[0][3] = 22;
chessArr[0][6] = 15;
chessArr[1][1] = 11;
chessArr[1][5] = 17;
chessArr[2][3] = -6;
chessArr[3][5] = 4;
chessArr[4][0] = 91;
chessArr[5][2] = 28;
//1.2 控制台输出这个二维数组,按照数组的形式输出
//因为,我不需要知道数组中每一个元素对应的位置坐标,所以可以直接使用foreach
System.out.println();
System.out.println(“这里输出的是原来的二维数组:”);
for (int[] row : chessArr) {
for (int element : row) {
//printf表示格式化输出
System.out.printf(“%d\t”, element);
}
System.out.println();//换行
}
//1.3首先我要遍历得到二维数组中,哪些元素的值为非0
//二维数组的大小,二维数组中非0值的数量,二维数组中非0值的位置与值的大小
int length = chessArr[0].length;
int height = chessArr.length;
int num = 0;
//应该是先输出height,即表示行的意思
for (int i = 0; i < height; i++) {
for (int j = 0; j < length; j++) {
if (chessArr[i][j] != 0) {
num++;
}
}
}
//1.4 根据上步得到的值创建稀疏数组
int[][] sparseArr = new int[num + 1][3];
sparseArr[0][0] = height;
sparseArr[0][1] = length;
sparseArr[0][2] = num;
int count = 0;
for (int i = 0; i < height; i++) {
for (int j = 0; j < length; j++) {
if (chessArr[i][j] != 0) {
count = count + 1;
sparseArr[count][0] = i;
sparseArr[count][1] = j;
sparseArr[count][2] = chessArr[i][j];
}
}
}
//1.5 控制台输出这个稀疏数组。另一种方法输出数组
System.out.println();
System.out.println(“接下来输出稀疏数组:”);
for (int[] ints : sparseArr) {
System.out.printf(“%d\t%d\t%d\t\n”, ints[0], ints[1], ints[2]);
}
System.out.println();
/*
第一步完成,接下来进行第二步
2 将稀疏数组重新转化为原来的二维数组
*/
//2.1 根据稀疏数组的第一行建立一个规定大小的二维数组
int[][] newChessArr = new int[sparseArr[0][0]][sparseArr[0][1]];
//先行后列
for (int i = 1; i < sparseArr.length; i++) {
newChessArr[sparseArr[i][0]][sparseArr[i][1]] = sparseArr[i][2];
}
//2.2 控制台输出稀疏数组
System.out.println(“接下来输出的是转化为原来的二维数组:”);
for (int[] ints : newChessArr) {
for (int j = 0; j < newChessArr[0].length; j++) {
System.out.printf(“%d\t”, ints[j]);
}
System.out.println();
}
}
}
[](()3 链表
=======================================================================
[](()3.1 单向链表
[](()3.1.1 概念
链表是一种物理存储结构上非连续、非顺序,然而在逻辑结构上是头接着尾、连续的数据结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
链表由一系列节点组成,节点可以在运行时动态生成。
每个结点包括两个部分:一个是存储数据元素的数据域,即 data 域;另一个是存储下一个结点地址的指针域,即 next 域。
由于 Java 中不存在指针的概念,所以用 Java 实现链表时, next 域申明的类型是节点的类型,有点类似于递归,无限套娃。
[](()3.1.2 实现原理和应用实例
利用一个应用实例来掌握链表的原理以及链表是如何实现的,具体操作如下图所示:
应用实例:使用带头节点的单向链表实现水浒英雄排行榜管理的增、删、改、查操作。
1)增加节点
① 在链表末尾增加节点:
-
首先创建链表的头节点(headNode),头节点的作用仅仅是表示链表的头部;
-
创建一个临时节点,初始化为tempNode=headNode;
-
然后利用头节点不断遍历该链表,直到遍历到链表尾部(判断条件:tempNode.next=null)。遍历完链表的tempNode其实就相当于链表本身;
-
将新加入的数据放在链表尾部(tempNode.next=newHeroNode)。
② 在链表中按照序号加入节点:
-
首先创建链表的头节点(headNode),头节点的作用仅仅是表示链表的头部;
-
创建一个临时节点,初始化为tempNode=headNode;
-
然后利用头节点不断遍历该链表,直到遍历到新加入节点序号的前一位(判断条件:tempNode.next.no>newHeroNode.no);
-
按照下图所示插入节点:
2)删
5. 首先创建链表的头节点(headNode),头节点的作用仅仅是表示链表的头部;
6. 创建一个临时节点,初始化为tempNode = headNode;
7. 然后利用头节点不断遍历该链表,直到遍历到需要删除节点的前一个节点。(tempNode.next = heroNode);
8. 利用 tempNode.next = tempNode.next.next 将该节点删除,该语法有效的前提是Java的自动垃圾回收机制。
3)改
9. 首先创建链表的头节点(headNode),头节点的作用仅仅是表示链表的头部;
10. 创建一个临时节点,初始化为 tempNode = headNode;
11. 然后利用头节点不断遍历该链表,直到遍历到需要删除节点的前一个节点;
12. 改的原则就是四个域中,只需修改 name 域与 nickname 域,no 不必修改。
4) 查
-
首先创建链表的头节点(headNode),头节点的作用仅仅是表示链表的头部;
-
创建一个临时节点,初始化为 tempNode=headNode;
-
利用临时节点不断遍历列表,然后通过重写 toString 方法来获得对象的全部属性到控制台。
在实际代码的时候是 System.out.println(tel);
输出 包名.类名@哈希码
。这不是最重要的,重要的是,我们可以重写toString()
方法来获得对象的全部属性,重写好之后的toString
的作用就是控制台输出对象的全部属性 。
[](()3.1.3 代码实现
/**
-
1、复现出单链表的实现(描述梁山好汉)
-
2、实现单链表的增、删、改、查
-
3、利用面向对象的编程思想来写
-
4、按照设计模式来写的话,这三个类应该要放在不同的包内,但是为了简单起见,我把它们放在一个文件中
*/
public class SingleLinkedListView {
public static void main(String[] args) {
//首先可以先建立一些节点
HeroNode hero1 = new HeroNode(1, “宋江”, “及时雨”);
HeroNode hero2 = new HeroNode(2, “卢俊义”, “玉麒麟”);
HeroNode hero3 = new HeroNode(3, “吴用”, “智多星”);
HeroNode hero4 = new HeroNode(2, “林冲”, “豹子头”);
//这里不能将上面的英雄直接赋值给singlelinkedlist,因为我没有在singlelinkedlist类中设置构造函数
SingleLinkedList singlelinkedlist = new SingleLinkedList();
//从尾部加入节点
singlelinkedlist.addTailNode(hero1);
singlelinkedlist.addTailNode(hero3);
//从任意位置加入节
//正常加入
singlelinkedlist.addMiddleNode(hero2);
// singlelinkedlist.replaceNode(hero4);
//测试自己的Exception类
// singlelinkedlist.addMiddleNode(hero3);
singlelinkedlist.deleteNode(1);
//显示链表
System.out.println(“显示链表的信息……”);
singlelinkedlist.shownode();
}
}
/**
- 用来存放基本类,这是一种面向对象的编程思想
*/
class SingleLinkedList {
//首先定义一个头节点:头节点的作用仅仅是表示单链表的头
public HeroNode headNode = new HeroNode(0, “”, “”);
//增、删、改、查
//首先书写增。增有两种情况,一种是在链表末尾增加节点,另一种是在链表中增加节点。
/**
-
1.1 在链表末尾增加节点
-
通过一个辅助变量tempnode来遍历,帮助遍历整个链表
-
@param heroNode 加入的新节点
*/
//直接将头节点赋值给临时节点,这样就类似于数组的直接赋值一样。
public void addTailNode(HeroNode heroNode) {
HeroNode tempNode = headNode;
while (true) {
if (tempNode.next == null) {
break;
}
//就相当于是循环语法中的n=n+1
tempNode = tempNode.next;
}
tempNode.next = heroNode;
}
/**
-
1.2 在链表中间,根据节点的序号来插入节点,这样的话,上述在链表尾部增加节点可以看成是这种情况的特例
-
@param heroNode 加入的节点
*/
public void addMiddleNode(HeroNode heroNode) {
//flag来判断该节点的序号在链表中是否存在
//要考虑三种 《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》开源 情况:
//1 新加入节点的序号在尾节点
//2 新加入的节点的序号在链表中已经存在,所以,要做一个报错处理
//3 新加入的节点的序号在链表中不存在,允许加入
boolean flag = false;
HeroNode tempNode = headNode;
while (true) {
if(tempNode.next==null){
break;
}
if (tempNode.next.no==heroNode.no){
flag=true;
break;
}
else if (tempNode.next.no>heroNode.no){
break;
}
tempNode=tempNode.next;
}
if (flag) {
System.out.println(“该链表中已经存在该序号的节点\n”);
} else {
heroNode.next=tempNode.next;
tempNode.next=heroNode;
}
}
/**
-
2 删。因此tempNode初始定义为headNode,所以,它之后就代表了这条链表
-
很好,没有补位,只是将这个节点删除了。
*/
public void deleteNode(int no){
boolean flag=false;
HeroNode tempNode = headNode;
while(true){
if(tempNode.next==null){
break;
}
if(tempNode.next.no==no){
flag=true;
break;
}
tempNode=tempNode.next;
}
if (flag==true){
//tempNode.next这个节点会被Java的垃圾回收机制自动回收
tempNode.next=tempNode.next.next;
}
else{
System.out.println(“该链表中不存在该节点。\n”);
}
}
/**
-
3 改。改的原则就是四个域中,只需修改name域与nickname域,no不必修改。
-
之前想不明白的就是为什么next域不用改,现在知道了。
*/
public void replaceNode(HeroNode heroNode){
//考虑两种情况:
//1 链表中不存在该序号
//2 链表中存在该序号
boolean flag=false;
HeroNode tempNode = headNode;
//考虑所有可能出现的情况
if(tempNode.next==null){
System.out.println(“该链表为空,无法修改节点。\n”);
//直接跳出方法
return;
}
while(true){
if(tempNode.next.no==heroNode.no){
flag=true;
break;
}
if(tempNode.next==null){
break;
}
tempNode=tempNode.next;
}
if(flag==true){
tempNode.next.name=heroNode.name;
tempNode.next.nickname=heroNode.nickname;
}
else{
System.out.println(“链表中不存在该序号!\n”);
}
}
/**
- 4 查,即遍历输出链表中的全部元素。
*/
public void shownode() {
HeroNode tempNode = headNode;
//对于数组中个数未知的情况,就不适合用for,而是应该用while
while (true) {
if (tempNode == null) {
break;
}
//因为我在HeroNode类中重写了toString方法,所以这里的toString的作用就是控制器print输出对象的全部属性
System.out.println(tempNode);
tempNode = tempNode.next;
}
}
}
/**
- 一个基本类是必不可少的
*/
//为什么这个类前面不能加public →因为文件名必须与 public 的类名保持一致,如果一个文件里面有多个public关键字定义的类,那就会报错
class HeroNode {
public int no;
public String name;
public String nickname;
//我还是想不明白,为什么next可以当成一个,不断指向后一个数。2020/9/3 明天写好这段代码,调试一下。
//调试完我终于知道了,这个类与上述的list类相结合起来时,这个next域起到作用,太妙了。把next当成一个变量。
//我似乎知道了,其实链表的每一个节点存储着两个域,分别是data域和next域,next域用于指向下一个节点 2020/9/4
public HeroNode next;
public HeroNode(int no, String name, String nickname) {
this.no = no;
this.name = name;
this.nickname = nickname;
}
//重写的话尽然还需要专门来一个注释,可怕
//我们可以重写同String()方法来获得(控制器输出)对象的全部属性
@Override
public String toString() {
return “HeroNode[no=” + no + “,name=” + name + “,nickname=” + nickname + “]”;
}
}
[](()3.2 双向链表
[](()3.2.1 概念
为什么要用双向链表呢?因为,单向链表存在以下缺点:
-
单向链表的查找方向只能是一个方向,而双向链表可以向前或者向后查找;
-
单向链表不能自我删除节点,而需要靠辅助节点,而双向链表可以实现自己删除,但是在代码实现中还是需要定义 tempNode 临时结点。
[](()3.2.2 实现原理和应用实例
和单向链表一样,我用水浒传排行榜的增、删、改、查来代码实现双向链表。
在节点类中,唯一与单向链表不同的是,双向链表除了单向链表的域外,还有一个 pre域,用于指向前一个节点。如下图所示:
代码的实现原理见代码中注释,具体步骤和单向链表类似。
[](()3.2.3 代码实现
public class DoubleLinkedListView {
public static void main(String[] args) {
//首先可以先建立一些节点
HeroNode hero1 = new HeroNode(1, “宋江”, “及时雨”);
HeroNode hero2 = new HeroNode(2, “卢俊义”, “玉麒麟”);
HeroNode hero3 = new HeroNode(3, “吴用”, “智多星”);
DoubleLinkedList doublelinkedlist = new DoubleLinkedList();
//增
doublelinkedlist.addTailNode(hero1);
doublelinkedlist.addTailNode(hero3);
//删
doublelinkedlist.deleteNode(3);
//改:只修改name和nickname,不修改其它四个变量
HeroNode hero4=new HeroNode(1,“赵志明”,“sharm”);
doublelinkedlist.replaceNode(hero4);
//查
System.out.println(“显示链表的信息……”);
doublelinkedlist.shownode();
}
}
/**
- 用来存放基本类,这是一种面向对象的编程思想
*/
class DoubleLinkedList {
//首先定义一个头节点:头节点的作用仅仅是表示单链表的头
public HeroNode headNode = new HeroNode(0, “”, “”);
//增、删、改、查
//首先书写增。增有两种情况,一种是在链表末尾增加节点,另一种是在链表中增加节点。
/**
-
1.1 在链表末尾增加节点
-
通过一个辅助变量tempnode来遍历,帮助遍历整个链表
-
@param newHeroNode 加入的新节点
*/
//直接将头节点赋值给临时节点,这样就类似于数组的直接赋值一样。
public void addTailNode(HeroNode newHeroNode) {
HeroNode tempNode = headNode;
while(true){
if (tempNode.next==null){
break;
}
//就相当于是循环语法中的n=n+1
tempNode = tempNode.next;
}
tempNode.next=newHeroNode;
newHeroNode.pre=tempNode;
}
/**
-
1.2 在链表中间,根据节点的序号来插入节点,这样的话,上述在链表尾部增加节点可以看成是这种情况的特例
-
@param newHeroNode 加入的节点
*/
public void addMiddleNode(HeroNode newHeroNode) {
//flag来判断该节点的序号在链表中是否存在
//要考虑三种情况:
//1 新加入节点的序号在尾节点
//2 新加入的节点的序号在链表中已经存在,所以,要做一个报错处理
//3 新加入的节点的序号在链表中不存在,允许加入
boolean flag = false;
HeroNode tempNode = headNode;
while (true) {
if(tempNode.next==null){
break;
}
if (tempNode.next.no==newHeroNode.no){
flag=true;
break;
}
else if (tempNode.next.no>newHeroNode.no){
break;
}
tempNode=tempNode.next;
}
if (flag) {
System.out.println(“该链表中已经存在该序号的节点\n”);
} else {
newHeroNode.next=tempNode.next;
tempNode.next=newHeroNode;
tempNode.next.pre=newHeroNode;
newHeroNode.pre=tempNode;
}
}
/**
-
2 删。因此tempNode初始定义为headNode,所以,它之后就代表了这条链表
-
双向链表的删除结点不需要依靠前一个节点,可以实现自我删除,但是临时节点还是需要的
-
两种情况,一种是删除链表的最后一个节点,另一种是删除链表中间的一个节点
*/
public void deleteNode(int no){
// 判断当前链表是否为空
if (headNode.next == null) {// 空链表
System.out.println(“链表为空,无法删除”);
return;
}
boolean flag=false;
HeroNode tempNode = headNode;
while(true){
//把两个if判断的语句改下位置,就可以达到目的
//原因是,如果因为我判断是否到达末尾用的是.next,如果我要删除的是最后一位,
// 那么当判断成立后,会直接break,导致不进入到tempNode.no=no的判断
if(tempNode.no==no){
flag=true;
break;
}
if(tempNode.next==null){
break;
}
tempNode=tempNode.next;
}
if (flag==true){
//tempNode.next这个节点会被Java的垃圾回收机制自动回收
//如果要删除的节点为尾节点时
if (tempNode.next==null){
tempNode.pre.next=null;
}
else{
tempNode.pre.next=tempNode.next;
tempNode.next.pre=tempNode.pre;
}
}
else{
System.out.println(“该链表中不存在该节点。\n”);
}
}
/**
-
3 改。改的原则就是四个域中,只需修改name域与nickname域,no不必修改。
-
之前想不明白的就是为什么next域不用改,现在知道了。
*/
public void replaceNode(HeroNode newHeroNode){
//考虑两种情况:
//1 链表中不存在该序号
//2 链表中存在该序号
boolean flag=false;
HeroNode tempNode = headNode;
//考虑所有可能出现的情况
if(tempNode.next==null){
System.out.println(“该链表为空,无法修改节点。\n”);
//直接跳出方法
return;
}
while(true){
if(tempNode.next.no==newHeroNode.no){
flag=true;
break;
}
if(tempNode.next==null){
break;
}
tempNode=tempNode.next;
}
if(flag==true){
tempNode.next.name=newHeroNode.name;
tempNode.next.nickname=newHeroNode.nickname;
}
else{
System.out.println(“链表中不存在该序号!\n”);
}
}
/**
- 4 查,即遍历输出链表中的全部元素。
*/
public void shownode() {
HeroNode tempNode = headNode;
//对于数组中个数未知的情况,就不适合用for,而是应该用while
while (true) {
if (tempNode == null) {
break;
}
//因为我在HeroNode类中重写了toString方法,所以这里的toString的作用就是控制器print输出对象的全部属性
System.out.println(tempNode);
tempNode = tempNode.next;
}
}
}
class HeroNode {
public int no;
public String name;
public String nickname;
//我还是想不明白,为什么next可以当成一个,不断指向后一个数。2020/9/3 明天写好这段代码,调试一下。
//调试完我终于知道了,这个类与上述的list类相结合起来时,这个next域起到作用,太妙了。把next当成一个变量。
//我似乎知道了,其实链表的每一个节点存储着两个域,分别是data域和next域,next域用于指向下一个节点 2020/9/4
public HeroNode next;
public HeroNode pre;
public HeroNode(int no, String name, String nickname) {
this.no = no;
this.name = name;
this.nickname = nickname;
}
//重写的话尽然还需要专门来一个注释,可怕
//我们可以重写同String()方法来获得(控制器输出)对象的全部属性
@Override
public String toString() {
return “HeroNode[no=” + no + “,name=” + name + “,nickname=” + nickname + “]”;
}
}
[](()3.3 单向环形链表
[](()3.3.1 概念
即Josephu(约瑟夫环)问题,该问题为:设编号为1,2……n的n个人围坐在一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m的那个人出列,他的下一位又从1开始报数,数到m的那个人出列,以此类推,直到剩下最后一个人。
[](()3.3.2 实现原理和应用实例
这里,我们使用单向环形链表来解决这个问题,该单向环形链表不带头节点。
在应用中,我们需要考虑三个需求:
1)单向环形链表的建立;
2)单向环形链表的遍历;
3)开始进行约瑟夫环的问题。
[](()3.3.3 代码实现
/**
-
@author 赵志明
-
@demand 即Josephu(约瑟夫环)问题,该问题为:设编号为1,2……n的n个人围坐在一圈,约定编号为k(1<=k<=n)的人从1开始报数,
-
数到m的那个人出列,他的下一位又从1开始报数,数到m的那个人出列,以此类推,直到剩下最后一个人。
*/
public class SingleCircleLinkedListView{
public static void main(String[] args) {
SingleCircleLinkedList linkedList =new SingleCircleLinkedList();
linkedList.createLinkedList(9);
linkedList.showLinkedList();
linkedList.countChild(2,3,4);
}
}
class SingleCircleLinkedList {
//首先是创建,输入创建节点的个数num即可创建该个数的链表
//1 定义一个first节点
//2 定义一个辅助节点 curChild节点
//3 利用for循环来增加节点,排除边界条件
private Child first = null;
private Child curChild= null;
public void createLinkedList(int num) {
if (num < 1) {
System.out.println(“输入的num不能有误,请输入大于0的数!”);
return;
}
for (int i = 1; i <= num; i++) {
Child newchild = new Child(i);
if (i == 1) {
//首先是两个指针的重新指向,然后是值的重新赋值。这一段还是有些迷惑的,不过随着学习的推进,肯定会慢慢了解的
first = newchild;
first.setNext(first);
curChild = first;
}
else{
curChild.setNext(newchild);
newchild.setNext(first);
curChild=newchild;
}
}
}
//然后是遍历
public void showLinkedList(){
if (first==null) {
System.out.println(“此时无小孩。”);
}
while(true){
// System.out.printf("小孩的编号 "+curChild.getNo()); 两种格式化方法
System.out.printf(“小孩的编号 %d\n”,curChild.getNo());
curChild=curChild.getNext();
if (curChild.getNext()==first){
break;
}
}
}
/**
-
@param startNo 即编号k,表示从第k个小孩开始数数
-
@param countNum 即数字m,表示要求数几下
-
@param num 即最开始的总人数n
-
这个方法之后的时候还需要自己再写一遍,第一遍没有独立完成——2020.10.10
*/
public void countChild(int startNo,int countNum,int num){
//首先判断边界条件。原理:
//定义一个helper辅助指针,其应当指向环形链表的最后一个节点:只能是通过遍历的方法使helper推到最后一位
//小孩报数前,先让first和helper移动k-1次,即将helper和first指针推到初始的节点
//当小孩报数时,让first 和 helper 指针同时 的移动 m -1 次, 然后出圈.这里是一个循环操作,直到圈中只有一个节点
// if(first == null || countNum < 1 || num < 1 || startNo < num){
// System.out.println(“输入的值有误,请重新输入。”);
// }
Child helper=first;
while(true){
if (helper.getNext()==first){
break;
}
else{
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();
}
//这时first指向的节点,就是要出圈的小孩节点
System.out.printf(“小孩%d出圈\n”, first.getNo());
//这时将first指向的小孩节点出圈,这里需要一个立体思维
first=first.getNext();
helper.setNext(first);
}
System.out.printf(“最后留在圈中的小孩编号%d \n”, first.getNo());
}
}
class Child {
//因为,单向链表和双向链表都是通过共有变量,即child.next=newchild来操作
最后
金三银四马上就到了,希望大家能好好学习一下这些技术点
学习视频:
大厂面试真题:
intf("小孩的编号 "+curChild.getNo()); 两种格式化方法
System.out.printf(“小孩的编号 %d\n”,curChild.getNo());
curChild=curChild.getNext();
if (curChild.getNext()==first){
break;
}
}
}
/**
-
@param startNo 即编号k,表示从第k个小孩开始数数
-
@param countNum 即数字m,表示要求数几下
-
@param num 即最开始的总人数n
-
这个方法之后的时候还需要自己再写一遍,第一遍没有独立完成——2020.10.10
*/
public void countChild(int startNo,int countNum,int num){
//首先判断边界条件。原理:
//定义一个helper辅助指针,其应当指向环形链表的最后一个节点:只能是通过遍历的方法使helper推到最后一位
//小孩报数前,先让first和helper移动k-1次,即将helper和first指针推到初始的节点
//当小孩报数时,让first 和 helper 指针同时 的移动 m -1 次, 然后出圈.这里是一个循环操作,直到圈中只有一个节点
// if(first == null || countNum < 1 || num < 1 || startNo < num){
// System.out.println(“输入的值有误,请重新输入。”);
// }
Child helper=first;
while(true){
if (helper.getNext()==first){
break;
}
else{
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();
}
//这时first指向的节点,就是要出圈的小孩节点
System.out.printf(“小孩%d出圈\n”, first.getNo());
//这时将first指向的小孩节点出圈,这里需要一个立体思维
first=first.getNext();
helper.setNext(first);
}
System.out.printf(“最后留在圈中的小孩编号%d \n”, first.getNo());
}
}
class Child {
//因为,单向链表和双向链表都是通过共有变量,即child.next=newchild来操作
最后
金三银四马上就到了,希望大家能好好学习一下这些技术点
学习视频:
[外链图片转存中…(img-Q1yCxEey-1650449188316)]
大厂面试真题:
[外链图片转存中…(img-07zsV26o-1650449188317)]