约瑟夫环历史
约瑟夫环问题(Joseph Ring)是以弗拉维奥·约瑟夫命名的,它是1世纪的一名犹太历史学家。
他在自己的日记中写道,他和他的40个战友被罗马军队包围在洞中,最终决定自杀,并以抽签的方式决定谁杀掉谁。约瑟夫斯和另外一个人是最后两个留下的人。约瑟夫斯说服了那个人向罗马军队投降,不再自杀。约瑟夫斯把他的存活归因于运气或天意。
问题描述
已知n个人(以编号1,2,3…n分别表示)围坐在一张圆桌周围。从编号1的人开始报数,数到k的人出局;下一个人又从1开始报数,数到k的人出局。依此规律重复下去,直到只剩下一个人。求每次出局的人的编号及最后剩下的人的编号。
解析:题目要求输出每次出局的人的编号,因此采用模拟法模拟过程。
以5个人为例,从第一个人开始报数,数到3的人出局。
循环链表解法
可以采用循环链表解决约瑟夫环问题。示例如下:
public class Joseph {
private static Node head;
static class Node {
int number;
Node next;
}
/**
* 创建循环链表
* @param n 节点个数
*/
private static void create(int n) {
head = new Node();
head.number = 1;
Node p = head, q;
for (int i = 1; i < n; i++) {
q = new Node();
q.number = i + 1;
p.next = q;
p = q;
}
p.next = head;
}
/**
* 开始约瑟夫环淘汰过程
* @param k 淘汰号
*/
private static void start(int k) {
Node node = head;
int t = 1;
while (node.next != node) {
while (t < k - 1) {
node = node.next;
t++;
}
System.out.println("本次出局编号:" + node.next.number);
node.next = node.next.next;
node = node.next;
t = 1;
}
System.out.println("===============");
System.out.println("幸存者编号:" + node.number);
}
/**
* @param total 总人数
* @param number 淘汰号
*/
public static void run(int total, int number) {
if (number <= 1) {
System.out.println("请输入正确数字(大于1)!");
return;
}
System.out.println("总人数(编号1-5):" + total + "\t" + "淘汰号:" + number);
create(total);
start(number);
}
public static void main(String[] args) {
int total = 5;
int number = 3;
run(total, number);
}
}
输出结果:
总人数(编号1-5):5 淘汰号:3
本次出局编号:3
本次出局编号:1
本次出局编号:5
本次出局编号:2
=============
幸存者编号:4
迭代法求解
模拟法可以输出每次出局人的编号。如果题目不需要输出每次出局人的编号,可以采用迭代法求解。相比模拟法,迭代法的效率更高。
该算法的主要思想是寻找递归关系式。假设现在淘汰号为k,当只剩下幸存者时,其在数组中的下标为0,即 f(1,k) = 0。能否根据 f(1,k) 求出剩下两个人时,幸存者在数组中的下标,即 f(2,k) 的值呢?
详细讲解可以看下参考资料,在此只讲结果:f(2,k) = (f(1,k) + k) % 2
推广到一般情况,即
f (n,k) = ( f (n - 1,k) + k ) % n
我们可以利用这个公式推出幸存者最初在数组中的编号。
示例如下:
public class Joseph1 {
public static int quickSolve(int total, int number) {
int p = 0;
for (int i = 2; i <= total; i++) {
p = (p + number) % i;
}
//因为编号从1开始,所以p需要加1
return p + 1;
}
public static void main(String[] args) {
int total = 5;
int number = 3;
System.out.println("总人数(编号1-5):" + total + "\t" + "淘汰编号:" + number);
System.out.println("幸存者编号:" + quickSolve(total,number));
}
}
输出结果:
总人数(编号1-5):5 淘汰编号:3
幸存者编号:4
参考资料: