经典问题约瑟夫环:有n个人围成一圈,第一个人从1开始报数,报m的人将被淘汰出圈,下一个人接着从1开始报。如此反复,直到最后留下一个胜利者。
例如有三个人甲、乙、丙,他们围成一圈,从甲开始报数,规则是报2的人被淘汰。第一轮,甲报1,乙报2,乙被淘汰;第二轮,丙接着报1,甲报2,甲被淘汰;最后丙获胜。
采用循环链表法可以解决问题,但时间复杂度较高,实现思路比较简单,这里不做讨论。接下来就递推公式法谈一谈我个人的理解,欢迎大家指正。
先给出递推公式: 。其中n表示共有n个人参加报数,m表示报到m的人被淘汰出局。
现以n取10,m取3为例,我们用数字编号1,2,3,4,5,6,7,8,9,10来表示这10个人,他们先排成一排,每报到3的人将被淘汰。
- 游戏开始,头一个人编号是1,从他开始报数,第一轮被淘汰的是编号为3的人。
- 编号为4的人从1重新开始报数,这时我们可以认为编号4这个人是队伍的头,新队伍有9个人,旧编号为4的这个人有了一个新编号是1。第二轮新编号是3旧编号为6的人被淘汰。
- 编号为7的人从1重新开始报数,这时我们可以认为原始编号7这个人是队伍的头,新队伍只有8个人了,原始编号为7,上一轮编号是4的人成为了新队伍的头,他现在的最新编号是1。第三轮原始编号为9的人被淘汰。
- ......
- 直到最后,原始编号为4的人获胜。
第一轮如图所示:
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |||
X | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 1 | 2 | xx | ||
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | xx |
可以观察得到规律:(1+3)%10=4 ; (2+3)%10=5 ; (3+3)%10=6 ; 但有一个问题:(7+3)%10=0!
基本符合公式 f(10,3)=(f(9,3)+3)%10,f(10,3)所代表的其实就是旧的编号,f(9,3)所代表的就是新的编号。通过递推公式,我们就能轻易的用新的编号来表示旧的编号,也就能用最新的编号(即n=1时)来表示最原始的编号了!
怎么样解决旧编号中最大的那个(即排在末尾的人的编号)无法用递推公式表示的问题呢?我们可以先从0开始编号,最后通过递推公式得出旧编号后再加一即可。
从0开始编号,第一轮如图所示:
旧 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |||
淘汰 | X | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | 1 | xx | ||
新 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | xx |
可以看出,现在完全符合递推公式了。(0+3)%10=3 ; (7+3)%10=0 ; (8+3)%10=1 。完全符合递推公式 f(10,3)=( f(9,3)+3)%10 。后续 f(9,3)=( f(8,3)+3)%9 ,直到最后 f(2,3)=( f(1,3)+3)%2 也完全正确。检验最后的式子如下表:
旧 | 0 | 1 | |
淘汰 | X | 1 | Xx |
新 | 0 |
这个递推公式的推导主要就在于新旧队伍编号之间的对应关系,每轮执行淘汰后,就要以接下来报1的人为新的队头,建立一个新队伍。然后想办法用新的编号来表示旧的编号,这样就可以不断用更新的编号来表示之前的旧编号,直到最后结束,编号无法再更新为止。找关系的过程中要特别注意是否有特殊情况,如果有特殊情况不满足一般关系式,就要想办法解决。除上述改变起始编号外,我以为或许还能用 if 语句判断是否为0来解决上述(7+3)%10=0 的问题,不过改变起始编号的方式更为巧妙。以上就是我对约瑟夫环问题的递归解决公式的理解。以下附上我的代码(以m=3为例)
package com.exerciseproblems.question49;
import java.util.Scanner;
//有 n 个人围成一圈,顺序排号。从第一个人开始报数
// (从 1 到 3 报数),凡报到 3 的人退出圈子,
// 问最后留下的是原来第几号的那位。
public class T49Plan1 {
public static void main(String[] args) {
System.out.print("请输入总人数 n :");
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
System.out.println("最后留下来的是原来的第 " + (winnerOfJoseph(n) + 1) + " 号。");
sc.close();
}
public static int winnerOfJoseph(int n) {
if (n == 1) {
return 0;
} else {
return (winnerOfJoseph(n - 1) + 3) % n;
}
}
}
运行结果如图: