0
点赞
收藏
分享

微信扫一扫

数据结构系列第一部分:表

_铁马冰河_ 2022-04-21 阅读 49
  • 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】 时,可以使用稀疏数组来保存该数组。

稀疏数组的处理方法是:

  1. 记录数组一共有几行几列,有多少个不同的值;

  2. 把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模。

如下所示,可以将左边稀疏数组转换为右边的数组:

|

[](()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)增加节点

① 在链表末尾增加节点:

  1. 首先创建链表的头节点(headNode),头节点的作用仅仅是表示链表的头部;

  2. 创建一个临时节点,初始化为tempNode=headNode;

  3. 然后利用头节点不断遍历该链表,直到遍历到链表尾部(判断条件:tempNode.next=null)。遍历完链表的tempNode其实就相当于链表本身;

  4. 将新加入的数据放在链表尾部(tempNode.next=newHeroNode)。

② 在链表中按照序号加入节点:

  1. 首先创建链表的头节点(headNode),头节点的作用仅仅是表示链表的头部;

  2. 创建一个临时节点,初始化为tempNode=headNode;

  3. 然后利用头节点不断遍历该链表,直到遍历到新加入节点序号的前一位(判断条件:tempNode.next.no>newHeroNode.no);

  4. 按照下图所示插入节点:

在这里插入图片描述

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) 查

  1. 首先创建链表的头节点(headNode),头节点的作用仅仅是表示链表的头部;

  2. 创建一个临时节点,初始化为 tempNode=headNode;

  3. 利用临时节点不断遍历列表,然后通过重写 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 概念

为什么要用双向链表呢?因为,单向链表存在以下缺点:

  1. 单向链表的查找方向只能是一个方向,而双向链表可以向前或者向后查找;

  2. 单向链表不能自我删除节点,而需要靠辅助节点,而双向链表可以实现自己删除,但是在代码实现中还是需要定义 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)]

举报

相关推荐

0 条评论