约瑟夫问题

来源:互联网 发布:有关网络暴力的法律 编辑:程序博客网 时间:2024/06/08 13:43

备注:多重转载、整理,如有疑问,请留言。

一. 问题描述:

n个人围成一圈,按顺时针方向依次编号,1, 2, ..., n,然后按顺时针方向从编号为1的人开始报数,报到m的人退出此圈,下一位继续从1开始报数,直到只剩下一人,求这个人的编号。

二. 问题解决:

方法一:朴素解法。利用循环链表,时间复杂度O(m*n),空间复杂度O(n)。C语言实现如下:

typedef struct node{    long n;    struct node *next;}NODE;long josephus_plain(long n=500,long m=2){//n: total person number//m: death numberif(n<1||m<0)return -1;if(m==1)return n;NODE *clink = (NODE *)malloc(sizeof(NODE));NODE *p = NULL, *pre = NULL;clink->n = 1;clink->next = clink;long i;for(i=n;i>1;i--){p = (NODE *)malloc(sizeof(NODE));p->n = i;p->next = clink->next;clink->next = p;}while(clink!=clink->next){for(i=1;i<m;++i){pre=clink;clink=clink->next;}p=clink;clink=clink->next;pre->next=clink;free(p);p=NULL;}i = clink->n;free(clink);clink=NULL;return i;}

方法二:数学解法。寻找递推公式,时间复杂度为O(n),空间复杂度为O(1)(备注:多重转载,作者不详,但思路清晰,比较容易理解)。

为方便计算,问题重新描述:n个人围成一圈,按顺时针方向依次编号,0, 1, 2, ..., n-1,然后按顺时针方向从编号为0的人开始报数,报到m-1的人退出此圈,下一位继续从0开始报数,直到只剩下一人,求这个人的编号。

第一个人出圈后(其编号一定(m-1)%n),剩下的n-1个人仍构成约瑟夫问题(以编号为k=m%n人开始):k, k+1, ..., n-1, 0, 1, ..., k-2,对剩下的n-1人重新编号:

k, k+1, ..., n-1, 0, 1, ..., k-2 (k-1)

=>

0, 1, ..., n-1-k, n-k, n-k+1, ..., n-2 (n-1)

若n-1人的约瑟夫问题最后结果为x,则由上述重新编号过程可以推出n人的约瑟夫问题的最后结果为x'=(x+m)%n。所以对步长为m(m>0且m为整数)的n人约瑟夫问题有如下递推公式(编号为:0, 1, ..., n-1):

J(1)=0;

J(n)=(J(n-1)+m)%n, n>0.

算法实现如下:

long josephus_optimize1(long n=500,long m=2){//n: total person number//m: death numberlong i,s=0;for(i=2;i<=n;++i){s=(s+m)%i;}return s;}

方法三:数学解法优化。对方法二中递推过程进行优化,当m<n时,时间复杂度可以降到对数数量级O(ln(n)/ln(m/(m-1)))。

方法二中,变量s,他的初始值为剩余的那个人的编号,但在循环的过程中,我们会发现它常常处在一种等差递增的状态。对递推式s=(s+m)%i,可以看出,当i比较大而s+m比较小的时候,s就处于一种等差递增的状态,这个等差递增的过程并不是必须的,可以跳过。设可以跳过的次数为x,则有:

s+m*(x+1)<=i+x

=>

x<=(i-s-m)/(m-1)

令x=floor((i-s-m)/(m-1)), s=s+m*x, i=i+x,然后直接进行操作s=(s+m)%i,这样就跳过了x次不必要的操作,从而节省了等差递增的时间开销。当然,若求出来的xi超过n,则表明可以直接结束算法了,另做处理。算法实现如下:

long josephus_optimize2(long n=500,long m=2){//n: total person number//m: death numberlong i,x=-1,s=0;for(i=2;i<=n;++i){if(s+m<i){x=(i-s-m)/(m-1);if(i+x<n){s=(s+m*x);i+=x;}else{s+=m*(n-i);i=n;}}s=(s+m)%i;}return s;}
对上述不等式:s+m*(x+1)<=i+x,求得的最大整数x,大多数情况下不等式等号都不成立,因此在大多数情况下可以直接跳过x+1次,不必要的操作。设可以跳过的最大次数,计算如下不等式:

s+m*x+1<=i+x-1

=>

x<=(i-s-2)/(m-1)

令x=floor((i-s-2)/(m-1)), 则此x,即为此次可以跳过的最大次数。令s=s+m*x, i=i+x,然后直接进行操作s=(s+m)%i。

long josephus_optimize3(long n=500,long m=2){//n: total person number//m: death numberlong i,x=-1,s=0;for(i=2;i<=n;++i){if(s+m<i){x=(i-s-2)/(m-1);if(i+x<n){s=(s+m*x);i+=x;}else{s+=m*(n-i);i=n;}}s=(s+m)%i;}return s;}
以上实现中,josephus_optimize1(), josephus_optimize2(), josephus_optimize3()给出的结果均为在编号为0~n时的结果,当编号为1~n时,只需将上述结果加1以修正。另外,当报数的起始位置不是从编号为1人开始时,只需做如下修正,假设编号为1~n,起始报数人编号为k(1<=k<=n),并从1开始报数,则结果为:(J(n)+k-1)%n+1。完整实现如下:

long josephus(long n=500,long m=2,long k=1){//n: total person number(1, 2, ..., n), default vaule: 500//m: death number(the one who get this number will be killed), default value: 2//k: start position(the position to start the game), defualt value: 1long s;n=(n<1)?500:n;m=(m<1)?2:m;k=(k<1)?1:(k+n-1)%n+1;if(m==1){s=n-1;}else{//optimize ://s=josephus_optimize1(n,m);//s=josephus_optimize2(n,m);s=josephus_optimize3(n,m);}return (s+k-1)%n+1;}

测试结果:

n=500, m=2, k=1(i:x:s): 489

josephus_optimize2:

2:-1:0,4:1:0,8:3:0,16:7:0,32:15:0,64:31:0,128:63:0,256:127:0,500:-1:488,times::9

josephus_optimize3:

2:-1:0,4:1:0,8:3:0,16:7:0,32:15:0,64:31:0,128:63:0,256:127:0,500:-1:488,times::9


n=1000, m=7, k=3(i:x:s): 406

josephus_optimize2:

2:-1:1,3:-1:2,4:-1:1,5:-1:3,6:-1:4,7:-1:4,8:-1:3,9:-1:1,10:0:8,11:0:4,12:0:11,13
:0:5,14:0:12,15:0:4,16:0:11,17:0:1,19:1:15,20:1:2,23:2:0,26:2:21,27:2:1,31:3:29,
32:3:4,36:3:32,37:3:2,42:4:37,43:4:1,50:6:0,58:7:56,59:7:4,68:8:67,69:8:5,79:9:7
5,80:9:2,93:12:0,108:14:105,109:14:3,126:16:122,127:16:2,147:19:142,148:19:1,172
:23:169,173:23:3,201:27:199,202:27:4,235:32:0,274:38:273,275:38:5,320:44:0,373:5
2:371,374:52:4,435:60:431,436:60:2,508:71:506,509:71:4,593:83:592,594:83:5,692:9
7:691,693:97:5,807:113:803,808:113:2,942:133:940,943:133:4,1000:-1:403,times::64

josephus_optimize3:

2:-1:1,3:-1:2,4:-1:1,5:-1:3,6:-1:4,7:-1:4,8:-1:3,9:-1:1,11:1:4,13:1:5,15:1:4,17:
1:1,20:2:2,23:2:0,27:3:1,32:4:4,37:4:2,43:5:1,50:6:0,59:8:4,69:9:5,80:10:2,93:12
:0,109:15:3,127:17:2,148:20:1,173:24:3,202:28:4,235:32:0,275:39:5,320:44:0,374:5
3:4,436:61:2,509:72:4,594:84:5,693:98:5,808:114:2,943:134:4,1000:-1:403,times::3
9

方法四:数学解法优化。时间复杂度降到O(logn)

详见 Donald E. Knuth的《具体数学》中相关部分的讨论,相当精彩,这里下载

当m=2时,有更简单的方法:

Josephus(1)=1

Josephus(2n)=2Josephus(n)-1, n>0

Josephus(2n+1)=2Josephus(n)+1, n>0

令n=2^k+l, 0<=l<2^k,则有,Josephus(n)=2*l+1

令n=(bm,bm-1,...,b1,b0)2,则有,Josephus(n)=(bm-1,...,b1,b0,bm)2,其中bm为1。

三. 参考资料:

1. 雨中飞燕

2. 【整理】约瑟夫问题的数学方法

0 0
原创粉丝点击