首先我们先明确什么是约瑟夫问题:
约瑟夫问题:设编号为1、2、....n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列
思路:
- 先创建一个环形链表,环形链表通过for循环来创建,关键要形成一个循环。
- 创建一个方法getCount(),计算出链表的有效数据的个数,通过循环遍历的方式获取链表的有效数据
作用:创建一个数组来存储出队编号的序列,长度为有效数据的个数
3.约瑟夫问题,首先传入两个参数k,m,k为开始报数的那个结点,m为出列的结点,所以我们需要先创建一个方法getCircleNode(),在初始报数时,获取到初始报数的那个结点.
4. 该问题解决为:先找到对应开始报数的那个结点,然后开始循环(即报数)m次(实际为m-1次,可以在代码中浏览),找到对应的结点后,在存储出队编号序列的数组中添加该结点的no值,即编号(有循环遍历,我们一般需要借助一个辅助结点cur,cur指向当前遍历的结点)。添加完该节点后,把该节点删除(故我们也需要创建一个结点删除的方法),然后cur后移,继续循环遍历,重复以上步骤,最终可获得的结果。
前期准备:
1.创建环形链表
/**
* 创建一个环形链表
*
* @param num 传入要创建的结点的个数
*/
public void addNode(int num) {
CircleNode cur = null;
for (int i = 1; i <= num; i++) {
if (first == null) {//还没有节点时
first = new CircleNode(i);
cur = first;//指向当前节点
} else {
CircleNode circleNode = new CircleNode(i);//创建结点对象
cur.next = circleNode;//让当前结点指向新创建的结点
circleNode.next = first;//形成循环
cur = circleNode;//向后递进
}
}
}
class CircleNode {
int no;//编号
CircleNode next;
public CircleNode(int no) {
this.no = no;
}
@Override
public String toString() {
return "CircleNode{" +
"no=" + no +
'}';
}
}
2.创建一个方法getCount(),计算出链表的有效数据的个数,通过循环遍历的方式获取链表的有效数据
/**
* 获取链表中有效数据的个数
*
* @return
*/
public int getCount() {
if (first == null) {
throw new RuntimeException("该链表中没有数据");
}
CircleNode cur = first.next;//接用辅助结点
int count;//记录有效数据的个数
if (cur != first) {
count = 2;//此时已经有两个数据
} else {//此时只有一个数据
count = 1;
return count;
}
//如果程序运行到此,说明此时至少有两个数据
while (cur.next != first) {
cur = cur.next;//向后递进
count++;//个数递进
}
return count;
}
3.约瑟夫问题,首先传入两个参数k,m,k为开始报数的那个结点,m为出列的结点,所以我们需要先创建一个方法getCircleNode(),在初始报数时,获取到初始报数的那个结点.
/**
* 查询对应序号的结点并返回
*
* @param k 查询的序号
* @return 返回对应的结点
*/
//这里在调用之前已经判断k是符合范围的,所以肯定能找到对应的结点,无需考虑找不到
public CircleNode getCircleNode(int k) {
CircleNode cur = first;//接用辅助结点
while (true) {
if (cur.no == k) {
return cur;
}
cur = cur.next;
}
}
4.创建一个删除结点的方法
/**
* 删除环形队列中的结点
*
* @param circleNode 要删除的结点
*/
public void deleteNode(CircleNode circleNode) {
if (first == null) {
System.out.println("该链表中没有数据");
return;
}
CircleNode cur = first;//借助辅助指针
if (first.no == circleNode.no) {//如果第一个数据就是要删除的结点
first = first.next;//需要将first向后移动
for (int i = 1; i < getCount(); i++) {
cur = cur.next;//找出最后链表中的最后一个数据
}
cur.next = first;//让链表中的最后一个数据指向新的first,继续形成循环
return;
}
boolean flag = false;//记录是否找到对应的结点
for (int i = 0; i < getCount(); i++) {//对链表的所有有效数据进行遍历
if (cur.next.no == circleNode.no) {//此时cur指向的是要删除结点的前一个结点(因为这是单向链表,删除要找到待删除结点的前一个结点)
flag = true;//找到后标记
break;
}
cur = cur.next;//向后递进
}
if (flag) {
cur.next = cur.next.next;//进行指针的向后递进,没被指向的结点会被java的垃圾处理机制gc()处理,即被删除的结点
}else{
System.out.println("没有该结点,无法删除");
}
}
最后实现约瑟夫问题的解决
/**
* 解决约瑟夫问题
*
* @param k 编号为k的人开始报数
* @param m 报数到m的人出列
* @return 返回一个出列编号的序列
*/
public int[] Josephu(int k, int m) {
if (k > getCount() || k <= 0) {//编号大于总的有效人数
throw new RuntimeException("输入的数:" + k + "有误");
}
int[] result = new int[getCount()];//记录出列序号,长度为有效数据的个数
int index = 0;//记录数组的指针
CircleNode curNode = null;//记录当前的结点
while (first.next != first) {//当链表中只剩下一个数据时,循环终止
if (curNode == null) {//初次判断curNode结点是否赋值
curNode = getCircleNode(k);//如果没赋值,就通过getCircleNode()方法获取到对应编号的数。
}
for (int i = 1; i < m; i++) {
curNode = curNode.next;//循环m-1次,因为在下面的代码中已经移动到下一个开始计数的点了
}
deleteNode(curNode);//要删除已找到的这个结点
//经过循环后获取到对应的出列的人
result[index++] = curNode.no;
curNode=curNode.next;//把curNode指针向后移动一位,故只需循环m-1次
}
result[index] = first.no;
return result;
}
即问题解决。
完整代码附上:
package com.liu.linkedlist;
import java.util.Arrays;
/**
* @author liuweixin
* @create 2021-09-07 19:00
*/
//约瑟夫问题:设编号为1、2、....n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,
// 数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,
// 由此产生一个出队编号的序列
//使用没有头结点的链表来实现
public class JosephuLinkedList {
private CircleNode first;//创建一个first节点
public static void main(String[] args) {
JosephuLinkedList list = new JosephuLinkedList();
list.addNode(5);
list.show();
System.out.println("约瑟夫问题的出列序号");
System.out.println(Arrays.toString(list.Josephu(1,2)));
list.Josephu(1, 2);
}
/**
* 解决约瑟夫问题
*
* @param k 编号为k的人开始报数
* @param m 报数到m的人出列
* @return 返回一个出列编号的序列
*/
public int[] Josephu(int k, int m) {
if (k > getCount() || k <= 0) {//编号大于总的有效人数
throw new RuntimeException("输入的数:" + k + "有误");
}
int[] result = new int[getCount()];//记录出列序号,长度为有效数据的个数
int index = 0;//记录数组的指针
CircleNode curNode = null;//记录当前的结点
while (first.next != first) {//当链表中只剩下一个数据时,循环终止
if (curNode == null) {//初次判断curNode结点是否赋值
curNode = getCircleNode(k);//如果没赋值,就通过getCircleNode()方法获取到对应编号的数。
}
for (int i = 1; i < m; i++) {
curNode = curNode.next;//循环m-1次,因为在下面的代码中已经移动到下一个开始计数的点了
}
deleteNode(curNode);//要删除已找到的这个结点
//经过循环后获取到对应的出列的人
result[index++] = curNode.no;
curNode=curNode.next;//把curNode指针向后移动一位,故只需循环m-1次
}
result[index] = first.no;
return result;
}
/**
* 查询对应序号的结点并返回
*
* @param k 查询的序号
* @return 返回对应的结点
*/
//这里在调用之前已经判断k是符合范围的,所以肯定能找到对应的结点,无需考虑找不到
public CircleNode getCircleNode(int k) {
CircleNode cur = first;//接用辅助结点
while (true) {
if (cur.no == k) {
return cur;
}
cur = cur.next;
}
}
/**
* 获取链表中有效数据的个数
*
* @return
*/
public int getCount() {
if (first == null) {
throw new RuntimeException("该链表中没有数据");
}
CircleNode cur = first.next;//接用辅助结点
int count;//记录有效数据的个数
if (cur != first) {
count = 2;//此时已经有两个数据
} else {//此时只有一个数据
count = 1;
return count;
}
//如果程序运行到此,说明此时至少有两个数据
while (cur.next != first) {
cur = cur.next;//向后递进
count++;//个数递进
}
return count;
}
/**
* 遍历链表
*/
public void show() {
if (first == null) {
System.out.println("该链表中没有数据");
return;
}
System.out.println(first);//先打印首个结点
CircleNode cur = first.next;//接用辅助结点
while (cur != first) {//借用首个结点进行循环终止条件
System.out.println(cur);
cur = cur.next;
}
}
/**
* 删除环形队列中的结点
*
* @param circleNode 要删除的结点
*/
public void deleteNode(CircleNode circleNode) {
if (first == null) {
System.out.println("该链表中没有数据");
return;
}
CircleNode cur = first;//借助辅助指针
if (first.no == circleNode.no) {//如果第一个数据就是要删除的结点
first = first.next;//需要将first向后移动
for (int i = 1; i < getCount(); i++) {
cur = cur.next;//找出最后链表中的最后一个数据
}
cur.next = first;//让链表中的最后一个数据指向新的first,继续形成循环
return;
}
boolean flag = false;//记录是否找到对应的结点
for (int i = 0; i < getCount(); i++) {//对链表的所有有效数据进行遍历
if (cur.next.no == circleNode.no) {//此时cur指向的是要删除结点的前一个结点(因为这是单向链表,删除要找到待删除结点的前一个结点)
flag = true;//找到后标记
break;
}
cur = cur.next;//向后递进
}
if (flag) {
cur.next = cur.next.next;//进行指针的向后递进,没被指向的结点会被java的垃圾处理机制gc()处理,即被删除的结点
}else{
System.out.println("没有该结点,无法删除");
}
}
/**
* 创建一个环形链表
*
* @param num 传入要创建的结点的个数
*/
public void addNode(int num) {
CircleNode cur = null;
for (int i = 1; i <= num; i++) {
if (first == null) {//还没有节点时
first = new CircleNode(i);
cur = first;//指向当前节点
} else {
CircleNode circleNode = new CircleNode(i);//创建结点对象
cur.next = circleNode;//让当前结点指向新创建的结点
circleNode.next = first;//形成循环
cur = circleNode;//向后递进
}
}
}
}
class CircleNode {
int no;//编号
CircleNode next;
public CircleNode(int no) {
this.no = no;
}
@Override
public String toString() {
return "CircleNode{" +
"no=" + no +
'}';
}
}