1. 介绍
约瑟夫环大概介绍如下:n个人围成一圈,第一个人从1开始报数,报m的人出局,下一个人接着从1开始报数,如此循环,直到所有人出局。详细介绍看这里。
解决约瑟夫环问题,我觉得可以分两种情况。
-
只求结果,即只要求算出最后出局的人。
-
求全过程,即要算出每轮出局的人。
2. 情况一:只求结果
先介绍约瑟夫问题的两种特例,约定符号含义:n代表总人数,m表示报该数的人出局,给这n个人从1开始编号(注:这里从1开始编号是为了方便理解)。
2.1 特例1:n是2的次幂,m是2
-
当n是2时,最后出局的人(胜利者)显然是1。
-
当n是4时,那么出局顺序为2、4、3,胜利者是1。
-
当n是8时,那么出局顺序为2、4、6、8、3、7、5,胜利者是1。
-
......
不难发现,在这种特例下,胜利者始终是1号。
2.2 特例2:n是任意数,m是2
比如当n为12,m为2时,其约瑟夫环如下图所示:
当8号出局后,就剩下8人了,而2^3=8,于是此时的情况就符合特例1了。那么8号的下一位9号就相当于特例1里的1号,所以9号就是最终胜利者。
综上所述,当m为2时,解决约瑟夫环问题的思路就是找出什么时候剩下的人数为2的次幂,其下一轮报1的人就是最终胜利者。
2.3 任意情况:n和m都是任意数
在这种情况下,解决问题的方法就是使用递归。为了方便计算,这里从0开始编号,即0~(n-1)。比如当n=7(即编号为0~6),m=3时,其每次出局的情况如下图所示:
每出局一人,就把剩余的所有人都往左移m位(这里的m是3),这样每次的出局情况就用黄色格子标识。显然可以看出,无论什么情况下,最后胜利者(红字)所在的位置必定是0号位,那么他在上一轮(第6轮)的位置就是(0+3)%2=1。这里"+3"的含义就是右移,因为他是通过左移来到这轮的,要想返回到上一轮就得右移,但要考虑越界的情况所以要对n求余,n是该轮所剩人数。在第5轮的位置就是(1+3)%3=1,如此循环就能得出胜利者在刚开始的位置。
至此,计算约瑟夫环结果的思路已全部给出,下面给出实现的Java代码。
2.4 Java代码
public class Test01 {
/**
* 约瑟夫环问题,只求结果
* @param n 该轮所剩的人数
* @param m 报该数的人出局
* @return 胜利者的下标,从0开始
*/
public static int josephus(int n, int m) {
if (m == 2) { // m=2时的特殊情况
int k = 1;
while (k <= n) {
k = k << 1;
}
k = k >> 1;
return (n - k) * 2;
} else { // 普遍情况,使用递归
if (n == 1) {
return 0;
} else {
return (josephus(n-1, m) + m) % n;
}
}
}
public static void main(String[] args) {
// 由于函数返回的是从0开始编号的下标,所以还要再加1
System.out.println(josephus(7, 3) + 1);
}
}
3. 情况二:求全过程
3.1 思路
解决方案是用数组模拟出局情况。大致思路如下:
- 定义一个count变量,初始值为1,用于报数
- 遍历数组,每访问一个新的元素,count自增,当count等于m时,则给该元素做标记,输出该元素的位置并把count置为1
- 若当前访问的元素已做过标记,则跳过该元素,继续访问下一个元素
- 重复上述的第2步和第3步,直至数组的所有元素都做过标记
3.2 Java代码
public class Test02 {
/**
* 约瑟夫环问题,求全过程
* @param n 总人数
* @param m 报该数的人出局
*/
public static void josephus(int n, int m) {
int[] nums = new int[n + 1];
int count = 1;
int flag = 0;
for (int i = 0; i < nums.length; i++) {
nums[i] = i;
flag += i;
}
while (flag > 0) {
for (int i = 1; i <= n; i++) {
if (nums[i] == 0) {
continue;
}
nums[i] = count;
count++;
if (nums[i] == m) {
System.out.printf(i + " ");
count = 1;
nums[i] = 0; // 对该元素做标记,下次遍历时跳过该元素
flag -= i;
}
}
}
}
public static void main(String[] args) {
josephus(7,3);
}
}
4. 结束语
感谢您的阅读,个人认为约瑟夫环问题的一些基本情况已全部罗列出来了,需要注意的是写代码时编号的换算。笔者能力有限,代码也未必是最优的,还请各位多多包涵!