约瑟夫问题
据说著名犹太历史学家 Josephus有过以下的故事:
在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。所以Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
一般性描述
为了探索约瑟夫问题的奥秘,我们不妨现将问题简化。
已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为1的人开始报数,数到3的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌仅剩下一个人。
神级代码(摘抄)
#include<stdio.h>
int main()
{
int i,r=0,people;
scanf("%d",&people);
for(i=2;i<=people;i++)
r=(r+3)%i;
printf("%d\n",r+1);
return 0;
}
个人理解
上面的代码十分简洁地解决了这个问题,它的核心到底是什么,我来谈谈我的看法。研究这个问题,我们可以统一将编号减一(即0,1,2……n-1),数到2的人被淘汰,这样进行取余时可以与编号对应。
那我们先取n=10吧,首先将编号展开:
①【0,1,2,3,4,5,6,7,8,9】
由此可知 编号2将被淘汰。更新一次列表:
②【0,1,3,4,5,6,7,8,9】
此时报数将从编号3开始,我们不妨再将它设为0,即把所有编号减3,为了得到圆桌的循环,我们将前面的两个负数加上上一次的人数10,可以得到:
③【7,8,0,1,2,3,4,5,6】
继续淘汰2号,重复上一步的步骤:
④【4,5,6,7,0,1,2,3】
一直到最后我们得到【1,0】,这时候我们没有2了怎么办?
因为这是圆桌循环,模拟出来便是 编号0数0,编号1再数1,回到编号0数1(被淘汰),不难发现淘汰的人就是2%2=0。
再进一步我们可以推出当报数上限m-1(本例中为m-1=2)≥剩下的人数n时,淘汰的编号是(m-1)%n。
接下来编号1再变为0,得到【0】
此时的编号0就是胜利者。
而神级代码的核心就是从这个0出发,找到了它原来的编号,具体实现步骤如下:
比如,表④中的0加上3得到它在表③中的编号3,再加上3得到它在表②中的编号⑥。
- 从上面的分析中,我们知道每次报数我们都会减3产生一个新的列表,那么新编号加上3就可以得到它在上一列表中的编号。
- 我们从胜利者编号0出发,先加上3,但是上一列表显然只有0,1,我们从上面的分析中得到启示,那不妨这样处理:
(初处理)上一列表 【3】 补充完整后【0,1,2,3】
(实际步骤)上一列表【0,1,0(淘汰),1】
这样我们发现,越界后还是取余,被除数是上一列表的元素个数,这样就能得到正常的编号。
-
当没有发生越界情况时,我们仍然可以像步骤2那样取余,结果无影响。因此我们就知道了神级代码中的:
r=(r+3)%i 的具体含义 (r 初始化为0说明最后剩下编号0,对编号0进行回溯,i初始化为2是因为上一列表还有两个元素,然后i自增也就是列表中的元素慢慢增加,直到达到原来的人数,完成后记得r+1,毕竟一开始减了1)
进阶
神级代码解决了只剩1人时的情形,但是约瑟夫问题最后剩下2人,那又该如何处理呢?
不难发现,我们只需要对 i 与 r 进行一定的修改,即可达到目的(甚至达到更多的要求)。具体就不多说了,直接贴上代码。
#include<stdio.h>
int main()
{
int jose(int sum,int numb,int rest,int old);
int n,old,people,left;
printf("请输入总人数:\n");
scanf("%d",&people);
printf("请输入口令上限:\n");
scanf("%d",&n);
printf("请输入留下的人数:\n");
scanf("%d",&left);
printf("留下的人是:\n");
for(old=0;old<left;old++)
printf("%d\n",jose(people,n,left,old));
return 0;
}
int jose(int sum,int numb,int rest,int old)// sum是总人数,numb口令上限
{ //rest是要求剩下的人数,old是剩下的编号
int temp=rest;//temp作为临时变量,因为rest会修改
for(rest=temp+1;rest<=sum;rest++)
old=(old+numb)%rest;
return old+1;
}
由此不难发现为啥Josephus将朋友与自己安排在第16个与第31个位置,从而逃过了这场死亡游戏。
灵感来源
https://blog.youkuaiyun.com/okasy/article/details/79503398
感谢这位博主的启发!