约瑟夫问题(单向环形链表)
今天学习了一下数据结构中的约瑟夫问题。谈了一下自己的解决思路,如果有错误的地方,还望指出,我一定及时改正。
问题描述:有n个小孩围坐成一个圈玩游戏,每个小孩按1,2…n进行编号; 从 第k个小孩开始进行报数,数到第m个的小孩出列,然后从他的下一位继续数m个小孩,直到所有人出列,求出列的顺序。
package com.LinkedList.DoubleLinkedList;
/*
* 约瑟夫问题:
* 描述:有n个小孩围坐成一个圈玩游戏,每个小孩按1,2...n进行编号;
* 游戏规则:
* 从 第k个小孩开始进行报数,数到第m个的小孩出列,然后从他的下一位继续数m个小孩,直到所有人出列,求出列的顺序
*/
public class Josepfu {
public static void main(String[] args) {
// 创建单向环形单链表
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
// 创建5个小男孩
circleSingleLinkedList.addBoy(5);
circleSingleLinkedList.countBoy();
// 出圈
circleSingleLinkedList.removeBoy(1, 2, 5);
}
}
//定义一个单向的环形链表
class CircleSingleLinkedList {
// 定义一个first,用来记录链表的开头
private Boy first = new Boy(0);
/**
* 出圈
*
* @param start 从哪里开始报数
* @param count 每次数几个数
* @param nums 小男孩的个数(环形链表的长度)
*/
public void removeBoy(int start, int count, int nums) {
// 链表为空、开始位置小于0,开始位置大于总人数都不可以
if (start < 0 || start > nums || first == null) {
System.out.println("请输入正确的参数!");
return;
}
// 思路:
// 1.单向环形链表要想删除某个节点,必须要找到该节点的前一个节点;
// 2.first标记的是链表的开头,那么指针一开始要在first的前面一个节点,就是在链表的最后一个节点
// 3.从第start个位置开始报数,所以要将first的位置向前挪start-1次,指针的开始位置也应该跟着挪start-1次
// 4.每次报数count个,就是每次指针向前移动count-1个的位置就删除,
// 5.从删除位置的下一个节点重新报数,意味着下一次报数的开始位置也向前移动了count-1个,即first也要向前移动count-1
// 6.最后剩最后一个的时候,就结束了
Boy temp = first;// 定义一个指针
// 1.现先将指针移动到最后一个节点上
while (true) {
if (temp.getNext() == first) {
break;
}
temp = temp.getNext();
}
// 2.将指针temp和first一起向前挪start-1次
for (int i = 0; i < start - 1; i++) {
first = first.getNext();
temp = temp.getNext();
}
// 3.现在开始报数
while (true) {
if (temp == first) {// 说明就剩最后一个小男孩了
break;
}
// 4.每次报count个,也就是将指针temp和first同时挪count-1次
for (int i = 0; i < count - 1; i++) {
first = first.getNext();
temp = temp.getNext();
}
// 4.此时,first位置上的小男孩出圈,先记录下来
System.out.printf("编号为%d的小男孩出圈\n", first.getNo());
// 5.开始出圈
first = first.getNext();// 新的报数位置是出圈男孩的下一个
temp.setNext(first);// 将指针位置的节点的后指针指向新的起始位置,完成出圈
}
// 最后一个小男孩,记录下来
System.out.printf("最后留在圈中的小孩编号为%d\n", first.getNo());
}
/**
* 建立一个有nums个男孩的单向环形链表
*
* @param nums
*/
public void addBoy(int nums) {
if (nums < 1) {
System.out.println("请输入正确的参数!");
return;
}
// first用来标记第一个小男孩,不能移动,所以需要定义一个指针定位每次添加进来的男孩
Boy temp = null;
for (int i = 1; i <= nums; i++) {// 创建nums个小男孩
Boy boy = new Boy(i);
// 第一个小男孩创建后,需要将boy的后指针指向自己,自己形成环
if (i == 1) {
first = boy;
first.setNext(first);// 将自己形成环
temp = first;// 指针指向第一个小男孩
} else {
temp.setNext(boy);// 将当前小男孩的后指针指向新创建的小男孩
boy.setNext(first);// 将新创建的小男孩的后指针指向第一个小男孩
temp = boy;// 指针移动到新创建的小男孩身上
}
}
}
/**
* 遍历单向环形链表
*/
public void countBoy() {
if (first == null) {
System.out.println("链表为空,无法遍历!");
return;
}
Boy temp = first;// 指针
while (true) {
System.out.printf("编号为%d的小男孩\n", temp.getNo());
if (temp.getNext() == first) {// 遍历完毕
break;
}
temp = temp.getNext();
}
}
}
//定义一个小男孩模拟节点
class Boy {
private int no;// 编号
private Boy next;// 指向下一个男孩
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public Boy getNext() {
return next;
}
public void setNext(Boy next) {
this.next = next;
}
// 构造方法
public Boy(int no) {
this.no = no;
}
}