目录
今日良言:总有人会成功,为什么不能是你呢?
一、问题描述
约瑟夫环问题是一个很经典的问题:一个圈共有N个人,第一个人的编号为1,第二个人的编号就为2号,第三个人的编号就为3号,第N个人的编号就为N号,现在提供一个数字M,从第一个人也就是1号开始报数,第二个人报的数是2,依次类推,报到M这个数字的人出局,紧接着从出局的这个人的下一个人重新开始从1报数,和上面过程类似,报到M的人出局,直到N个人全部出局,请问,这个出局的顺序是什么?
图文结合总归好理解一点,如下图就是上述问题:
我们假设有5个小孩围成一圈, 从1号小孩开始报数,每次报2个数字,由此可知出局顺序是:2 -> 4 -> 1 -> 5-> 3
二、 环形单链表
1.思路分析
由上述图片也可看出,小孩围成的圈就是一个环形,我们这里使用环形单链表来解决这个问题。
结点下标从1开始,然后以用户输入的数字结束,我们先通过遍历该单链表,得到最后一个结点cur,头结点时first,这里依旧假设是5个小孩,最后一个结点的小孩编号是5,我们要求报两个数,第一个小孩报1,第二个小孩报2,因此,我们先让第二个小孩出圈,出圈的过程其实也就是删除这个结点的过程,我们先输出这个小孩的编号,然后让first指向下一个结点,再让cur指向first即可,不难发现,这个出圈的过程是一个循环,而循环结束的条件就是只剩下一个结点 ,也就是 cur == first时。
分析可知:每次让cur和first走2-1 = 1步,因为first这个小孩需要报数。
2.解决步骤
第一步当然是我们亲爱滴单链表的创建了,分析问题可知,我们创建一个类CircleList 来表示这个单链表,同时我们创建一个类Node来表示结点,Node类中有两个私有属性,一个data(数据域),一个Node类型的next(指针域),构造方法以及get和set方法是必不可少的喔,如下代码:
结点类实现后,我们创建CircleList类,这个类首先有一个Node类型的私有属性first,可以表示结点,不用初始化,默认为null即可,如下代码:
private Node first;// 头结点
接下来我们开始进行各种操作:
添加数据
思路:用户输入一个数字data表示多少个孩子,然后循环创建结点,我们创建一个Node类型的curr用来帮助我们构建链表,当第一次创建一个data 等于1的结点时,将该结点赋给firs和curt,需要注意的是,一定要让first指向自己构成环,然后第二个结点创建好后,先让cur.next指向第二个结点,然后让第二个结点的next指向first构成环,然后再将第二个结点赋给cur,依次类推,如下代码:
public void add(int data) {
if (data < 1) {
System.out.println("data的值不正确");
return;
}
Node cur = null; // 辅助结点,帮助链接链表
for (int i = 1; i <= data; i++) {
// 根据编号创建结点
Node node = new Node(i);
// 如果是第一个结点
if (i == 1) {
first = node;
first.setNext(first);// 构成环形
cur = node;
} else {
cur.setNext(node);
node.setNext(first);// 让最后一个结点继续指向头结点
cur = node;
}
}
遍历单链表
思路:我们设置一个Node类型的cur,先将first赋给cur,然后让cur先打印data域的值,然后继续向后遍历打印,直到cur.getNext == first时结束循环。如下代码:
public void show() {
// 判空
if (first == null) {
System.out.println("链表为空,操作失败");
return;
}
Node cur = first;
while (true) {
System.out.println("小孩的编号是:"+cur.getData());
if (cur.getNext() == first) {
break;
}
cur = cur.getNext();
}
接下来就是最最重要的计算出圈的方法啦!!!
出圈方法:
思路:该方法有三个参数,第一个参数startNum表示从第几个小孩开始,第二个参数表示每次报countNum个数,第三个参数表示共多少个孩子。
我们首先创建一个Node类型的cur,遍历该单链表,让cur指向最后一个结点,然后让cur和first同时开始走,直到first指向当前的startNum结点结束,然后让cur和first同时开始走countNum -1 步(因为first也需要报数),当走完以后,此时first的位置就是需要出圈的小孩的编号,先让该小孩出圈,然后再让fitst指向它的下一个结点,再让cur指向first即可,不难看出这是一个循环,循环结束的条件是:只有一个结点时,结束循环,然后让输出该小孩的编号即可。如下代码:
public void count(int startNum,int countNum,int nums) {
// 检验各种数据的合法性
if (first == null || startNum < 1 || startNum > nums) {
System.out.println("输出的数据有误");
return;
}
// 创建一个辅助结点,指向最后一个结点
Node cur = first;
while (true) {
if (cur.getNext() == first) {
break;
}
cur = cur.getNext();
}
// 此时cur 就是最后一个结点
// 此时让cur和first一起走
while (true) {
// 说明此时就一个结点
if (cur == first) {
break;
}
for (int j = 0; j < countNum - 1; j++) {
first = first.getNext();
cur = cur.getNext();
}
// 此时first指的结点就是要出圈的结点
System.out.println("出圈的小孩编号是:"+first.getData());
// 然后删除这个结点
first = first.getNext();
cur.setNext(first);
}
System.out.println("最后出圈的小孩编号是:"+cur.getData());
}
3.效果展示
从1号小孩开始报数,每次报2个数
从1号小孩开始,每次报3个数
从4号小孩开始,每次报3个数
当然,上述操作可以封装成一个菜单哦
4.完整代码
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
}
class CircleList {
private Node first;// 头结点
public void add(int data) {
if (data < 1) {
System.out.println("data的值不正确");
return;
}
Node cur = null; // 辅助结点,帮助链接链表
for (int i = 1; i <= data; i++) {
// 根据编号创建结点
Node node = new Node(i);
// 如果是第一个结点
if (i == 1) {
first = node;
first.setNext(first);// 构成环形
cur = node;
} else {
cur.setNext(node);
node.setNext(first);// 让最后一个结点继续指向头结点
cur = node;
}
}
}
/**
* 遍历当前链表
*/
public void show() {
// 判空
if (first == null) {
System.out.println("链表为空,操作失败");
return;
}
Node cur = first;
while (true) {
System.out.println("小孩的编号是:"+cur.getData());
if (cur.getNext() == first) {
break;
}
cur = cur.getNext();
}
}
/**
* 出圈方法
* @param startNum 从几号孩子开始
* @param countNum 总共数几下
* @param nums 共多少个孩子
*/
public void count(int startNum,int countNum,int nums) {
// 检验各种数据的合法性
if (first == null || startNum < 1 || startNum > nums) {
System.out.println("输出的数据有误");
return;
}
// 创建一个辅助结点,指向最后一个结点
Node cur = first;
while (true) {
if (cur.getNext() == first) {
break;
}
cur = cur.getNext();
}
// 让first指向用户要求的那个结点,cur需要一起走
while (startNum - 1 > 0) {
first = first.getNext();
cur = cur.getNext();
startNum--;
}
// 此时cur 就是最后一个结点
// 此时让cur和first一起走
while (true) {
// 说明此时就一个结点
if (cur == first) {
break;
}
for (int j = 0; j < countNum - 1; j++) {
first = first.getNext();
cur = cur.getNext();
}
// 此时first指的结点就是要出圈的结点
System.out.println("出圈的小孩编号是:"+first.getData());
// 然后删除这个结点
first = first.getNext();
cur.setNext(first);
}
System.out.println("最后出圈的小孩编号是:"+cur.getData());
}
}
public class Josepfu {
public static void main(String[] args) {
CircleList circleList = new CircleList();
circleList.add(5);
System.out.println("显示5个小孩的编号");
circleList.show();
System.out.println("=================开始出圈===================");
circleList.count(4,3,5);
}
}