ImageVerifierCode 换一换
格式:DOCX , 页数:18 ,大小:22.54KB ,
资源ID:8005731      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bdocx.com/down/8005731.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(C语言程序设计漫谈之从约瑟夫问题谈起.docx)为本站会员(b****5)主动上传,冰豆网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰豆网(发送邮件至service@bdocx.com或直接QQ联系客服),我们立即给予删除!

C语言程序设计漫谈之从约瑟夫问题谈起.docx

1、C语言程序设计漫谈之从约瑟夫问题谈起从“约瑟夫问题”谈起约瑟夫问题是一个出现在计算机科学和数学中的问题。在计算机编程的算法中,类似问题又称为约瑟夫环。据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想自杀。为避免与其他39个决定自杀的犹太人发生冲突,Josephus要他的朋友先假装遵从,他将

2、朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。17世纪的法国数学家加斯帕在数目的游戏问题中讲了这样一个故事:15个教徒和15 个非教徒在深海上遇险,必须将一半的人投入海中,其余的人才能幸免于难,于是想了一个办法:30个人围成一圆圈,从第一个人开始依次报数,每数到第九个人就将他扔入大海,如此循环进行,直到仅余15个人为止。问怎样的排法,才能使每次投入大海的都是非教徒。【例1】约瑟夫问题。N个人围成一圈,从某个人开始,按顺时针方向从1开始依次编号。从编号为1的人开始顺时针“1,2,M”报数,报到M的人退出圈子。这样不断循环下去,圈子里的人将不断减少。由于人的个数是有限的,因此最终

3、会剩下一个人,该人就是优胜者。输入N和M,输出出圈顺序。例如,N=6、M=5,出圈的顺序是:5,4,6,2,3,1。(1)编程思路。为输出出圈顺序,采用一个数组来进行模拟。定义int circleN+1,并按circlei=i+1的方式赋予各元素初值。该值代表两个含义:1)值为0,代表编号i+1的人不再圈中;2)值非0,代表圈中第i个位置的人编号为i+1。定义变量i代表报数位置的流动,i的初值为0,代表编号为1的人的位置,i的变化方式为: i=(i+1)%(n), 即0-1-2-n-1 -0-1。i流动到了位置i后,该位置的人若已出圈(circlei=0),显然无法报数,得跳过该位置;若该位置

4、的人在圈中,则报数(定义一个表示报数的变量p,初值为0,每次报数p+)。当报数到m(即p=m)时,位置i的人出圈,记录出圈人数cnt+,同时p置为0。当出圈人数等于N时循环结束。(2)源程序。#include int main() int n,m,i,p,cnt; int circle50; while (scanf(%d%d,&n,&m) & n!=0) for (i=0;in;i+) circlei=i+1; i=0; / 报数指示 p=0; / 报数计数器 cnt=0; / 出队人数 while (cntn) if (circlei!=0) p+; if (p=m) printf(%d

5、,circlei); cnt+; circlei=0; p=0; i=(i+1)%(n); printf(n); return 0;下面我们从例1的基础上进行扩展讨论。例如,运行例1的程序时,输入41 3,则输出为:3 6 9 12 15 18 21 24 27 30 33 36 39 1 5 10 14 19 23 28 32 37 41 7 13 20 26 34 40 8 17 29 38 11 25 2 22 4 35 16 31为这个输出结果进行的模拟是需要耗时的。实际上,在大多数问题中,我们不关心中间的结果,只关心某个最终结果。例如,在Josephus 的故事中,Josephus

6、和他的朋友不想自杀,Josephus 需要关心的是最后一个和倒数第2个出圈的编号是多少,至于中间过程(39个犹太人谁先自杀,谁后自杀)对Josephus 来说无意义。因此,Josephus 需要的是快速确定最后一个和倒数第2个出圈的编号,然后站到对应位置即可。而无需耗时模拟整个过程。【例2】猴子选大王。一堆猴子都有编号,编号是1,2,3 .m,这群猴子(m个)按照1m的顺序围坐一圈,从第1开始数,每数到第N个,该猴子就要离开此圈,这样依次下来,直到圈中只剩下最后一只猴子,则该猴子为大王。已知猴子数m和报数间隔n(设1=n=m=50),问编号为多少的猴子当大王?(1)编程思路1。将例1的源程序略

7、作修改,增加一个变量last记录最后获胜者编号,不输出中间过程。显然, if (cnt=n) last=circlei;(2)源程序1。#include int main() int n,m,i,p,cnt,last; int circle50; while (scanf(%d%d,&n,&m) & n!=0) for (i=0;in;i+) circlei=i+1; i=0; / 报数指示 p=0; / 报数计数器 cnt=0; / 出队人数 while (cntn) if (circlei!=0) p+; if (p=m) cnt+; if (cnt=n) last=circlei; ci

8、rclei=0; p=0; i=(i+1)%(n); printf(%dn,last); return 0;(3)编程思路2。源程序1中采用数组模拟,由于猴子在圈中还是出圈是通过数组元素circlei的值非0还是0来判断,位置并未真正删除,因此当n和m很大时,程序的执行效率很低。例如,仅求最后一个出圈的元素,循环就得执行m*n次(p从1报到m,每次报数流动i得走完整一圈,其中n-1个已出圈,圈中仅一个元素)。为提高运行效率,可以考虑采用循环链表来进行模拟,这样每次出圈就将链表中的相应元素删除。循环链表只剩最后一个元素时,输出胜者编号。(4)源程序2。#include struct Jose i

9、nt code; / 编号 Jose *next;int main() Jose *head,*p1,*p2; int n,m,i,cnt,tmp; scanf(%d%d,&n,&m); while (n!=0 & m!=0) head=new Jose; head-code=1; p2=head; for (i=2;icode=i; p2-next=p1; p2=p1; p2-next=head; p1=head; cnt=n; while (cnt1) tmp=m%cnt; / 提高效率之举,当m大于圈中人数时不用循环多圈 if (tmp=0) tmp=cnt; i=1; while (i

10、next; p2-next=p1-next; / 报m的结点出圈 delete p1; / 释放出圈结点的空间 cnt-; p1=p2-next; printf(%dn,p1-code); delete p1; scanf(%d%d,&n,&m); return 0;(5)编程思路3。本例中的源程序2相比源程序1可以提高运行效率,但毕竟也是采用过程模拟,因此对于n和m较大的情况,效率仍然不高。有没有可以根据n和m的值直接推出最后出圈人编号的办法呢?为了讨论方便,先把问题稍微改变一下,并不影响原意。问题描述:n个人(编号0(n-1),从0开始报数,报到(m-1)的退出,剩下的人继续从0开始报数。

11、求胜利者的编号。我们知道第1个人(编号一定是(m-1)%n)出列之后,剩下的n-1个人组成了一个新的约瑟夫环(以编号为k=m%n的人开始):k , k+1 , k+2 . n-2 , n-1 , 0 , 1 , 2 , . k-2并且从k开始报0。现在我们把他们的编号做一下转换:k - 0 k+1 - 1 k+2 - 2 . .k-3 - n-3k-2 - n-2变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解:例如x是最终的胜利者,那么根据转换把这个x变回去不刚好就是n个人情况的解吗?下面我们来推导变回去的公式。序列1: 1 , 2 , 3 , 4 , k-1 ,

12、 k , k+1 ,, n-2 , n-1 , n序列2: 1 , 2 , 3 , 4 , k-1 , k+1 , , n-2 , n-1 , n序列3: k+1 , k+2 , k+3 , , n-2 , n-1 , n , 1 , 2 , 3 , , k-2 , k-1序列4: 1 , 2 , 3 , 4 , , 5 , 6 , 7 , 8 , , n-2 , n-1 k=m%n; x = x+k = x+ m%n ; 而 x+ m%n 可能大于n x= (x+ m%n)%n = (x+m)%n 。如何知道(n-1)个人报数的问题的解f(n-1)呢? 显然只要知道(n-2)个人的解f(n-

13、2)就行了。(n-2)个人的解呢?当然是先求f(n-3) - 这显然就是一个倒推问题!令 fi 表示i个人玩报m退出的约瑟夫环游戏的最后胜利者的编号,则有递推公式:f1 = 0 ;fi = (fi-1+m)%i; (i1)有了这个递推公式,我们就很容易求得n个人报m退出的约瑟夫问题的最后胜利者编号fn。因为实际生活中编号总是从1开始,我们输出fn+1即可。 编写程序时,我们可以采用数组递推以便保存中间结果,也可以不保存中间任何结果采用迭代直接得到最后胜利者编号。(6)采用迭代方式实现的源程序3。#include int main() int n,m,i,s; scanf(%d%d,&n,&m)

14、; while (n!=0 & m!=0) s=0; for (i=2;i1) #include int main() int n,m,i,j,f5151; for (i=1;i51;i+) f1i=0; for (i=1;i51;i+) for (j=2;j51;j+) fji=(fj-1i+i)%j; scanf(%d%d,&n,&m); while (n!=0 & m!=0) printf(%dn,fnm+1); scanf(%d%d,&n,&m); return 0; 【例3】城市断电。有n(3=n150)个城市围成圈,先将第1个城市(编号为1)断电,然后每隔m个城市使一个城市断电,直

15、到剩下最后一个城市不断电。问使2号城市不断电的最小的m是多少?(1)编程思路。采用例2的求最后胜利者的方式,对n个城市,从m=1开始搜索,若当前m可使2号城市作为胜利者,则m就是所求,否则m=m+1后,继续搜索。程序采用打表的方式,先将n=3149的对应m值求出来并保存到数组ans150中。另外,需要注意的是第1个城市先断电了,2号城市相当第1个城市,也可以把问题看成编号从1n-1的约瑟夫问题。(2)源程序。#include int main() int ans150,i,j,m,tmp; for (i = 3;i150;i+) m = 1; while(1) tmp = 1; / 注意第1个

16、城市已经断电,相当从1n-1个城市 for (j = 2;j =1), f(0)=0; f(i) 表示当前子序列中要出圈的那个人(当前序列编号为0(n-i);例如,设n=6,m=5f(0)=0;f(1)= f(0)+5-1%6=4; 子序列(0,1,2,3,4,5)中的4 (也就是实际序列(1,2,3,4,5,6)中的5)f(2)= f(1)+5-1%5=3; 子序列(0,1,2,3,5)中的3 (也就是实际序列(1,2,3,4,6)中的4)f(3)= f(2)+5-1%4=3; 子序列(0,1,2,5)中的5 (也就是实际序列(1,2,3,6)中的6)f(4)= f(3)+5-1%3=1;

17、子序列(0,1,2)中的1 (也就是实际序列(1,2,3)中的2)f(5)= f(4)+5-1%2=1; 子序列(0,2)中的1 (也就是实际序列(1,3)中的3)f(6)= f(5)+5-1%1=0; 子序列(0)中的0 (也就是实际序列(1)中的1)故得到的出圈顺序为:5,4,6,2,3,1。 结果正确。按照这样的思路,可以修改例1的源程序为:#include int main() int n,m,i,j,cnt,circle51,f51; scanf(%d%d,&n,&m); while (n!=0 & m!=0) for (i=0;in;i+) circlei=i+1; f0=0; f

18、or (i=1;i=n;i+) fi=(fi-1+m-1)%(n-i+1); cnt=n; for (i=1;i=n;i+) printf(%d ,circlefi); for (j=fi;jcnt-1;j+) circlej=circlej+1; cnt-; printf(n); scanf(%d%d,&n,&m); return 0;在弄清楚上面例子的情况下,建议大家刷下面几道POJ的题目,加深对约瑟夫问题及其变形的解决方法的理解与运用。特别是约瑟夫递推公式的灵活运用。1012 Joseph 题意为:k个好人和k个坏人围成一圈(将他们顺序编号,好人编号为1K,坏人编号k+12k)。从编号为

19、1的人开始顺时针“1,2,M”报数,报到M的人退出圈子。这样不断循环下去,圈子里的人将不断减少。问M最小为多少时,前k个出圈的人全部是坏人?1781 In Danger 题意为:n个人排成一圈,从第2个人开始隔一个杀一个,直到剩下最后一人。最后一个人的编号为多少?2359 Questions 题意为:输入一个字符串,从第一个字符开始循环数,数到第1999就删除这个字符,继续数,直到只剩下一个字符。如果剩下的那个字符等于空格输出“no”, 等于?输出yes,其他输出No comments。3517 And Then There Was One 题意为:n个人围一圈,第m个人先出圈,然后从第m+1

20、个人开始报k出圈,求最后胜利者编号。3750 小孩报数问题。最后,给出上面5道题的源程序供参考,这些程序均可以Accepted。参考源程序POJ 1012 Joseph#include int main() int m,k,i,j,s; int joseph15=0; while (scanf(%d,&k) & k!=0) if (josephk!=0) printf(%dn,josephk); continue; m=k+1; while (1) for(i=0;ik;i+) s = (m-1)%(2*k - i); for(j=2*k-i;j2*k;j+) s = (s+m)%(j+1);

21、 if(sk) break; if(i=k) break; m+; printf(%dn,m); josephk = m; return 0; POJ 1781 In Danger#include int main() char p5; int a,b,c; while(scanf(%s,p)!=EOF) if(p0=0&p1=0&p3=0) break; a=(p0-0)*10+p1-0; for(b=1;b=a)break; a=a-c; c=2*c; printf(%dn,2*a-1); return 0; POJ 2359 Questions#include #include #def

22、ine N 1999int main() char c,s30001; int len,i,cnt; len=0; while(c = getchar() != EOF) if(c = n) continue; slen+=c; slen=0; cnt = 0; for (i=2;i=len;i+) cnt=(cnt+N)%i; if(scnt = ?) printf(Yesn); else if(scnt = ) printf(Non); else printf(No commentsn); return 0; POJ 3517 And Then There Was One#include

23、int main() int n,k,m,i,s; scanf(%d%d%d,&n,&k,&m); while (n!=0 & k!=0 & m!=0) s=0; for (i=2;i=n-1;i+) s=(s+k) % i; printf(%dn,(s+m)%n+1); scanf(%d%d%d,&n,&k,&m); return 0; POJ 3750 小孩报数问题/ 参考源程序1#include int main() int n,w,s,i,p,cnt; char name6516; int outId65; scanf(%d,&n); for (i=1;i=n;i+) scanf(%s

24、,namei); for (i=0;in;i+) outIdi=i+1; scanf(%d,%d,&w,&s); i=w-1; / 报数指示 p=0; / 报数计数器 cnt=0; / 出队人数 while (cntn) if (outIdi!=0) p+; if (p=s) cnt+; printf(%sn,nameoutIdi); outIdi=0; p=0; i=(i+1)%(n); return 0;/ 参考源程序2#include struct Jose char name16; Jose *next;int main() Jose *head,*p1,*p2; int n,w,s,i; scanf(%d,&n); head=new Jose; scanf(%s,head-name); p2=head; for (i=2;iname); p2-next=p1; p2=p1; p2-next=head; scanf(%d,%d,&w,&s); p1=head; i=1; while(inext; while (p1-next!=p1) i=1; while(is) / 报数s-1次 i+;

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1