约 瑟 夫 环 问 题 的 三 种 解 法 ( 2 0 2 0 )

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

约瑟夫问题(数学解法及数组模拟)

约瑟夫问题(有时也称为约瑟夫斯置换,是一个出现在计算机科学和数学中的问题。在计算机编程的算法中,类似问题又称为约瑟夫环。又称“丢手绢问题”.)据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决?Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。 ? 以上来自百度百科约瑟夫【导师实操追-女视频】问题是个很有名的问题:N个人围成一个圈,从第一个人开始报数,第M个人会被杀掉,最后一个人则为幸存者【Q】,其余人都将被杀掉。例如N=6,M=5,被杀掉的顺序是:5【1】,4,6,2,3,1。

约瑟夫【0】问题其实并不难,但求解的方法多种多样;题目的

变化形式【⒈】也很多。接下来我们来对约瑟夫问题进行讨论。

1.模拟【б】解法优点 : 思维简单。?缺点:时间复杂度高达O(m*【9】n) 当n和m的值较大时,无法短时间内得到答案。

为了叙述【5】的方便我们将n个人编号为:1- n ,用一个数组vis【2】来标记是否存活:1表示死亡 0表示存活 s代表当前死亡的人【6】数? cnt 代表当前报了数的人数用t来枚举每一个位置(当tn时 t=1将人首尾相连)? 那么我们不难得出核心代码如下:bool vis[1000]; --标记当前位置的人的存活状态

int t = 0; --模拟位置

int s = 0; --死亡人数

int cnt = 0; --计数器

if(t n) t = 1;

if(!vis[t]) cnt++; --如果这里有人,计数器+1

if(cnt == m) --如果此时已经等于m,这这个人死去

cnt = 0; --计数器清零

s++; --死亡人数+1

vis[t] = 1 --标记这个位置的人已经死去

coutt" "; --输出这个位置的编号

}while(s != n);

接下来我们来看另一种更为高效快速的解法数学解法

我们将这n个人按顺时针编号为0~n-1,则每次报数到m-1的人死去,剩下的人又继续从0开始报数,不断重复,求最后幸存的人最

初的编号是多少?我们只需要将最后求得的解加1就能得到原来的编号。

我们先给出递推公式:

?f(N,M)=(f(N?1,M)+M)%N

f(N,M)=(f(N?1,M)+M)%N

f(N,M)表示,N个人报数,每报到M时杀掉那个人,最终胜利者的编号

f(N?1,M)表示,N-1个人报数,每报到M时杀掉那个人,最终胜利者的编号

接下来我们开始进行解释

事实上,每次报到m-1的人出列以后,剩余的n-1个人又会组成新的约瑟夫环。

我们现在假设有11个人

依次编号为 1、2、3、4、5、6、7、8、9、10、11

假设每次报到3的人死去

刚开始时,第一个人开始报数,第一轮死去的人是3号。

编号为4的人又从1开始报数,这时编号为4的人是这个队伍的头,则第二轮死去的人是6号。

编号为7的人又从1开始报数,这时编号为7的人是这个队伍的头,则第三轮死去的人是9号。

第九轮时,编号为2的人开始重新报数,这时我们认为编号为2的人是这个队伍的头,这一轮死去的人是8号。

现在还剩下编号为2和7的两个人,编号为2的人从1开始报数,不幸的是这一轮他死去了

最后7号玩家活了下来

f(1,3):只有1个人了,他就是幸存者,他的下标位置是0 f(2,3) = (f(1,3)+3)%2 = 3%2 = 1 ,还有2个人的时候,幸存者的下标位置为1

f(3,3) = (f(2,3)+3)%3 = 4%3 = 1,还有3个人的时候,幸存者的下标位置为1

f(4,3) = (f(3,3)+3)%4 = 4%4 = 0,还有4个人的时候,幸存者的下标位置为0

f(11,3) = 6

是不是觉得很神奇呢?你还在质疑这个公式的正确性吗?

我们来看这个公式如何得到的呢?

1.假设我们已经知道在第一轮还剩11个人时,幸存者的下标位置是6。那么下一轮剩10个人的时候,幸存者的下标位置是多少呢?

聪明的你是否已经想到了呢?没错,下一轮这个幸存者的下标位置变为了3,其实,在第一轮编号为3的人死去了以后,因为下一轮是从下标位置为4号的人开始报数,那么其实第一轮在3号死去了以后,相当于每个人的位置都往前移动了3位。

2.假设我们已经知道在还剩10个人的时候,幸存者的下标位置为3。那么上一轮11个人的时候,幸存者的编号是多少呢?

其实这个问题可以看成是上一个问题的逆过程,相当于大家都往

后移动了3位,所以f(11,3)=f(10,3)+3,但是一直累加的话,人数可能会超过总人数,所以最后模上当前人数的个数,每死去一个人,下一个人成为第一个报数的人,就相当于把环向前移动了M位。若已知N-1个人时,幸存者的下标位置为f(N?1,M),则剩下N个人的时候,就是往后移动M位,(因为有可能人数越界,超过的部分会被接到头上(因为是一个环),所以还要模上N),则可以得到递推公式f(N,M) = (f(N?1,M)+M)%n

由此我们给出代码:

int ring(int n,int m)

int p = 0; --幸存者在最后一轮的编号是0

for(int i = 2; i = n; i++)

p = (p+m)%i; --i代表当前人数

return p+1; --最后的下标+1就是最开始的编号

while(tmp.getVal()!=cur.getVal()){

最后不要忘了删除那个不要的结点。对分配在堆上的对象,C++是要手动进行内存管理的。

k k+1 k+2 … n-2, n-1, 0, 1, 2, … k-2并且从k开始报0。

?--?pcur为当前结点,pre为辅助结点,指向pcur的前驱结点,head为头节点?

f(n,k)=((f(n-1,k)+k-1) mod n)+1

Scanner sc=new Scanner(System.in);

Node* josephuskill2(Node* pHead,int m)--第m个人被杀掉

相关文档
最新文档