1 问题来源
据说著名犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。
2 举例说明
例:7个人竞选组长,这7个人围成一个圆圈,每个人的铭牌分别为0、1、2、3、4、5、6。然后这7个人按照如下规则进行报数直到选出组长。
-
起始点:从站位为0的成员开始报数;
-
报数规则:从数字1开始报数,报到数字2的成员退出竞选,然后剩下的人从当前退出竞选的成员后一位开始继续从1开始报数,。
-
终止准则:只剩下最后一人参与竞选即胜出。
-
竞选模拟流程如下:最终初始6号位的成员胜出,成员退出竞选的顺序为1–>3–>5–>0–>4 –>2
3 核心思想
上一轮进行约瑟夫环操作后最后剩下的数字和这一轮进行约瑟夫环操作后的最后剩下的数字保持一致
定义 F F F法则:
- 操作对象:按照从0开始顺序排列的数字
- 输出对象:进行约瑟夫环操作后最后剩下的数字。
例: F ( i , m ) F(i,m) F(i,m)是对 i i i个按照如下顺序排列的数字进行约瑟夫环操作后最后剩下的数字。
删除第m个数字后的约瑟夫环为 x = [ m , m + 1 , … , i − 1 , 0 , 1 , … , m − 2 ] x=[m,m+1,\dots,i-1,0,1,\dots,m-2] x=[m,m+1,…,i−1,0,1,…,m−2];
因为 F ( i − 1 , m ) F(i-1,m) F(i−1,m)是定义在以0为开始的序列进行约瑟夫环操作,因此我们需要将上一轮删除第 m m m个数字后得到的新顺序环 x x x映射为 g ( x ) = [ 0 , 1 , … , i − m − 1 , i − m , i − m + 1 , … , i − 2 ] g(x)=[0,1,\dots,i-m-1,i-m,i-m+1,\dots,i-2] g(x)=[0,1,…,i−m−1,i−m,i−m+1,…,i−2],才能使用 F F F法则,此时环中的数字个数为 i − 2 i-2 i−2。
定义H法则:对任意序列进行约瑟夫环操作后最后剩下的数字。
对序列 x x x使用 H H H法则得到的结果和 F ( i , m ) F(i,m) F(i,m)的得到结果相等,即有 F ( i , m ) = H ( i − 1 , m ) F(i,m)=H(i-1,m) F(i,m)=H(i−1,m)。
H
H
H法则的操作对象和
F
F
F法则的操作对象满足如下映射关系(思考如果我们不按照顺序编号是否能够得到更加简便的映射关系?)
g
(
x
i
)
=
{
x
i
−
m
,
i
f
m
≤
x
≤
i
−
1
x
i
+
i
−
m
,
i
f
0
≤
x
i
≤
m
−
2
(1)
g(x_i)= \begin{cases} x_i - m, & if \quad m \le x \le i-1\\ x_i + i-m,& if\quad 0 \le x_i \le m-2 \end{cases}\tag{1}
g(xi)={xi−m,xi+i−m,ifm≤x≤i−1if0≤xi≤m−2(1)
等价于
g
(
x
i
)
=
(
x
i
+
i
−
m
)
%
i
g(x_i) = (x_i + i - m)\% i
g(xi)=(xi+i−m)%i $ \Longrightarrow$
g
−
1
(
x
i
)
=
(
x
i
+
m
)
%
i
g^{-1}(x_i) = (x_i+m)\%i
g−1(xi)=(xi+m)%i
F
(
i
−
1
,
m
)
=
g
(
H
(
i
−
1
,
m
)
)
(2)
F(i-1,m)=g(H(i-1,m))\tag{2}
F(i−1,m)=g(H(i−1,m))(2)
H ( i − 1 , m ) = g − 1 ( F ( i − 1 , m ) ) = ( F ( i − 1 , m ) + m ) % i (3) \begin{aligned} H(i-1,m)& = g^{-1}(F(i-1,m))\\ & =(F(i-1,m)+m)\%i \tag{3} \end{aligned} H(i−1,m)=g−1(F(i−1,m))=(F(i−1,m)+m)%i(3)
所以
F
(
i
,
m
)
=
(
F
(
i
−
1
,
m
)
+
m
)
%
i
(4)
F(i,m) = (F(i-1,m)+m)\%i \tag{4}
F(i,m)=(F(i−1,m)+m)%i(4)
终止条件
F
(
1
,
M
)
=
0
F(1,M)=0
F(1,M)=0
因此,约瑟夫环操作过程满足如下递推公式
F
(
i
,
m
)
=
{
0
,
i
f
i
=
1
F
(
i
−
1
,
m
)
,
i
f
i
>
1
(5)
F(i,m)= \begin{cases} 0, & if \quad i=1\\ F(i-1,m),& if\quad i>1 \end{cases}\tag{5}
F(i,m)={0,F(i−1,m),ifi=1ifi>1(5)
4 代码实现
import java.util.LinkedList;
import edu.princeton.cs.algs4.Queue;
public class E1337 {
public static void main(String[] args) {
int N = 7;
int M = 2;
// System.out.println(josephusON(N, M));
// josephusON2(N, M);
josephusON3(N, M);
}
//模拟报数过程,时间复杂度O(M*N)
public static int josephusON(int N,int M){
Queue<Integer> Josephus = new Queue<>();
for (int i = 0;i < N;i++)
Josephus.enqueue(i);
while (Josephus.size() != 1) {
for (int i = 0; i < M-1; i++)
Josephus.enqueue(Josephus.dequeue());
System.out.print(Josephus.dequeue() + " ");
}
return Josephus.dequeue();
}
//约瑟夫环递推公式:s[1] = 0,s[i] = (s[i] + M) % i(i>1)
public static void josephusON2(int N,int M) {
int s = 0;
for(int i = 2;i <= N;i++){
s = (s + M) % i;
}
System.out.println(s);
}
public static int josephusON3(int N,int M) {
LinkedList<Integer> Josephus = new LinkedList<>();
for (int i = 0;i < N;i++) Josephus.add(i);
int outPos;
int k = 1;
while (Josephus.size() > 1){
outPos = (int) (k + M - 2) % Josephus.size();
System.out.print(Josephus.get(outPos)+" ");
Josephus.remove(outPos);
k = outPos + 1;
}
return Josephus.get(0);
}
}