约瑟夫问题的来历,先讲一个小故事
据说著名犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决。Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
简而言之,所谓的约瑟夫问题就是n个人围成一圈,从指定编号的人从1开始报数,数到m的人出列,他的下一个又从1开始报数,数到m的那个人继续出列,以此类推,直到所有人出列为止。
Tips
使用一个不带头结点的单向循环链表来处理约瑟夫问题,先构成一个有n个节点的单向循环链表,然后从i节点起从1开始计数,计到m时,对应节点从链表中删除,然后从被删除节点的下一节点开始继续上述操作,直到最后一个节点从链表中删除
首先创建一个Person节点
//创建一个Person类,表示一个节点
public class Person {
private int no;//编号
private Person next;//指向下一个节点,默认为null
public Person(int no) {
this.no = no;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public Person getNext() {
return next;
}
public void setNext(Person next) {
this.next = next;
}
}
创建单向环形链表:
//创建一个环形的单向链表
public class CircleSingleLinkedList {
//先创建一个first节点
private Person first = null;
/**
* 添加节点,构建成一个环形的链表
*
* @param nums 当前链表中的节点个数
*/
public void addBoy(int nums) {
//nums 做一个数据校验
if (nums < 1) {
System.out.println("数值错误!");
return;
}
//创建辅助节点,帮助构建环形链表
Person curPerson = null;
//利用for循环来创建环形链表
for (int i = 1; i <= nums; i++) {
//根据编号添加节点
Person person = new Person(i);
//判断是不是第一个节点
if (i == 1) {
first = person;
first.setNext(first);//构成环状
curPerson = first;//让curPerson指向第一个节点
} else {
curPerson.setNext(person);//将当前节点的下一节点指向新加入的节点
person.setNext(first);//将新添加入的节点的下一节点指向first节点,再次构成环状
curPerson = person;//将辅助节点后移
}
}
}
/**
* 根据用户的输入,计算出出圈的顺序
*
* @param startNo 从第几个节点开始数
* @param countNum 数几下
* @param nums 链表中节点的个数,需要进行校验
*/
public void countBoy(int startNo, int countNum, int nums) {
//先对数据进行校验
if (first == null || startNo < 1 || startNo > nums) {
System.out.println("输入有误!");
return;
}
//创建辅助节点,完成出圈任务
Person helper = first;
//当出这个while循环时,辅助节点helper即指向了最后一个节点
while (true) {
//当辅助节点的下一节点指向first节点时,则说明辅助节点指向了最后一个节点
if (helper.getNext() == first) {
break;
}
helper = helper.getNext();//将辅助节点向后移动
}
//在报数前,应先将first和helper移动startNo - 1 次
for (int i = 0; i < startNo - 1; i++) {
first = first.getNext();//此时first就指向了开始报号的节点
helper = helper.getNext();//此时helper就指向了开始报号的节点的前一个节点
}
//报数时,让first和helper同时移动countNum - 1次,然后出圈(这里是一个循环操作,直到链表中还剩下一个节点)
System.out.print("出圈的顺序为:");
while (true) {
if (helper == first) { // 说明圈中只有一个节点
break;
}
for (int i = 0; i < countNum - 1; i++) {
first = first.getNext();
helper = helper.getNext();
}
//此时first指向的节点就是要出圈的节点
System.out.print(first.getNo() + "->");
//这时将first指向的节点出圈
first = first.getNext();
helper.setNext(first);
}
System.out.print(+ first.getNo());
}
//遍历当前环形链表
public void showList() {
//判断链表是否为空
if (first == null) {
System.out.println("链表为空!");
return;
}
//创建一个辅助节点,来帮助完成遍历
Person curPerson = first;
System.out.print("链表顺序为:");
while (true) {
//如果当前节点的下一节点指向first,则说明遍历完毕
if (curPerson.getNext() == first) {
System.out.print(curPerson.getNo());
break;
}
System.out.print(curPerson.getNo()+"->");
curPerson = curPerson.getNext();//将当前节点后移
}
System.out.println();
}
}
测试代码如下:
public class CircleSingleLinkedListTest {
public static void main(String[] args) {
CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
circleSingleLinkedList.addBoy(5);//添加5个节点
circleSingleLinkedList.showList();//打印一下该循环链表
circleSingleLinkedList.countBoy(2,3,5);//从第二个节点开始报数,每数3次出一个节点,链表中的节点数共有5个。
}
}