josephus环公式法
约 瑟 夫 环 问 题 的 三 种 解 法

约瑟夫环问题python解法约瑟夫环问题:已知n个人(以编号1,2,3.n分别表示)围坐在一张圆桌周围。
从编号为k的人开始报数,数到k的那个人被杀掉;他的下一个人又从1开始报数,数到k的那个人又被杀掉;依此规律重复下去,直到圆桌周围的人只剩最后一个。
思路是:当k是1的时候,存活的是最后一个人,当k=2的时候,构造一个n个元素的循环链表,然后依次杀掉第k个人,留下的最后一个是可以存活的人。
代码如下:class Node():def __init__(self,value,next=None):self.value=valueself.next=nextdef createLink(n):return Falseif n==1:return Node(1)root=Node(1)tmp=rootfor i in range(2,n+1):tmp.next=Node(i)tmp=tmp.nexttmp.next=rootreturn rootdef showLink(root):tmp=rootwhile True:print(tmp.value)tmp=tmp.nextif tmp==None or tmp==root: def josephus(n,k):if k==1:print('survive:',n)root=createLink(n)tmp=rootwhile True:for i in range(k-2):tmp=tmp.nextprint('kill:',tmp.next.value) tmp.next=tmp.next.nexttmp=tmp.nextif tmp.next==tmp:print('survive:',tmp.value)if __name__=='__main__':print('-----------------')josephus(10,2)print('-----------------')josephus(10,1)print('-----------------')输出结果如下:-------------------------------------分界线-----------------------------------------感谢大家建议,第一种方法是直观暴力裸搞,确实不太简洁,下面写出我的第二种方法,求模来搞起,代码少了一些,如下:def josephus(n,k):if k==1:print('survive:',n)people=list(range(1,n+1))while True:if len(people)==1:p=(p+(k-1))%len(people)print('kill:',people[p])del people[p]print('survive:',people[0])if __name__=='__main__':josephus(10,2)josephus(10,1)运行结果和上面一样。
约瑟夫环公式

约瑟夫环公式约瑟夫环:编号从0开始,第⼀个出去的⼈是(k-1)%n,重新编号,出去的⼈的下⼀位编号为0,以此类推,最后⼀个出去的⼈的编号⼀定为0,f[1] = 0;当第⼀个⼈出去后,剩下n – 1 个⼈出去编号f[9] =(k - 1) % (n – 1), 还原原来队列编号(f[n - 1] + k) % (n – 1 + 1);1. 编号从0开始2. 每出去⼀个⼈重新编号3. 还原原排列公式:f[x] = (f[x] + k) % (x + 1) (不断+k模原⼈数+1直到原⼈数+1=n);得初始值n个⼈,数k个数第⼀个出去:剩n⼈;初始值:f[n] = (k - 1) % n;第⼆个出去:剩n - 1⼈;初始值:f[n - 1] = (k - 1) % (n - 1);第三个出去:剩n - 2⼈;初始值:f[n - 2] = (k - 1) % (n - 2);。
还原原排列编号有了初始值,接下来还原编号即可第⼀个出去:剩n⼈,⽆需还原第⼆个出去:剩n - 1⼈,f[n - 1] = (f[n - 1] + k) % n;第三个出去:剩n - 2⼈,f[n - 2] = (f[n - 2] + k) % (n - 1), f[n - 2] = (f[n - 2] + k) % n;。
就是不断 +k 模⼈数+1;直到⼈到n个代码#include <cstdio>#define N 100001int n,k;int f[N];int main(){scanf("%d%d", &n, &k);for(int i = n; i >= 1; --i){f[i] = (k - 1) % i;for(int j = i + 1; j <= n; ++j)f[i] = (f[i] + k) % j;printf("%d ", f[i] + 1);//输出编号+1,因为从0开始编号}return0;}。
约瑟夫环

约瑟夫环问题
主讲人:nuanran
Flavius Josephus
弗拉维奥·约瑟夫(37-100)是第一世纪时的 著名的犹太历史学家,也是军官及辩论家。 《犹太古史》(The Antiquities of the Jews):记录了由圣经创世记至公元66年的 犹太人历史,以旧约圣经为蓝图以及古人的 传说,编写而成的犹太巨著。由于当时的犹 太人散居各地,此书成为各地土生犹太人重 要学习典籍,亦为当代神学学者及历史学者 所采用。
约瑟夫环问题三
不要妄想再找到公式了,模拟是唯一的选择, 但是直接模拟的话,该算法的复杂度将达到 O(n^2). 事实上,我们可以用线段数对此做一个优化, 用线段数来统计每个区间上还剩下人的个数, 从而使算法的复杂度降低到O(n*logn)。 线段数??一棵平衡二叉树,它的每个节点 都是一个线段,这里就不做详细介绍了。
约瑟夫环问题三
问题描述:编号从1到n的n个人,站成一个环,每个 人手里拿着一个卡片,卡片上写着一个非零的数,首 先去掉编号为k的人,然后看他手里的卡片上的数字 m[k],如果m[k]>0,则去掉他左手边的第m[k]个人, 如果m[k]<0,则去掉他右手边的第m[k]个人。重复上 述步骤,直至只剩下一个人,问这个人的编号是多少。
约瑟夫环问题二
为了方便,在这里我们把这n个人的编号改为从0到n-1, 第一个去掉的人总是m%n-1,剩下n-1个人,这n-1个人 又组成了一个从第m%n个人开始的新的约瑟夫环问题。 m%n 0 m%n+1 1 … … n-1 n-m%n-1 0 n-m%n … … m%n-2 n-2
J(1,m)=0; J(n,m)=(m%n+J(n-1,m))%n, n>=2. 最后的结果加1就OK了。 这个问题可以用O(n)的算法去解决。
约瑟夫环知识点总结

约瑟夫环知识点总结1. 约瑟夫环的数学模型约瑟夫环可以用数学的方式进行建模和解决。
通常情况下,我们把约瑟夫环的问题理解为一个数学公式的求解。
假设n个士兵分别编号为1、2、3、...、n,m为出列的间隔数。
首先,我们可以得到第一个出列的士兵编号为(m-1)%n+1,例如当n=7,m=3时,第一个出列的士兵为(3-1)%7+1=3。
之后,每次出列后的编号变换规律为:下一个出列士兵的编号为前一个出列士兵编号加上m在n取模后的结果,并且再对n取模,即f(i)=f(i-1)+m)%n。
以上公式是解决约瑟夫环问题的核心,因为根据这个公式可以有效地计算出每一轮出列的士兵的编号。
然后我们只需要循环迭代这个公式,直到最后只有一个士兵为止,这个士兵的编号就是最后的结果。
2. 约瑟夫环的递归解法除了上述的数学模型,还可以使用递归的方法来解决约瑟夫环的问题。
递归是一种非常高效的解决问题的方法,适用于很多数学问题,包括约瑟夫环的计算。
递归方法的求解思路是:先假设已知了n-1个士兵的约瑟夫环问题的解f(n-1, m),那么我们要求的n个士兵的约瑟夫环的解f(n, m)可以通过以下方式推导得到。
首先,第一个出列的士兵编号为(m-1)%n+1,之后剩下的n-1个士兵重新排列成一个圆圈,编号重新从1到n-1。
将这n-1个士兵的解f(n-1, m)映射到n个士兵的解f(n, m)上,此时,再回到上述的数学模型进行计算,找到最终的结果。
递归的思路虽然清晰,但是在实际求解的过程中,由于递归的不断嵌套,计算量会非常庞大,不适合解决大规模的约瑟夫环问题。
3. 约瑟夫环的迭代解法在解决实际问题的时候,我们更多地使用迭代的方法来求解约瑟夫环的问题。
迭代的思路是从最简单的情况开始,然后不断迭代得到更加复杂的情况的解。
对于约瑟夫环问题,迭代的思路是逐步得出每一轮出列的士兵的编号并记录下来,直到剩下最后一个士兵为止。
通常情况下,我们会使用一个数组或者链表来保存每一轮出列的士兵的编号,最后得出最后一个士兵的编号。
(完整word版)约瑟夫(Joseph)环问题

约瑟夫问题的一种描述是:编号为1,2,…,n的n个人按顺时针方向围坐一圈,从1起报到k则出圈,下一个人再从1报起,如此下去直到圈中只有一人为止。
求最后剩下的人的编号。
【说明】1)建议用循环链表存储方式,设计循环链表类和约瑟夫类。
2)问题改进:在人数n、k及起始报数人确定的情况下,最后剩下的人的编号事前是可以确定的。
若每人有一个密码Ki(整数),留作其出圈后的报到Ki后出圈。
密码Ki可用随机数产生。
这样事前无法确定谁是最后一人。
#include<stdio.h>#include<stdlib.h>typedef struct Joseph{int num;int key;struct Joseph *next;} Joseph1;Joseph1 *CreatList(int n){Joseph1 *R,*p,*q;int i,k;R=p=(Joseph1*)malloc(sizeof(Joseph1));p->next=NULL;for(i=0;i<n-1;i++){q=(Joseph1*)malloc(sizeof(Joseph1));p->num=i+1;scanf("%d",&k);if(k<=0){printf("输入信息有误!");exit(0);}p->key=k;p->next=q;p=q;}q->num=n;scanf("%d",&k);if(k<=0){printf("输入信息有误!");exit(0);}q->key=k;q->next=R;R=q;return(R);}void DeleList(int n,Joseph1 *P,int m){Joseph1 *q,*t;q=P;int i,j;for(i=1;i<n;i++){for(j=1;j<m;j++)q=q->next;t=q->next;q->next=t->next;m=t->key;printf("删除的第%d个数是:",i);printf("%d\n",t->num);free(t);}printf("删除的最后一个数是:%d\n",q->num);free(q);}void main(){int m,n;Joseph1 *P;printf("请输入参加的人数: ");scanf("%d",&n);if(n<=0){printf("输入信息有误!");exit(0);}printf("请输入初始密码: ");scanf("%d",&m);if(m<=0){printf("输入信息有误!");exit(0);}printf("请输入每个人的密码: ");P=CreatList(n);DeleList(n,P,m);}。
约 瑟 夫 环 问 题 的 三 种 解 法

约瑟夫环问题的简单解法(数学公式法)关于约瑟夫环问题,无论是用链表实现还是用数组实现都有一个共同点:要模拟整个游戏过程,不仅程序写起来比较烦,而且时间复杂度高达O(nm),当n,m非常大(例如上百万,上千万)的时候,几乎是没有办法在短时间内出结果的。
我们注意到原问题仅仅是要求出最后的胜利者的序号,而不是要读者模拟整个过程。
因此如果要追求效率,就要打破常规,实施一点数学策略。
为了讨论方便,先把问题稍微改变一下,并不影响原意:问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人继续从0开始报数。
求胜利者的编号。
我们知道第一个人(编号一定是m%n-1) 出列之后,剩下的n-1个人组成了一个新的约瑟夫环(以编号为k=m%n的人开始):k k+1 k+2 … n-2, n-1, 0, 1, 2, … k-2并且从k开始报0。
现在我们把他们的编号做一下转换:k-2 – n-2k-1 – n-1解x’ —- 解为x注意x’就是最终的解变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解:例如x是最终的胜利者,那么根据上面这个表把这个x变回去不刚好就是n个人情况的解吗?!!变回去的公式很简单,相信大家都可以推出来:x’=(x+k)%n如何知道(n-1)个人报数的问题的解?对,只要知道(n-2)个人的解就行了。
(n-2)个人的解呢?当然是先求(n-3)的情况—- 这显然就是一个倒推问题!下面举例说明:假设现在是6个人(编号从0到5)报数,报到(2-1)的退出,即 m=2。
那么第一次编号为1的人退出圈子,从他之后的人开始算起,序列变为2,3,4,5,0,即问题变成了这5个人报数的问题,将序号做一下转换:现在假设x为0,1,2,3,4的解,x’设为那么原问题的解(这里注意,2,3,4,5,0的解就是0,1,2,3,4,5的解,因为1出去了,结果还是一个),根据观察发现,x与x’关系为x’=(x+m)%n,因此只要求出x,就可以求x’。
约瑟夫环问题的两种解法(详解)

约瑟夫环问题的两种解法(详解)约瑟夫环问题的两种解法(详解)题⽬:Josephus有过的故事:39 个犹太⼈与Josephus及他的朋友躲到⼀个洞中,39个犹太⼈决定宁愿死也不要被敌⼈抓。
于是决定了⾃杀⽅式,41个⼈排成⼀个圆圈,由第1个⼈开始报数,每报数到第3⼈该⼈就必须⾃杀。
然后下⼀个重新报数,直到所有⼈都⾃杀⾝亡为⽌。
然⽽Josephus 和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与⾃⼰安排在第16个与第31个位置,于是逃过了这场死亡游戏。
对于这个题⽬⼤概两种解法:⼀、使⽤循环链表模拟全过程⼆、公式法我们假设这41个⼈编号是从0开始,从1开始报数,第3个⼈⾃杀。
1、最开始我们有这么多⼈:[ 0 1 2 3 4 5 ... 37 38 39 40 ]2、第⼀次⾃杀,则是(3-1)%41=2 这个⼈⾃杀,则剩下:[ 0 1 3 4 5 ... 37 38 39 40 ]3、然后就是从编号为3%41=3的⼈开始从1报数,那么3号就相当于头,既然是头为什么不把它置为0,这样从它开始就⼜是与第1,2步⼀样的步骤了,只是⼈数少了⼀个,这样不就是递归了就可以得到递归公式。
想法有了就开始做:4、把第2步中剩下的⼈编号减去3映射为:[ -3 -2 0 1 2 ... 34 35 36 37 ]5、出现负数了,这样不利于我们计算,既然是环形,37后⾯报数的应该是-3,-2,那么把他们加上⼀个总数(相当于加上360度,得到的还是它)[ 38 39 0 1 2 3 ... 34 35 36 37 ]6、这样就是⼀个总数为40个⼈,报数到3杀⼀个⼈的游戏。
这次⾃杀的是第5步中的(3-1)%40=2号,但是我们想要的是第2步中的编号(也就是最初的编号)那最初的是多少?对应回去是5;这个5是如何得到的呢?是(2+3)%41得到的。
⼤家可以把第5步中所有元素对应到第2步都是正确的。
7、接下来是[ 35 36 37 38 0 1 2... 31 32 33 34 ]⾃杀的是(3-1)%39=2,先对应到第5步中是(2+3)%40=5,对应到第2步是(5+3)%41=8。
约瑟夫环——递推公式

0123456789 12345678910 4567891012 789101245 10124578 4578101
810145
45810
1045
104
4约瑟夫环——递推公式
递推公式:
f(N,M)=(f(N−1,M)+M)%N
f(N,M)表⽰,N个⼈报数,每报到M时杀掉那个⼈,最终
f(N−1,M)表⽰,N-1个⼈报数,每报到M时杀掉那个⼈,最终胜利者的编号
现在假设有10个⼈,报到3的⼈就会被杀掉,我们⽤数字给这⼗个⼈编号为
1 2 3 4 5 6 7 8 9 10
第·⼀⾏绿⾊那⾏是数组下标,第⼆⾏是每个⼈的编号
现在逆向推导
f(1,3):只剩最后⼀个⼈,胜利者的数组下标为0
f(2,3)=(f(1,3)+3)%2=1,只有两个⼈的时候,胜利者下标为1。
f(10,3)=3,因为我们数组下标是从0开始的,所以⼈的编号是下标+1,也就是4
那么这个公式是怎么推导的呢?
1.假设我们已经知道了10个⼈时,胜利者的下标为3,那下⼀次9个⼈时,胜利者的下标为多少?
其实就是10个⼈时杀掉了编号为3(即数组下标为2)的⼈后,后⾯的⼈都往前移动了3位,所以胜利者的下标由3变成了0
2.那我们倒过来我们知道9个⼈时,胜利者的下标为0,那10个⼈时胜利者的下标为多少?
其实这和上⾯的问题⼀样,这是这是上个问题的逆过程,就是把⼤家都往后移动3位,所以f(10,3)=f(9,3)+3,不过可能会出现数组越界所以要取模变成f(10,3)=(f(9,3)+3)%10
3.那么⼈数改为n报到m时就杀掉数组怎么移动呢
⼀样的,杀⼀⼈则后⾯的⼈的下标都往前移动m则,f(n,m)=(f(n-1,m)+m)%n。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
现在考虑一种 m 为 2 的特殊情形。 这时候有更简单的递归公式:
x = 2*n + 1 - (2*n+1-2*k)*2^log2((2*n)/(2*n+1-2*k))
其中,log2((2*n)/(2*n+1-2*k)) 为计算(2*n)/(2*n+1-2*k)以 2 为底的对数, 结果向下取整数。
联系 2^log2((2*n)/(2*n+1-2*k))整体,可以理解为将(2*n)/(2*n+1-2*k)向下 舍取到 2 的幂。有些地方把这中 运算称为地板函数,我们定义为 flp2,下面是 C 语言的实现:
unsigned flp2(unsigned x) { unsigned y; do { y = x; x &= x-1; }while(x); return y;
} 其中 x &= x-1;语句是每次把 x 二进制最右边的 1 修改为 0,直到最左边的 1 为止. 这种方法也可以用来计算 x 二进制中 1 的数目,当 x 二进制中 1 的数目比较小的 时候算法的效率很高。
m 为 2 的代码实现:
unsigned josephus2k(unsigned n, unsigned k) { unsiged t = (n<<1) - (k<<1) + 1; return (n<<1)+1 - t*flp2((n<<1)/t); }
unsigned josephus(unsigned m, unsigned n, unsigned k) { unsigned x = km; while(x <= n) x = (m*(x-n)-1)/(m-1); return x; }
unsigned flp2(unsigned x) { unsigned y; do { y = x; x &= x-1; }while(x); return y; }
有了这个公 式,我们要做的就是从 1-n 顺序算出 f[i]的数值,最后结果是 f[n]。因为实际生活 中编号总是从 1 开始,我们输出 f[n]+1
由于是 逐级递推,不需要保存每个 f[i],程序也是异常简单:
#i nclude <stdio.h>
main() { int n, m, i, s=0; printf ("N M = "); scanf("%d%d", &n, &m); for (i=2; i<=n; i++) s=(s+m)%i; printf ("The winner is %d\n", s+1); }
Out.println( "Survivor is " + x.val); } }
3. 递归公式
喜欢这个问题的朋友肯定不满足上面的方法,很想知道更简单的算法。 其实 Josephus 问题中的序列确实存在递归 的公式。但是递归公式的推导 比较麻烦,我就直接给出结果。如果想了解详细过程可以查阅相关资料。
Node t = new Node(1); Node x = t;
for (int i = 2; i <= N; x = (x.next=new Node(i++))); x.next = t;
while (x != x.next) { for (int i = 1; i < M; i++) x = x.next; x.next = x.next.next; }
从 二进制的角度可以理解为: 将 n 左移 1 位(即乘以 2),然后将最右端设置为 1(既加 1), 最后将左端的 1 置为 0(既减去 2*n 的向下取的 2 的幂)。
更简单的描述是将 n 的二进制表示循环右移动一位! 例如: n 为 1011001 -> 0110011 -> 110011
josephus 环公式法
Josephus(约瑟夫)问题的数学方法(转)2010-06-25 3:08Josephus 问 题
1. 问题的由来
Josephus 问题是以 10 世纪的著名历史学家 Flavius Josephus 命名的. 据说, Josephus 如果没 有数学才能, 他就不会在活着的时候出名! 在犹太人和古罗马人战争期间, 他是陷 如罗马人陷 阱的 41 个犹太反抗者之一. 反抗者宁死不做俘虏, 他们决定围成一个圆圈,且围绕圆圈来进行, 杀死所有第 3 个剩下的人直到没有一个人留下. 但是, Josephus 和一个不告发的同谋者感到自 杀是愚蠢的行为, 所以以他快速计算出在此恶性循环中他和他的朋友应该站的地方. 因此, 他们 活了下来...
Node t = new Node(1); Node x = t;
for (int i = 2; i <= N; x = (x.next=new Node(i++))); x.next = t;
while (x != x.next) { for (int i = 1; i < M; i++) x = x.next; x.next = x.next.next; } Out.println("Survivor is " + x.val); } }
5. m 为 2 的情况, k 为 n 的情形
该问题一般都是计算最
后一个被杀的人的位置。 现在考虑更为特殊的,m 为 2 的情况, k 为 n 的情形。
令 k=n 可以化简前边 m=2 的公式:
x = 2*n + 1 - (2*n+1-2*n)*2^log2((2*n)/(2*n+1-2*n)) 即,x = 2*n + 1 - 2^log2(2*n)
这 个算法的时间复杂度为 O(n),相对于模拟算法已经有了很大的提高。算 n,m 等于一百万, 一千万的情况不是问题了。可见,适当地运用数学策略,不仅可以让 编程变得简单,而且往往 会成倍地提高算法执行效率。
在网上看到这个,好像就是 wywcgs 高人 说的约瑟夫问题的高效解法!这个方法太妙了! 想到我们学校的 4022-Recursive Survival。想应该可以用这个方法解决。 刚 开始没太细想,就直接这个方法,结果肯定超时(两个数的范围在 2^63-1)。 就想这个约瑟夫问题的高效解法关键是找到第 n 跟 n-1 的递推关系 x‘=(x+k)%n。它是每次 去掉第一个人,在构成 一个新队列,而且队列有一定的规律 k k+1 k+2 ... n-2, n-1, 0, 1, 2, ... k-2。 而这道题是每 2 个人去掉一个,如果从整个队列 1 2 3 ...... n,来考虑,把整个队列都数一遍的 话,剩下的是: 1 3 5 7 ...... n-1 (n 为偶数,且下一次相当于从 1 开始重新数) 1 3 5 7 ...... n (n 为奇数,且下一个去掉的是 1,也就是说相当于从 3 开始数)。 由此得到关于 n 的递推公式 n=2*f(n/2)-1(n 为偶数); n=2*f(n/2)+1(n 为奇数). 这样即使 n=2^63-1,最多也只要 63 次递归. 当时,觉得已经想的差不 多了,一提交,还是超时呢! 一细想,忘了考虑 m,the times of the function nested。 如果 m=2^63-1,这么做是没法想像的。就如果 n=1 时,m 就不需要考虑,直接就可以出来. 如果 n 递减也比较快可以出来(只是自己认为如此)。如 果 n 个人时剩下的是 n,那就要把 m 都 用完。 所以加了个判断语句就 OK 呢! 结果真的给 AC 呢,用了 0.03 秒。 不过看了别人的提交 纪录,只用了 0.00 秒,而且我的占用内纯是别人的好几倍。
用代码实现为:
unsigned josephus2n(unsigned n) { return ((n-flp2(n))<<1)|1; }
===================
class Josephus
{ static class Node { int val; Node next; Node(int v) { val = v; } } public static void main(String[] args) { int N = Integer.parseInt(args[0]); int M = Integer.parseInt(args[1]);
无论是用链表实现还是用数组实现都有一个共同点:要模拟整个游戏过程,不仅程序写起来比较 烦,而且时间复杂度高达 O(nm),当 n,m 非常大(例如上百万,上千万)的时候,几 乎是没有 办法在短时间内出结果的。我们注意到原问题仅仅是要求出最后的胜利者的序号,而不是要读者 模拟整个过程。因此如果要追求效率,就要打破常规,实施 一点数学策略。 为了讨论方便,先把问题稍微改变一下,并不影响原意:
现在我们把他们的编号做一下转换:
k
--> 0
k+1 --> 1
k+2 --> 2
...
...
k-2 --> n-2
k-1 --> n-1
变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解:例如 x 是最 终的胜利者,那么根据 上面这个表把这个 x 变回去不刚好就是 n 个人情况的解吗?!!变回去 的公式很简单,相信大家都可以推出来:x‘=(x+k)%n