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,……n的n个人按照顺时针方向围坐一圈。
现在从第s个人开始顺序报数,数到第m的人出列,然后从出列的下一个人重新开始报数,数到第m的人又出列,……如此反复,直到所有的人都出列为止。
解决思路:(1)首先利用线性表的一些运算如创建空线性表、插入元素等构造Josephus表。
(2)从Josephus表中的第s个结点开始寻找、输出和删除表中的第m个结点,然后再从该结点的下一个结点开始寻找、输出和删除表中的第m个结点,重复此过程,直到表中的所有元素都删除。
算法基本思想:用整数i来代替n i,将初始序列改写成一个整数的序列:1,2,3,……,n,并把它们存储在一个palist所指向的顺序表中,当s<=n时,第s个人放在palist->elem[s-1]之中,因此第一个报数出列的应该是下标为s-1+m-1对n取模后的元素,如果这个下标为i,出列工作只要将palist->elem[i]从顺序表中删除,然后对palist->elem[0],palist->elem[1],……,palist->elem[n-2]从下标i开始重复上述过程。
程序清单:#include<stdio.h>#include<stdlib.h>#define FALSE 0#define TRUE 1typedef int DataType;struct SeqList{int MAXNUM;int n;DataType *elem;};typedef struct SeqList *PSeqList;PSeqList createNullList_seq(int m){PSeqList palist=(PSeqList)malloc(sizeof(struct SeqList));if(palist!=NULL){palist->elem=(DataType *)malloc(sizeof(DataType)*m);if(palist->elem){palist->MAXNUM=m;palist->n=0;return palist;}elsefree(palist);}printf("Out of space!!\n");return NULL;}int isNullList_seq(PSeqList palist){return(palist->n==0);}int locate_seq(PSeqList palist,DataType x){int q;for(q=0;q<palist->n;q++)if(palist->elem[q]==x)return(q);return -1;}int insertPre_seq(PSeqList palist,int p,DataType x){int q;if(palist->n>=palist->MAXNUM){printf("Overflow!\n");return 0;}if(isNullList_seq(palist)){palist->elem[0]=x;palist->n=1;return 1;}if(p<0||p>palist->n){printf("Not exist!\n");return 0;}for(q=palist->n-1;q>=p;q--)palist->elem[q+1]=palist->elem[q];palist->elem[p]=x;palist->n=palist->n+1;return 1;}int deleteP_seq(PSeqList palist,int p){int q;if(p<0||p>palist->n-1){printf("Not exist!\n");return 0;}for(q=p;q<palist->n-1;q++)palist->elem[q]=palist->elem[q+1];palist->n=palist->n-1;return 1;}void josephus_seq(PSeqList palist,int s,int m){int s1,i,w;s1=s-1;for(i=palist->n;i>0;i--){s1=(s1+m-1)%i;w=palist->elem[s1];printf("Out element %d\n",w);deleteP_seq(palist,s1);}}main(){PSeqList jos_alist;int i;int n,s,m;printf("\n please input the values(<100) of n=");scanf("%d",&n);printf("please input the values of s=");scanf("%d",&s);printf("please input the values of n=");scanf("%d",&m);jos_alist=createNullList_seq(n);if(jos_alist!=NULL){for(i=0;i<n;i++)insertPre_seq(jos_alist,i,i+1);josephus_seq(jos_alist,s,m);free(jos_alist->elem);free(jos_alist);}}运行结果:#include<stdio.h>#include<stdlib.h>#define FALSE 0#define TRUE 1typedef int DataType;//定义datatype为int型struct SeqList //定义链表结构体{int MAXNUM; //报数的最大值int n; //链表中结点个数DataType *elem; //一个指向数组的指针};typedef struct SeqList *PSeqList;PSeqList createNullList_seq(int m) //创建空表{PSeqList palist=(PSeqList)malloc(sizeof(struct SeqList));//定义结构体指针if(palist!=NULL){palist->elem=(DataType *)malloc(sizeof(DataType)*m);//给elme开空间if(palist->elem){palist->MAXNUM=m;palist->n=0;return palist;}elsefree(palist);}printf("Out of space!!\n");return NULL;}int isNullList_seq(PSeqList palist)//判断链表是否为空{return(palist->n==0);}int locate_seq(PSeqList palist,DataType x)//返回x节点的位置{int q;for(q=0;q<palist->n;q++)if(palist->elem[q]==x)return(q);return -1;}int insertPre_seq(PSeqList palist,int p,DataType x)//插入x,到位置p上{int q;if(palist->n>=palist->MAXNUM){printf("Overflow!\n");return 0;}if(isNullList_seq(palist)){palist->elem[0]=x;palist->n=1;return 1;}if(p<0||p>palist->n){printf("Not exist!\n");return 0;}for(q=palist->n-1;q>=p;q--)palist->elem[q+1]=palist->elem[q];palist->elem[p]=x;palist->n=palist->n+1;return 1;}int deleteP_seq(PSeqList palist,int p){int q;if(p<0||p>palist->n-1){printf("Not exist!\n");return 0;}for(q=p;q<palist->n-1;q++)palist->elem[q]=palist->elem[q+1];//让p后面的每一个节点前移一位palist->n=palist->n-1;return 1;}void josephus_seq(PSeqList palist,int s,int m)//最重要的函数,按顺序输出出队的号{int s1,i,w;s1=s-1;for(i=palist->n;i>0;i--){s1=(s1+m-1)%i;//这个关系式,可以得出要出队的人的位置w=palist->elem[s1];printf("Out element %d,,s1=%d,,i=%d,,m=%d\n",w,s1,i,m);deleteP_seq(palist,s1);//删除s1节点}}main(){PSeqList jos_alist;int i;int n,s,m;printf("\n please input the values(<100) of n=");scanf("%d",&n);printf("please input the values of s=");scanf("%d",&s);printf("please input the values of m=");scanf("%d",&m);jos_alist=createNullList_seq(n);if(jos_alist!=NULL){for(i=0;i<n;i++)insertPre_seq(jos_alist,i,i+1);josephus_seq(jos_alist,s,m);free(jos_alist->elem);free(jos_alist);}}。
约瑟夫环问题的三种解法
约瑟夫环问题的三种解法约瑟夫问题是个著名的问题:N个⼈围成⼀圈,第⼀个⼈从1开始报数,报到k的⼈将被杀掉,接着下⼀个⼈⼜从1开始报,直到最后剩下⼀个,求最后留下的⼈的下标。
题⽬集合解法1:暴⼒可以直接暴⼒求解,时间复杂度为O(nk)解法2:递推设f(n,k)为当n个⼈围成⼀圈时,最后留下的⼈的下标。
对于f(n-1,k)来说,其结果相当于f(n,k)的结果向前移动k\%(n-1)位。
因为对于f(n,k)来说,去掉第⼀轮报的数(k\%n)后,现在就只剩下n-1个数,并且是以(k\%(n-1)+1)作为第⼀个数,即所有数向前移动k\%(n-1)位。
现在的结果就为f(n-1,k)对于f(5,3)来说,其结果为4。
当其去掉第⼀轮报的数后,其向前移动了(3\%4)位,以4为起始,f(4,3)结果为1,对应着f(5,3)的结果4向前移动了3位所以反过来看即为,即为f(n-1,k)的结果向后移动k\%(n-1)位即f(n+1,k)=(f(n,k)+k\%n)\%n (x下标从0开始,因为取模结果为[0,n-1])时间复杂度为O(n)ll josephus2(ll n,ll k){ll pos=0;for(int len=1;len<=n;len++){pos = (pos+k)%len;}return pos+1;}递推代码解法3:如果当前这⼀位⼈没被杀掉,则他可以放在幸存者的末尾,直到幸存者数量为1所以对于下标为i的⼈,如果在他前⾯已经被杀掉了q个⼈,那么他的新的下标为n+q(k-1)+x,(1\leq x <k)如下图所⽰,最后被淘汰的编号⼀定是n*k,所以幸存者最后的编号是n*k我们现在需要从幸存者最后的编号中恢复出最初编号假设幸存者这⼀次的编号为p os_{i},在他后⾯包括他还有x位幸存者,则[pos_{i-1},pos_{i})间⼀定有x个不能被k整除的数这样才能使在他后⾯包括他还有x位幸存者。
约瑟夫环问题(Josephus)
• b[c]=a[i]; • a[i]=0; • c++; • if(c==n) break; • } • System.out.print(“最后出列的 3人: "); • this.show(b,g); • } • }
• 1.数据选择: 要求:n<2^15; 1<=k<=n; 2.数据和结果显示:
问题描述
• 已知n(<2^15)个人(以编号1,2,…,n 分别表示)围坐在一圆桌上,从编号为k (1≤ k≤ n)的人开始报数,数到m的那个 人出列,他的下一个人又从1开始报数,数 到m的那个人又出列,依此重复,直到圆桌 周围的人全部出列,依次输出最后个整数T(<2^15)表示测 试次数,接着第二到T+1行分别为n,m和k 的值。 例:2 10 2 3
• public void SortArray(int[]a,int n,int m,int k,int g){ • int[] b=new int[n]; • int c=0; • int i=k-2; • while(true){ • for(int j=0;j<m;){ • i=(i+1)%n; • if(a[i]!=0){ • j++; • } • }
总人数n 起始号码k 循环数m 最后出列的3人 总人数n 起始号码k 5 2 2 2 1 4 20 6
数据测试
总人数n 起始号码k 循环数m 最后出列的3人 总人数n 起始号码k 循环数m 最后出列的3人
10 2 3 6 1 5 30 4 7 18 23 26
循环数m
最后出列的3人
8
3 14 6
数据测试
最后出列的3人 总人数n
约 瑟 夫 环 问 题 的 三 种 解 法
约瑟夫问题(数学解法及数组模拟)约瑟夫问题(有时也称为约瑟夫斯置换,是一个出现在计算机科学和数学中的问题。
在计算机编程的算法中,类似问题又称为约瑟夫环。
又称“丢手绢问题”.)据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。
然而Josephus 和他的朋友并不想遵从。
首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。
接着,再越过k-1个人,并杀掉第k个人。
这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。
问题是,给定了和,一开始要站在什么地方才能避免被处决?Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
? 以上来自百度百科约瑟夫问题是个很有名的问题:N个人围成一个圈,从第一个人开始报数,第M个人会被杀掉,最后一个人则为幸存者,其余人都将被杀掉。
例如N=6,M=5,被杀掉的顺序是:5,4,6,2,3,1。
约瑟夫问题其实并不难,但求解的方法多种多样;题目的变化形式也很多。
接下来我们来对约瑟夫问题进行讨论。
1.模拟解法优点 : 思维简单。
?缺点:时间复杂度高达O(m*n)当n和m的值较大时,无法短时间内得到答案。
为了叙述的方便我们将n个人编号为:1- n ,用一个数组vis 来标记是否存活:1表示死亡 0表示存活 s代表当前死亡的人数? 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++; --如果这里有人,计数器+1if(cnt == m) --如果此时已经等于m,这这个人死去cnt = 0; --计数器清零s++; --死亡人数+1vis[t] = 1 --标记这个位置的人已经死去coutt" "; --输出这个位置的编号}while(s != n);接下来我们来看另一种更为高效快速的解法数学解法我们将这n个人按顺时针编号为0~n-1,则每次报数到m-1的人死去,剩下的人又继续从0开始报数,不断重复,求最后幸存的人最初的编号是多少?我们只需要将最后求得的解加1就能得到原来的编号。
约瑟夫环问题
约瑟夫环问题一、前言约瑟夫环(Josephus )问题是由古罗马的史学家约瑟夫(Flavius Josephus )提出的。
该问题的说法不一,传说他参加并记录了公元66—70年犹太人反抗罗马的起义。
约瑟夫作为一个将军,设法守住了裘达伯特城达47天之久,在城市沦陷之后,他和40名死硬的将士在附近的一个洞穴中避难。
在那里,这些叛乱者表决说“要投降毋宁死”。
于是,约瑟夫建议每隔两个人杀死一人,而这个顺序是由抽签决定的。
约瑟夫有预谋地抓到了最后一签,并且,作为洞穴中的两个幸存者之一,他说服了另一个幸存者一起投降了罗马。
假设现在一个房间内共有n 个人。
同上所述,“杀人狂” 只想留下一个人活命,并且他将按下面的规则去杀人:● 所有的人围成一圈;● 顺时针报数,每次报到q 的人将被杀掉; ● 被杀掉的人将从房间内移走;● 然后从被杀掉的下一个人重新报数,直到剩余一人。
你非常不幸地参加了这场“游戏”,当然,你是想活命的,所以你必须快速决定要站到哪一个位置,才能使得最后留下的人是你。
这就是最初的“约瑟夫环”问题,纯粹的数学计算无法做出解答,但对于参加信息学竞赛的选手来说,可以快速的编写一个程序来解决。
下面就针对这个问题进行分析并解答。
二、特例当q=2时,是约瑟夫环的一个特例,可以利用数学的方法快速求解。
分析:如果只有2个人,显然剩余的为1号。
如果有4个人,在第一轮移除掉2、4以后,只剩下1、3,现在环中又仅剩下2个人了,我们已经知道2个人时,结果为1,所以当n=4时,最后剩余的也为1号。
如此可设想:当()02≥=k n k时,最后剩余的必定为1。
我们定义()n J 为由n 个人的构建成的约瑟夫环最后的结果,则有()12=kJ 。
让我们来证明该设想的正确性:11222--=-=k k k n (第一轮后,移除掉122/2-=k k 个人,剩余12-k ,1号仍在环中) 221222---=-=k k k n (第二轮后,1号仍在环中)……112222=-=n (1号仍在序列当中)()12=J在上面的分析中,每一轮的移除操作,均移除一半出列,1号总在剩余的环中。
josephus环公式法
int N = Integer.parseInt(args[0]);
int M = Integer.parseInt(args[1]);
Node t = new Node(1);
Node x = t;
for (int i = 2; i <= N; x = (x.next=new Node(i++)));
f[i]=(f[i-1]+m)%i; (i>1)
有了这个公 式,我们要做的就是从1-n顺序算出f[i]的数值,最后结果是f[n]。因为实际生活中编号总是从1开始,我们输出f[n]+1
由于是 逐级递推,不需要保存每个f[i],程序也是异常简单:
#i nclude <stdio.h>
maபைடு நூலகம்n()
如何知道 (n-1)个人报数的问题的解?对,只要知道(n-2)个人的解就行了。(n-2)个人的解呢?当然是先求(n-3)的情况 ---- 这显然就是一个倒推问题!好了,思路出来了,下面写递推公式:
令f[i]表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是 f[n]
递推公式
f[1]=0;
}
无论是用链表实现还是用数组实现都有一个共同点:要模拟整个游戏过程,不仅程序写起来比较烦,而且时间复杂度高达O(nm),当n,m非常大(例如上百万,上千万)的时候,几 乎是没有办法在短时间内出结果的。我们注意到原问题仅仅是要求出最后的胜利者的序号,而不是要读者模拟整个过程。因此如果要追求效率,就要打破常规,实施 一点数学策略。
k k+1 k+2 ... n-2, n-1, 0, 1, 2, ... k-2
Josephus环问题
Josephus环问题约瑟夫环问题问题描述:Josephus问题可以描述为如下的⼀个游戏:N个⼈编号从1到N,围坐成⼀个圆圈,从1号开始传递⼀个热⼟⾖,经过M次传递后拿着⼟⾖的⼈离开圈⼦,由坐在离开的⼈的后⾯的⼈拿起热⼟⾖继续进⾏游戏,直到圈⼦只剩下最后⼀个⼈。
例如:M=0,N=5,则游戏⼈依次被清除,5号最后留下;如果M=1,N=5,那么被清除的⼈的顺序是2,4,1,5,最后剩下的是3号。
如下是两种解题⽅法:建⽴⼀个N⼤⼩的数组,存储N个⼈是否还在圈⼦内(0为在圈⼦内,-1为已经离开圈⼦),依次循环遍历整个数组,直到剩下的⼈数(left)为1。
public static void pass(int m, int n){int[] num = new int[n];int left = n; //剩下的⼈数int index = 0; //当前遍历到的位置while(left != 1){for(int i = 0; i< m; i++){if(num[index++] != 0) //如果当前⼈已经清除i--;if(index >= n)index = 0;}while(num[index] != 0){index++;if(index >= n)index = 0;}System.out.println("out number is " + (index + 1));num[index] = -1; //将清除的数据下标置-1index++;if(index >= n)index = 0;left--;}}第⼆种⽅式,将1~N的数添加到⼀个ArrayList对列中,⾸先计算偏移量(mPrime),当mPrime⼩于对列长度的⼀半时,则从前往后依次遍历找到清除的元素;当mPrime⼤于对列长度的⼀半时,从后往前遍历找到清除的元素。
public static void pass2(int m, int n){ArrayList<Integer> list = new ArrayList<Integer>();for(int i = 1; i <= n; i++)list.add(i);ListIterator<Integer> iter = list.listIterator();int left = n; //剩下的⼈数int item = 0; //the out numberfor(int i= 1; i < n; i++){int mPrime = m % left;if(mPrime < left/2){ //如果当前的偏移量⼩于list长度的⼀半时if(iter.hasNext())item = iter.next();for(int j = 0; j < mPrime; j++){if(!iter.hasNext())iter= list.listIterator();item = iter.next();}}else{ //当偏移量⼤于list长度的⼀半时,从后往前找for(int j = 0; j< left - mPrime; j++){if(!iter.hasPrevious())iter = list.listIterator(list.size());item = iter.previous();}}System.out.println("out number is " + item);iter.remove();if(!iter.hasNext()) //有可能下⼀次循环mPrime就会⼩于left的⼀半iter = list.listIterator();left--;}}总结当M,N较⼤时,则第⼆种⽅法时间效率更⾼,实现表明,当N=100000,M=9000时(省略的两个算法中的syso语句),⽅法⼀个执⾏时间是30713ms,⽽第⼆种⽅法的执⾏时间仅为4891ms,M越⼤时⽅法⼀的时间效率会更差。
简单的约瑟夫环算法
简单的约瑟夫环算法约瑟夫环问题起源于⼀个犹太故事。
约瑟夫环问题的⼤意如下: 罗马⼈攻占了桥塔帕特,41个⼈藏在⼀个⼭洞中躲过了这场浩劫。
这41个⼈中,包括历史学家Josephus(约瑟夫)和他的⼀个朋友。
剩余的39个⼈为了表⽰不向罗马⼈屈服,决定集体⾃杀。
⼤家制定了⼀个⾃杀⽅案,所有这41个⼈围成⼀个圆圈,由第⼀个⼈开始顺时针报数,每报数为3的⼈就⽴刻⾃杀,然后再由下⼀个⼈重新开始报数,仍然是每报数为3的⼈就⽴刻⾃杀......,直到所有的⼈都⾃杀⾝亡为⽌。
约瑟夫和他的朋友并不想⾃杀,于是约瑟夫想到了⼀个计策,他们两个同样参与到⾃杀⽅案中,但是最后却躲过了⾃杀。
请问,他们是怎么做到的?package .datastruct;import java.util.Scanner;//简单的约瑟夫环求解public class Josephus {static final int Num=41; //总⼈数static final int KillMan=3; //⾃杀者报数//约瑟夫环算法static void josephus(int alive){int []man = new int[Num];int count=1;int i=0,pos=-1;while(count<=Num){do{pos=(pos+1)%Num; //环处理if(man[pos]==0) //只有没⾃杀的⼈才不等于0i++;if(i==KillMan){ //该⼈⾃杀i=0;break;}}while(true);man[pos]=count;System.out.printf("第%2d个⼈⾃杀!约瑟夫环编号为%2d",pos+1,man[pos]);if(count%2==1){System.out.printf("->");}else{System.out.printf("->\n"); //输出换⾏}count++;}System.out.println();System.out.printf("这%d个需要存活的⼈的初始位置应该排在以下序号:\n",alive);alive = Num - alive;for(i=0;i<Num;i++){if(man[i]>alive)System.out.printf("初始编号:%d,约瑟夫环编号:%d\n", i+1,man[i]);}System.out.println();}public static void main(String[] args){int alive;System.out.print("约瑟夫环问题求解!\n");System.out.print("请输⼊需要留存的⼈的数量:");Scanner input = new Scanner(System.in);alive = input.nextInt();josephus(alive);}}。
约瑟夫环设计的知识点
约瑟夫环设计的知识点约瑟夫环(Josephus problem)是一个经典的数学问题,它的背后涉及到一些有趣的数学原理和设计思想。
本文将介绍约瑟夫环的背景、问题描述和解决方法,并介绍一些与约瑟夫环相关的知识点。
背景介绍:约瑟夫环问题最早出现在古代历史中,据说是由犹太历史学家弗拉维奥·约瑟夫斯(Flavius Josephus)提出的。
故事的背景是在公元1世纪犹太战争时,罗马军队包围了一个围城的犹太人民。
为了避免被俘或杀害,一群犹太人决定形成一个“死亡圈”,按照一定规则轮流自杀,直到最后一个人留下。
他们选择了一个合适的位置,每次报数到某个固定的数字时将当前的人杀掉,直到只剩下最后的一个人。
问题描述:现在我们将这个问题抽象为数学问题。
假设有n个人,从1到n编号,围成一个圆圈。
从编号为1的人开始报数,每次报到m的人将被淘汰出局,然后从下一个人重新开始报数。
重复这个过程,直到只剩下一个人。
问题的关键是确定最后剩下的人的编号。
解决方法:对于约瑟夫环问题,有很多种解决方法。
下面介绍其中两种常用的方法:1. 数学公式法:我们可以通过数学公式来解决约瑟夫环问题。
根据数学推导,对于给定的n和m,最后剩下的人的编号可以通过以下公式计算得出:f(n,m) = (f(n-1,m) + m) % n其中f(n,m)表示n个人中最后剩下的人的编号。
2. 循环链表法:另一种常用的解决约瑟夫环问题的方法是使用循环链表。
我们可以用一个循环链表模拟这个过程,每次找到要删除的节点并删除,直到只剩下一个节点为止。
这种方法的好处是时间复杂度较低,可以快速找到要删除的节点。
与约瑟夫环相关的知识点:除了约瑟夫环问题本身,还有一些与之相关的知识点,让我们更深入地了解这个问题。
以下是其中几个重要的知识点:1. 约瑟夫环的应用:约瑟夫环在现实生活中有一些应用。
例如,可以用来解决任务分配的问题。
假设有n个任务需要分配给m个人完成,可以通过构建一个约瑟夫环来确定每个任务分配给哪个人。
约瑟夫环问题的两种解法(循环链表和公式法)
约瑟夫环问题的两种解法(循环链表和公式法)问题描述这⾥是数据结构课堂上的描述:N people form a circle, eliminate a person every k people, who is the final survior?Label each person with 0, 1, 2, ..., n - 1, denote(表⽰,指代) J(n, k) the labels of surviors when there are n people.(J(n, k)表⽰了当有 n 个⼈时幸存者的标号)First eliminate the person labeled k - 1, relabel the rest, starting with 0 for the one originally labeled k.0 1 2 3 ... k-2 k-1 k k+1 ... n-1... k-2 0 1 ...Dynamic programmingJ(n, k) = J(J(n - 1, k) + k) % n, if n > 1,J(1, k) = 0⽤中⽂的⽅式简单翻译⼀下就是 (吐槽:为啥课上不直接⽤中⽂呢?淦!) 有 n 个⼈围成⼀圈,从第⼀个⼈开始,从 1 开始报数,报 k 的⼈就将被杀死,然后从下⼀个⼈开始重新从 1 开始报数,往后还是报 k 的⼈被杀掉,杀到最后只剩⼀个⼈时,其⼈就为幸存者。
(上⾯的英⽂是从 0 开始的,是因为我们写程序时使⽤了数组,所以下标从 0 开始)解决⽅案循环链表⽅法算法思路很简单,我们这⾥使⽤了循环链表模拟了这个过程:节点 1 指向节点 2,节点 2 指向节点 3,...,然后节点 N 再指向节点 1,这样就形成了⼀个圆环。
如图所⽰,n 取 12,k 取 3,从 1 开始报数,然后依次删除 3, 6, 9, 12:#include<stdio.h>#include<stdlib.h>typedef struct Node // 节点存放⼀个数据和指向下⼀个节点的指针{int data;struct Node *next;} *NList; // NList为指向 Node 节点的指针// 创建⼀个节点数为 n 的循环链表NList createList(int n){// 先创建⼀个节点NList p, tmp, head;p = (NList)malloc(sizeof(struct Node));head = p; // 保存头节点p->data = 1; // 第⼀个节点for (int i = 2; i <=n ; i++){tmp = (NList)malloc(sizeof(struct Node));tmp->data = i;p->next = tmp;p = tmp;}p->next = head; // 最后⼀个节点指回开头return head;}// 从编号为 1 的⼈开始报数,报到 k 的⼈出列,被杀掉void processList(NList head, int k){if (!head) return;NList p = head;NList tmp;while (p->next != p){for (int i = 0; i < k - 1; i++){tmp = p;p = p->next;}printf("%d 号被杀死\n", p->data);tmp->next = p->next;free(p);p = NULL; // 防⽌产⽣野指针,下同p = tmp->next;}printf("幸存者为 %d 号", p->data);free(p);p = NULL;}int main(){NList head = createList(11);processList(head, 3);return 0;}测试结果:易知,这个算法的时间复杂度为O(nk),显然,这不是⼀个好的算法。
约瑟夫环问题(Josephus)
第一行为一个整数T(<2^15)表示测 试次数,接着第二到T+1行分别为n,m和k 的值。 例:2
10 2 3
输出格式:
T行最后min(n,3)个出列的编号。 结果:6 1 5
问题背景
• 这个问题是以弗拉维奥•约瑟夫斯命名的, 它是1世纪的一名犹太历史学家。他在自己 的日记中写道,他和他的40个战友被罗马 军队包围在洞中。他们讨论是自杀还是被 俘,最终决定自杀,并以抽签的方式决定 谁杀掉谁。约瑟夫斯和另外一个人是最后 两个留下的人。约瑟夫斯说服了那个人, 他们将向罗马军队投降,不再自杀。
(3)us jp=new Josephus(); int a[]=new int[n]; for(int i=0;i<n;i++){ a[i]=i+1; } jp.SortArray(a,n,m,k,g); }
public void show(int[]b,int g){ for(int i=b.length-g;i<b.length;i++){ System.out.print(b[i]+" ");
68 34 25 38 54 4 120 16 23 32 53 97
500 12 30 166 358 266
实验总结:
(1) 经过这次的实践,让我们明白了合作 的重要性。
(2)在程序设计初期,总会或多或少的出现 问题,经过我们的耐心调试,不断地修改, 慢慢地将程序设计较好的符合了要求。
(3)当然其中还是会存在一些漏洞,需要进 一步的改进。在计算机中是容不得丝毫的 错误的,这也让我们学到了面对科学要持 有严谨的态度,否则必定得不到应该有的 结果。
细究“约瑟夫环”
细究“约瑟夫环”0 引言17世纪的法国数学家加斯帕在《数目的游戏问题》中讲了这样一个故事:15个教徒和15 个非教徒在深海上遇险,必须将一半的人投入海中,其余的人才能幸免于难,于是想了一个办法:30个人围成一圆圈,从第一个人开始依次报数,每数到第九个人就将他扔入大海,如此循环进行直到仅余15个人为止。
问怎样排法,才能使每次投入大海的都是非教徒。
1 问题描述有n个人围成一圈,顺序排号。
从第一个人开始报数(从1到3报数),凡报到3的人退出圈子,问最后留下的是原来第几号的那位。
示例一:输入:n = 3输出:“2”解释:首先轮流报数,3就被退出了,之后1,2,1,1就被退出了,最后只剩下了2。
2 算法描述解题思路:首先因为考虑到是不断的有规律退出数字则首先要考虑到循环的使用,我们从索引上看,如果将每次循环的三人看成一组,则被退出的人的索引为2,此时我们就知道了我们删去的就应该是索引为2的人。
但我们此时又想到该如何让其满足我们想得到的方式呢?我们不妨将其横排排列,123删去3后变成1212,此时我们发现可以将之前还未删去的数值排列在其之后,我们可以多举几个例子如1234变成12412。
那么此时我们就解决了第一个如何将其围成圈的问题,而之后就到了最关键的时候了,如何删去这些值?我们又举123为例,若想得到1,我们可以有很多的做法,而取余则是一种很巧的运算方式,如:“1”的位置是1,所以0%3(3是这三个值的长度(1,2,3))得到0,而1 的索引就是0,同理我们可得其他的值。
根据规律可得,若k=2的值为删去的数,那么我们只需进行k = k+2得到下一个删去的值。
(简单讲就是本事索引除以长度得到自身位置,本身长度加1除以长度得到下一个位置,同理加2)3 实验结果与讨论通过编程最终求出了约瑟夫环的问题。
附件代码清单用python求出杨辉三角数4 结语约瑟夫环是一个很经典的数学问题,其中的解法多种多样,通过这种复杂的循环可以使我们很轻松的解决一些问题。
- 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