约瑟夫问题及变种
约瑟夫环的知识点总结
约瑟夫环的知识点总结约瑟夫环这个问题不仅在古代受到了广泛的关注,而且在现代数学中也有着重要的地位。
它涉及到了排列、递推、循环和递归等多个数学概念,并且有着一些有趣的数学特性。
因此,学习约瑟夫环不仅能够增加我们对于数学问题的理解,而且也可以提高我们的数学思维能力。
接下来,我们将从几个方面对约瑟夫环进行深入的讨论。
1. 约瑟夫环的历史约瑟夫环最早出现在约瑟夫斯的《犹太古记》中,他描述了犹太人在与罗马军队的战斗中围攻马萨达城的情景。
根据《犹太古记》的记载,当罗马军队攻陷了马萨达城后,大约960名男子决定宁死不从。
于是,他们站成一个圈,每隔两个人就有一个杀掉,直到最后只剩下一个人。
而这个幸存者恰恰就是约瑟夫斯本人。
因此,这个问题就得名为约瑟夫环。
除了这个故事之外,约瑟夫环在古代数学文献中也有着多次的提及。
例如,中国古代数学家秦九韶在其著作《数书九章》中也提到了这个问题。
他利用递推的方法解出了约瑟夫环的一般解,并推广到了更一般的情况。
自古代以来,约瑟夫环一直受到数学家们的关注,他们提出了很多不同的方法来解决这个问题。
而到了现代,约瑟夫环在计算机科学和密码学中也有着广泛的应用。
因此,约瑟夫环问题可以说是一个古老而又具有重要意义的数学问题。
2. 约瑟夫环的一般解在数学中,我们可以用递推的方法对约瑟夫环进行求解。
假设有N个人站成一圈,编号从0到N-1,而每隔M个人就有一个人出列。
那么一个简单直接的方法就是用递归来求解。
具体来说,我们可以定义一个递归函数f(n, m),表示N个人中最后存活下来的那个人的编号。
那么这个函数的递归关系可以如下定义:f(n, m) = (f(n-1, m) + m) % n其中f(1, m) = 0,表示只有一个人时的情况。
通过递归的方法,我们可以得到约瑟夫环的一般解。
而根据这个递归关系,我们还可以得到一些有趣的数学性质。
例如,我们可以求解约瑟夫环在给定N和M的情况下的解,而不需要实际模拟整个过程。
约瑟夫环小结
约瑟夫环实验小结:智能1201 陈明201208070103一、问题描述:约瑟夫问题是由古罗马著名的史学家Josephus提出的问题演变而来,所以通常称为Josephus问题。
改进约瑟夫问题的描述是:编号为1,2,…,n 的n个人按顺时针方向围坐一圈,每人有一个密码Ki(整数),留作其出圈后应报到Ki后出圈。
报数方法采用顺时针报数和逆时针报数交替进行,初始密码可任意确定。
求最后剩下的人的编号。
这个就是约瑟夫环问题的实际场景二、问题分析:用数组可以解决问题。
每次数到要出列时,就先出列,然后再从数组中删除相应元素,每次数到数组末尾时就重新回到数组第一个元素,重复此步骤,直至到达最后一个元素。
三、代码1)C++代码:/*约瑟夫环问题描述:设编号为1-n的n(n>0)个人按顺时针方向围成一圈.首先第1个人从1开始顺时针报数.报m的人(m 为正整数).令其出列。
然后再从他的下一个人开始,重新从1顺时针报数,报m的人,再令其出列。
如此下去,直到圈中所有人出列为止。
求出列编号序列。
*/#include<iostream>using namespace std;int main(){while(true){//多次循环实验cout<<"#########***********************多次实验分隔符********************#########"<<endl;//分隔符int n;//数组的大小int m;//m为数到第几个就出列的数cout<<"请输入要数组的初始大小n值,以及数到第m个就出列的m的值,n、m需为任意正整数:"<<endl;;//m可以大于ncin>>n>>m;cout<<"原始数组:"<<endl;int *p=new int[n];for(int i=0;i<n;i++){p[i]=i+1;} //初始化数组元素for(i=0;i<n;i++){cout<<p[i]<<",";}cout<<"\n出队顺序:"<<endl;int count_Num=1;//计数变量for(int j=1;j<=n;j++){//n的值随出列的元素的变化而变化//cout<<"count_Num="<<count_Num<<",j="<<j<<",n="<<n<<endl;if(n==1){cout<<p[0];break;}else if(m==count_Num){//如果数到就出列// cout<<"!!!!!!!!!!!!!@@@@"<<endl;count_Num=0;if(j==n){j=1;}//判断数到末尾,恢复到开始cout<<"\n"<<p[j-1]<<"->";//“除去”数到第j个元素p[j-1],“复制”数组int *q=new int[n-1];for(int t=0;t<j-1;t++){q[t]=p[t];}//跳过数组元素p[j];for(t=j;t<=n-1;t++){q[t-1]=p[t];}//将q的地址赋给p;p=q;cout<<endl;for(int i=0;i<n-1;i++){cout<<p[i]<<",";}// count_Num=1;//count_Num再从1开始计数n=n-1;//由于元素总数减少1,故n要减少1//****************j=j-1;//此句没有写,导致输出一直不是需要的结果,经过查找,加上后,就变好了!//***************}if(j==n){j=0;}//判断数到末尾,恢复到开始count_Num++;//计数变量自加!}//释放内存空间!delete [] p;//delete [] q;cout<<"\n";}system("pause");return 0;}2)Java代码:/****/package 约瑟夫环问题;/*** @author 陈明win7**/public class YSFH {int Ini_length;//java类中有四种数据,此处为default类型int SrcA []=new int[20];//定义数组/*** @param args*/public static void main(String[] args) {// TODO Auto-generated method stubSystem.out.println("约瑟夫环问题:");YSFH ys=new YSFH();ys.Ini_length=20;System.out.print("原始数组:");for(int i=1;i<=ys.SrcA.length;i++){ys.SrcA[i-1]=i;System.out.print(ys.SrcA[i-1]+",");}/*ys.remove(3);ys.remove(5);ys.remove(8);for(int i=0;i<ys.SrcA.length;i++){System.out.print(ys.SrcA[i]+",");}*///约瑟夫问题****************int Count_Num=22;//数到就要删除的号码System.out.println("\n数到22就删除!"+"出场顺序:");int j=1;for(int i=0;i<=ys.SrcA.length;i++,j++){//System.out.println("当前数组长度是:"+ys.SrcA.length+",,"+"i值是:"+i+",j值是:"+j);if(ys.SrcA.length==1){System.out.println(ys.SrcA[0]);}else{if(j==22){if(i==ys.SrcA.length){i=0;}//System.out.println("将删除:"+ys.SrcA[i]+",");System.out.print(ys.SrcA[i]+"-》");//if(i==ys.SrcA.length){i=0;System.out.println("###########");}ys.remove(i+1);//ys.Arrayout();j=1;}//System.out.println("@@@@@@@@"+"当前数组长度是:"+ys.SrcA.length+",,"+"i值是:"+i+",j值是:"+j);if(i==ys.SrcA.length){i=0;}}}System.out.println();}/* //移除元素的方法public void remove(int index) {// TODO Auto-generated method stubif(SrcA.length==0||index<0){System.out.println("错误!队列无元素可以删除!");}else if(SrcA.length==1){SrcA=null;}else if(index<=SrcA.length){int[] dest=new int[SrcA.length-1];//复制元素,注意复制的长度出错的可能性大System.arraycopy(SrcA, 0, dest,0 , index-1);//System.out.println("@@@@@@@"+SrcA.length+"##########"+index);System.arraycopy(SrcA, index, dest,index-1 , SrcA.length-index);SrcA=dest;}}//输出当前数组所以元素public void Arrayout(){System.out.print("当前数组为:");for(int i=0;i<SrcA.length;i++){System.out.print(SrcA[i]+",");}System.out.print("\n");}}四、运行结果截图C++结果截图:当需要输出当前结果时:当不需要输出当前结果时:Java结果截图:五、实验小结1)最容易出现的问题就是数组越界,然后多数情况就是使用输出当前所有数组来调试、解决问题2)两个程序均没有参考其他代码,以前的情况多数就是先看看别人的思路、代码,然后再去自己编写程序,但是这次全部独立完成。
约瑟夫问题多种解决方法
• • • • • • • • • • •
s:=0; while s<n do begin if j<m then inc(j) else j:=1; s:=s+a[j]; end; write(j); a[j]:=0; end; end.
约瑟夫问题多 种解决方法
约瑟夫问题的来历
• 据说著名犹太历史学家 Josephus有过以下的故事:在罗 马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋 友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓 到,于是决定了一个自杀方式,41个人排成一个圆圈,由 第1个人开始报数,每报数到第3人该人就必须自杀,然后 再由下一个重新报数,直到所有人都自杀身亡为止。然而 Josephus 和他的朋友并不想遵从,Josephus要他的朋友 先假装遵从,他将朋友与自己安排在第16个与第31个位置, 于是逃过了这场死亡游戏。17世纪的法国数学家加斯帕在 《数目的游戏问题》中讲了这样一个故事:15个教徒和15 个非教徒在深海上遇险,必须将一半的人投入海中,其余 的人才能幸免于难,于是想了一个办法:30个人围成一圆 圈,从第一个人开始依次报数,每数到第九个人就将他扔 入大海,如此循环进行直到仅余15个人为止。问怎样排法, 才能使每次投入大海的都是非教徒。
著名约瑟夫问题一
• 17世纪的法国数学家加斯帕在《数目的游戏问题》中讲了 这样一个故事:15个教徒和15 个非教徒在深海上遇险, 必须将一半的人投入海中,其余的人才能幸免于难,于是 想了一个办法:30个人围成一圆圈,从第一个人开始依次 报数,每数到第九个人就将他扔入大海,如此循环进行直 到仅余15个人为止。问怎样排法,才能使每次投入大海的 都是非教徒。题目中30个人围成一圈,因而启发我们用一 个循环的链来表示。可以使用结构数组来构成一个循环链。 结构中有两个成员,其一为指向下一个人的指针,以构成 环形的链;其二为该人是否被扔下海的标记,为1表示还 在船上。从第一个人开始对还未扔下海的人进行计数,每 数到9时,将结构中的标记改为0,表示该人已被扔下海了。 这样循环计数直到有15个人被扔下海为止
约瑟夫问题的经典三个例子
约瑟夫问题的经典三个例子以下是 9 条关于约瑟夫问题的经典例子:例子 1:想想看,一群小朋友围成一圈玩游戏,就像我们小时候那样。
这时候说从某个小朋友开始报数,每隔一个人淘汰,最后剩下的那个就是胜利者。
这不就是约瑟夫问题嘛。
就好像在一个神秘的游戏圈子里,大家都紧张又兴奋地等待着命运的裁决。
例子 2:你能想象军队里士兵们站成一圈,然后用这种方式来决定谁去执行特殊任务吗?哎呀呀,那场面肯定很刺激。
每个士兵心里都七上八下的,不知道自己是不是那个“幸运儿”,这和约瑟夫问题如出一辙。
例子 3:假如在一场盛大的聚会中,大家玩这样的游戏,是不是超级有趣?就像一个魔法圈,把大家的注意力都吸引过来了。
每淘汰一个人,大家就会一阵惊呼,这不正是约瑟夫问题带来的独特体验嘛。
例子 4:你看过那种生存挑战节目吗?选手们围成一圈,然后通过类似约瑟夫问题的规则来淘汰人。
哇塞,那紧张的氛围,可不就是在经历一场残酷的竞争,这就是约瑟夫问题在现实中的精彩呈现呀!例子5:好比一群探险家在荒岛上,为了分配重要资源而采取这种方式。
每个人都祈祷自己不要被先淘汰掉,这种感觉是不是很奇妙?这就是约瑟夫问题带来的不确定性啊。
例子6:想象一下公司团建的时候玩这个,大家既期待又担心。
“哎呀,可别先轮到我呀!”“哇,我居然留下来了。
”这种种反应,不就是约瑟夫问题的魅力所在吗?例子 7:学校运动会上,各班学生围成一圈进行比赛,多刺激呀!有人欢喜有人忧,这不就是约瑟夫问题所引发的情绪波澜吗?例子 8:在一个神秘的魔法学院里,学生们也用这种方式来选拔优秀学员。
每一个人都全神贯注,这和约瑟夫问题一样充满了悬念呢!例子9:如果在一个古老的部落中,用约瑟夫问题来决定首领的继承人,那该是多么惊心动魄的场面啊。
大家的心都提到了嗓子眼,。
(完整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);}。
约瑟夫环问题小结
约瑟夫环问题⼩结⼀问题描述约瑟夫环问题的基本描述如下:已知n个⼈(以编号1,2,3...n分别表⽰)围坐在⼀张圆桌周围。
从编号为1的⼈开始报数,数到m的那个⼈出列;他的下⼀个⼈⼜从1开始报数,数到m的那个⼈⼜出列;依此规律重复下去,要求找到最后⼀个出列的⼈或者模拟这个过程。
⼆问题解法在解决这个问题之前,⾸先我们对⼈物进⾏虚拟编号,即相当于从0开始把⼈物重新进⾏编号,即⽤0,1,2,3,...n-1来表⽰⼈物的编号,最后返回的编号结果加上1,就是原问题的解(为什么这么做呢,下⽂有解释)。
⽽关于该问题的解通常有两种⽅法:1.利⽤循环链表或者数组来模拟整个过程。
具体来讲,整个过程很明显就可以看成是⼀个循环链表删除节点的问题。
当然,我们也可以⽤数组来代替循环链表来模拟整个计数以及出列的过程。
此处只给出利⽤数组来模拟这个过程的解法,最终结果为最后⼀个出列的⼈的编号:#include<iostream>#include<unordered_map>#include<queue>#include<cstring>#include<cstdlib>#include<cmath>#include<algorithm>#include<sstream>#include<set>#include<map>using namespace std;int main(){int n,m;cin>>n>>m;vector<int>rs(n);for(int i = 0 ; i < n; i++)rs[i] = i + 1;//对⼈物重新进⾏编号,从0开始int cur_index = 0;//当前圆桌状态下的出列⼈的编号int out_cnt = 0;//⽤以表⽰出列的⼈数int cnt = n;//表⽰当前圆桌的总⼈数while(out_cnt < n - 1)//当out_cnt等于n-1时,循环结束,此时圆桌师⽣最后⼀个⼈,即我们要的结果{if(cur_index + m > cnt){if((cur_index + m) % cnt == 0)//这种情况需要单独考虑,否则cur_index就变成负值了cur_index = cnt - 1;elsecur_index = (cur_index + m) % cnt - 1;}elsecur_index = cur_index + m - 1;cnt--;out_cnt++;cout<<"当前出列的为:"<<*(rs.begin() + cur_index)<<endl;rs.erase(rs.begin() + cur_index);//从数组中删去需要出队的⼈员}cout<<"最后⼀个出列的⼈物为:"<<rs[0]<<endl;}该⽅法的时间复杂度为O(nm),空间复杂度为O(n),整个算法的基本流程还是⽐较清晰的,相当于每次循环更新cur_cnt、cnt和out_cnt这三个变量,当out_cnt == n-1时,此时出队的⼈数⼀共有n-1⼈,圆桌上只剩下⼀个⼈了,停⽌循环。
实验一、约瑟夫问题
实验一:约瑟夫问题求解一、问题描述1、实验题目:约瑟夫(Josephus)问题的一种描述是:编号为1,2,……,n的n个人按顺时针方向围坐一圈,每人持有一个密码(正整数)。
一开始任选一个正整数作为报数上线值m,从第一个人开始按顺时针方向自1开始报数,报到m时停止报数。
报m的人出列,将他的密码作为新的m值,从他在顺时针方向下一个人开始重新从1报数,如此下去,直至所有的人全部出列为止。
2、基本要求:试设计一个程序,按出列顺序印出个人编号。
3、测试数据:m的初值为20;n=7,7个人的密码依次为:3,1,7,2,4,8,4。
m的初值为6,正确的出列顺序应为:6,1,4,7,2,3,5。
二、需求分析1、本程序用来求出含有密码的约瑟夫问题,可以输出所有人的出列顺序。
2 、程序运行后显示提示信息,提示用户输入一圈的人数n,接着输入每个人的密码,最后提示输入初始密码。
3、用户输入完毕后,程序自动输出运算结果。
三、概要设计1、设计思路n个人围成一圈,每个人的手中都有一个密码,这个密码决定了下一次报数的上限。
游戏规则:①给定一个初始密码②循环报数,报到密码值的人要出列,依次类推,直到所有的人都出列本程序要求输入的内容:n个人的密码及初始密码;本程序要求输出的内容:n个人出列的顺序。
2、数据结构为了实现上述功能,可以采用链式存储结构。
采用链式存储结构,定义了一个存储个人信息的结构体,及两个自定义函数,分别用于创建链表和约瑟夫出列操作。
①链表抽象数据类型的定义: #define SLNODE struct slnodeADT SLNODE{数据对象:D={ i a |i a ∈SLNODE, i=1,2,3.... }数据关系:R=φ}ADT SLNODE;②自定义函数:void create_SLnode(SLNODE *p,int n)//创建队列{ 创建链表,为N 个人分配密码 }void Josef(SLNODE *p,int n)//进行约瑟夫操作{输入初始密码m;for(){ 将出列的结点删除,并输出出列序号;}}③本程序的保护模块:结构体模块主程序模块自定义函数模块调用关系:3、程序设计主要算法的流程图:create_SLnode( )算法流程图Josef( )算法流程图四、详细设计1、元素类型、结点的类型及指针#define SLNODE struct slnodeSLNODE//每个结点的结构体{int num;//num代表序号int code;//code代表密码SLNODE *next;};2、自定义函数:void create_SLnode(SLNODE *p,int n)//创建队列,并将其尾指针指向第一个序号{SLNODE *r,*s;s=p;int i,m;cout<<"请给这"<<n<<"个人分配密码:"<<endl;for(i=0;i<n;i++){cout<<"请给第"<<i+1<<"个人输入密码:"<<endl;cin>>m;r=(SLNODE *)malloc(sizeof(SLNODE));r->code=m;r->num=i+1;r->next=s->next;s->next=r;s=s->next;}p=p->next;s->next=p;}void Josef(SLNODE *p,int n)//进行约瑟夫操作{p=p->next;int m;int i,j;SLNODE *r;cout<<"请输入初始密码:"<<endl;cin>>m;cout<<"依次出列的序号为:"<<endl;for(i=0;i<n-1;i++)p=p->next;for(i=0;i<n-2;i++){for(j=0;j<m-1;j++)p=p->next;cout<<(p->next)->num<<endl;m=(p->next)->code;r=p->next;p->next=r->next;}if(m%2==0)cout<<p->num<<endl<<(p->next)->num<<endl;elsecout<<(p->next)->num<<endl<<p->num<<endl;}3、主函数:int main(){SLNODE *p;int n;cout<<"请输入一圈的人数:"<<endl;cin>>n;p=(SLNODE *)malloc(sizeof(SLNODE));p->next=NULL;create_SLnode(p,n);Josef(p,n);return 0;}4、函数的调用关系:主函数main()调用自定义函数void create_SLnode(SLNODE *p,int n);/*创建队列*/与void Josef(SLNODE *p,int n);/*进行约瑟夫操作*/。
数据结构经典题(一)Josephus(约瑟夫)问题
Josephus(约瑟夫)问题有n个人围成一个圈,从第1个人开始报数,数到第m个人,让他出局;然后从出局的下一个人重新开始报数,数到第m个人,再让他出局,……,如此反复直到剩下一个人,问此人编号为几?或:有n个人围成一个圈,从第k个人开始报数,数到第m个人,让他出局;然后从出局的下一个人重新开始报数,数到第m个人,再让他出局,……,如此反复直到所有人出列,由此产生一个出队编号的序列。
1、数组解法#include<iostream>#include<stdlib.h>using namespace std;const int n=11, m=3;int main(){int a[n],p=0;int i,k=0,number=0;for(i=0; i<n; i++) a[i]=i+1;while(number<n-1) //number表示出去的人数{ if(a[p]!=0) //p指向正要报数的人{ k++; //k为1,2,3...报数if(k==m) //报到m时,a[p]出去{ a[p]=0; k=0; number++; }}p=(p+1) % n; //下一个人}for(i=0; i<n; i++)if(a[i]!=0){ cout<<"最后一个获胜者的编号是:"<<i+1<<endl; break; }system("pause");}其中while循环也可改为:while(number<n-1) //number表示出去的人数{while(a[p]==0) p=(p+1) % n; //找到下一个报数的人k++; //k为1,2,3...报数if(k==m) //报到m时,a[p]出去{ a[p]=0; k=0; number++; }p=(p+1) % n;}2、链表解法#include<iostream>#include<stdlib.h>using namespace std;const int n=11, m=3;struct node{ int no;node *next;};int main(){int k=0;node *p,*q,*r;p=q=new node; //创建第一个节点p->no=1;for(int i=2; i<=n; i++) //建立链表{ r=new node;r->no=i;q->next=r;q=r;}q->next=p; //构成一个"环"q=p;while(q->next!=q){ k++; //k为1,2,3...报数if(k==m) //报到m时,删除q所指结点{ p->next=q->next;delete q;q=p->next;k=0;}else{ p=q; q=q->next; }}cout<<"最后一个获胜者的编号是:"<<q->no<<endl; system("pause");}其中while循环也可改为:while(q->next!=q){ for(int i=1; i<m; i++) //直接找到报m的人{ p=q; q=q->next; }p->next=q->next;delete q;q=p->next;}。
约瑟夫问题
约瑟夫问题一、问题描述和要求1、问题描述约瑟夫问题是这样的:设有n个人围圆桌坐成一圈,现从第s个人开始报数,数到m的人出列,接着从出列的下一个人开始重新报数,数到m的人又出列,如此重复下去,直到所有人都出列为止。
2、程序设计要求(1)、通过本学期《数据结构》的学习,综合运用已学过的理论和技能去分析和解决约瑟夫问题,加深对数据结构课程理论的理解和运用、切实加强自己的实践动手能力和创新能力。
(2)、结合C语言程序设计、数据结构中所学的理论知识,小组独立设计方案,培养分析与解决问题的能力。
(3)、学会查阅相关手册和资料,进一步熟悉常用算法的用途和技巧,掌握这些算法的具体含义。
(4)、认真调试程序,学会改正程序中的错误,尝试不同的方法实现同一功能,培养严谨的作风和科学的态度。
二、个人所负责的工作1、算法思路约瑟夫问题的解决可以用线性链表,循环链表,数组等多种方法,我们采用线性链表解决该问题,采用分块的设计思路,利用函数调用解决问题。
本算法采用for循环解决线性链表的建立,用printf函数输出链表,利用for循环找到第s个人开始报数,再利用一个for循环找到第m个人,然后多重if语句讨论不同情况的处理,最后通过函数调用解决约瑟夫问题。
2、我负责的工作在这次程序设计中,我主要负责对整个题目设计思路进行分析和对程序的注释。
我们刚设计出来的程序存在着种种问题,各部分程序都需要进行一定的修改和完善,比如线性链表不能够正常输入,主函数对用户的输入要求的描述太过笼统,输出函数不能调用等等。
我就是不断发现这些问题并完善它们,使程序能够正确的运行。
然后我和其他组员经过商议,对程序的各部分含义进行注释。
在程序演示时我主要负责对整个题目设计思路分析及组员介绍。
以下是我修改过的程序之一:NODE *creatlinklist(int n)/*建立线性链表*/{ int i;NODE *head,*p,*q;/*定义整形实参i,指针head、p、q*/if(n==0)return(NULL);/*如果n=0建立一个空链表*/elsehead=(NODE *)malloc(sizeof *head);/*申请一个结点为表头*/q=head;for (i=1;i<=n;i++)/*将n个结点输入到单链表中*/{p=(NODE *)malloc(sizeof *head);printf("输入数据:");p->info=getche();/*getche功能: 输入后立即从控制台取字符,不以回车为结束*/printf("\n");q->next=p;/*q指向表尾,准备导入下一个结点*/q=p;}p->next=NULL;/*将最后一个结点的链域置为空*/return(head);}三、结论经过这次程序设计任务,我认真的复习了数据结构中学习过的理论知识,尤其是对线性链表这一部分进行了认真的归纳总结,对数据结构的认识更深刻了,对线性链表的建立、插入、删除、查找等操作的运用更加熟练,对for循环、输入输出函数等更加了解,而且我意识到今后我可以利用这些函数解决更多的实际问题。
约瑟夫斯问题
实验一:约瑟夫斯问题实验报告一、问题描述1)问题描述约瑟夫斯(Josephus)问题的一种描述是:编号为1,2,…,n的n个人按顺时针方向围坐一圈,每人持有一个密码(正整数)。
一开始任选一个正整数作为报数上限值m,从第一个人开始按顺时针方向自1开始报数,报到m时停止报数.报m的人出列,将他的密码作为新的m值,从他在顺时针方向下一个人开始重新从1报数,如此下去,直至所有的人全部出列为止。
试设计一个程序,按出列顺序印出各人编号。
2)基本要求利用单向循环链表存储结构模拟此过程,按照出列的顺序印出各人的编号.3)测试数据m的初值为20;n=7,7个人的密码依次为:3,1,7,2,4,8,4。
m初值为6(正确的出列顺序应为6,1,4,7,2,3,5).二、需求分析1.该程序所能实现的功能:将n个数字按照某一特定序列输出,直至全部输出为止。
2.输入的形式及输入值范围:依次输入人数n、每个人的密码及报数上限m值。
3.输出的形式:按照报数上限m及每个人的密码依次输出每个人的编号。
4.测试数据要求:m的初值为20;n=7,7个人的密码依次为:3,1,7,2,4,8,4。
m初值为6(正确的出列顺序应为6,1,4,7,2,3,5)。
三、概要设计1.主程序流程获取每个人的密码,选定m的初值,调用函数,按照特定要求完成输出操作。
2.核心模块的算法伪码主程序:void main(void){int m;people *s;s=GetData();cout〈<"请输入m的初值:"<〈endl;cin〉〉m;DealData(s,m);}预编译代码:#include〈iostream。
h〉四、详细设计1。
实现每个操作的伪码,重点语句加注释(1)建立“人”的结构体struct people{int data;int num;people *next;};(2)获取每个人的相关数据people *GetData(){i nt n;p eople *p1,*p2,*head;h ead=NULL;c out<<”请输入人数:”;c in〉〉n;c out〈〈”请依次输入每个人的密码:”<〈endl;f or(int i=0;i<n;i++){p1=new (people);cin〉〉p1—>data;p1—>num=i+1;if(head==NULL){head=p2=p1; //建立首结点}else{p2-〉next=p1; //中间结点p2=p1;}}i f(head!=NULL)p2-〉next=head;r eturn(head); //循环链表}2。
约瑟夫问题
第一个人出列:从 数器 j 从 1 数到 person[i] <- 0 第二个人出列:从 数器 j 从 1 数到 person[i] <- 0 „„ n-1个出列:„„
k = 0 的后继开始扫描数组,报数计 m,该人出列,把他的状态置成 0,
k = i 的后继开始扫描数组,报数计 m,该人出列,把他的状态置成 0,
此题如果意思是定位第 m 个元素,当不需要跳过任何元素 时。 从 k 开始报数第 m 个元素是: ((( k + m - 1 ) – 1 ) mod ) n +1
“遍历”和“数到”的区别: 遍历到的每个元素需要做相同的操作,而“数到m”第m个 元素和前 m-1 个元素有不同的操作。
如果需要跳过某些标记的元素,就无法直接确定第 m 个元 素的位置,则需要按顺序逐个扫描元素,跳过不符合要求的 元素,对符合要求的元素进行计数。 此处注意第一个数也许需要跳过,第一个报数的编号也许不 是 k,不能直接从 1 计数。 i <- k j <- 0 while ( j< m ) do begin i <- (i mod n) + 1 if ( person[i] <> 0 ) then j <- j+1 end
2.1
模拟方法
2.2
2.1.1 数组+标记 2.1.2 循环链表+删除节点
数学模型
定义数组: array person[1..n]代表 n 个人的状态; 第i个元素代表编号为i的人的状态,下标代表人的编号; 状态: 1代表没有出列,0代表已出列。 使用线性数组表示环状圆圈 * 数组元素间相邻关系和环状结构元素间相邻关系有所不 同,person[n]没有后继节点,person[1]没有前驱节点。 数组可以使用下面方法实现环状结构的相邻关系: person[i]后继节点的下标为j: if i = n then i<- 1 else i <- i+1 或者 i <- (i mod n) +1
约瑟夫问题多种解决方法
著名约瑟夫问题一
• 17世纪的法国数学家加斯帕在《数目的游戏问题》中讲了 这样一个故事:15个教徒和15 个非教徒在深海上遇险, 必须将一半的人投入海中,其余的人才能幸免于难,于是 想了一个办法:30个人围成一圆圈,从第一个人开始依次 报数,每数到第九个人就将他扔入大海,如此循环进行直 到仅余15个人为止。问怎样排法,才能使每次投入大海的 都是非教徒。题目中30个人围成一圈,因而启发我们用一 个循环的链来表示。可以使用结构数组来构成一个循环链。 结构中有两个成员,其一为指向下一个人的指针,以构成 环形的链;其二为该人是否被扔下海的标记,为1表示还 在船上。从第一个人开始对还未扔下海的人进行计数,每 数到9时,将结构中的标记改为0,表示该人已被扔下海了。 这样循环计数直到有15个人被扔下海为止
约瑟夫问题的另外一个有名的例子
• 一堆猴子都有编号,编号是1,2,3 ...m , 这群猴子(m个)按照1-m的顺序围坐一圈, 从第1开始数,每数到第N个,该猴子就要 离开此圈,这样依次下来,直到圈中只剩 下最后一只猴子,则该猴子为大王。
• 二. 基本要求: (1) 输入数据:输入 m,n m,n 为整数,n<m (2)中文提示 按照m个猴子,数n 个数的方法,输出为大 王的猴子是几号 ,建立一个函数来实现此 功能
12
• p2->next=p1; • p2=p1; •} • p2->next=head; • return head; •} • struct monkey *findout(struct monkey *start,int
n) •{ • int i; • struct monkey *p; • i=n; • p=start;
• cout<<"----------------------------------------
有关经典约瑟夫问题的四种解法
有关经典约瑟夫问题的四种解法 约瑟夫问题是信息学奥赛中的⼀类经典且重要的题型,在平常的测试中屡屡出现。
通常题设可抽象为:⼀开始有n个⼈围成⼀个圈,从 1开始顺时针报数,报出m的⼈被踢出游戏.。
然后下⼀个⼈再从1开始报数,直到只剩下⼀个⼈。
或者:曾经有个⼈在他⾝边,然⽽现在只剩他⼀个⼈。
Who are you? Who am I? Why am I here?⾛的越来越慢,⼈越来越少,可终于还是只剩⼀个了呢。
他们围成⼀圈,随机了⼀个⼈作为1号,然后逆时针依次编号。
1号开始报数,报到 1,他⾛了;然后2号开始报数,2号报了1,3 号报了2 ,于是3 号也⾛了……每⼀轮都从上⼀次出局的下⼀个⼈开始报数,第i轮从1 报到i,报i的⼈出局。
直到只剩他⼀个⼈。
却早已不记得他⾃⼰是谁。
针对不同的数据范围,可以存在如下⼏种做法:1. O(nm) O(nm)的复杂度适⽤于n,m都在30000以内的情况,此类题型较少,例如“约瑟夫游戏”⼀题,n,m<=30000,由于随着游戏的不断进⾏,需要枚举的⼈数越少,所以复杂度实际低于O(nm)。
算法思路:暴⼒模拟即可。
#include<bits/stdc++.h>using namespace std;int T,N,M; bool v[1000100];void wk(){memset(v,0,sizeof(v));scanf("%d%d",&N,&M);int t=0,num=0,pos=1;while(1){if(v[pos]){++pos;if(pos==N+1) pos=1;continue;}++num;if(num==M){if(t==N-1){printf("%d\n",pos);return;}v[pos]=1,++t,num=0;}++pos;if(pos==N+1) pos=1;}}int main(){scanf("%d",&T);while(T--) wk();return 0;}暴⼒模拟约瑟夫问题2.O(n) O(n)算法已经适⽤于⼤多数约瑟夫问题,让n<=1e7的数据范围可以被轻松解决,考虑以任意⼀⼈为起点,选出第m个⼈后的编号变化,设起始id==0,选出第m个⼈后,id−>(id+m),再回归到原来的圆形,设i表⽰第i轮游戏,那么整体的公式即为(id+m)%(n−i+1)。
约瑟夫问题
约瑟夫问题约瑟夫问题约瑟夫问题是个有名的问题:N个人围成一圈,从第一个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉。
例如N=6,M=5,被杀掉的人的序号为5,4,6,2,3。
最后剩下1号。
假定在圈子里前K个为好人,后K个为坏人,你的任务是确定这样的最少M,使得所有的坏人在第一个好人之前被杀掉。
举个例子:有64名战士被敌人俘虏了。
敌人命令他们拍成一圆圈,编上号码1,2,3…,64。
敌人把1号杀了,又把3号杀了,他们隔着一个杀一个这样转着圈杀。
最后只剩下一个人,这个人就是约瑟夫斯。
请问约瑟夫斯是多少号?(这就是“约瑟夫斯”问题。
)这个问题解答起来比较简单:敌人从1号开始,隔一个杀一个,第一圈把所有的奇数号码的战士圈杀光了。
剩下的32名战士需要重新编号,而敌人在第二圈杀死的是重新编号的奇数号码。
由于第一圈剩下的全部是偶数号2,4,6,…,64。
把它们全部用2去除,得1,2,3,…,32。
这是第二圈编的号码。
第二圈杀过以后,又把奇数号码都杀掉了,还剩16个人。
如此下去,可以想到最后剩下的必然是64号。
$64=2^6$,它可以连续被2整除6次,是从1到64中能被2整除次数最多的数,因此,最后必然把64 号留下。
如果有65名战士被俘,敌人还是按上述的方法残杀战士,最后还会剩下约瑟夫斯吗?经过计算,很容易得到结论,不是。
因为第一个人被杀后,也就是1号被杀后,第二个被杀的是必然3号。
如果把1号排除在外,那么还剩下的仍是64人,新1号就是3号。
这样原来的2号就变成了新的64 号,所以剩下的必然是2号。
进一步的归类,不难发现如果原来有$2^k$个人,最后剩下的必然$2^k$号;如果原来有$2^k+1$个人,最后剩下2号;如果原来有$2^k+2$个人,最后剩下4号……如果原来有$2^k+m$个人,最后剩下2m号.比如:原来有100人,由于$100=64+36=2^6+36$,所以最后剩下的就是36×2=72号;又如:原来有11 1人,由于$100=64+47=2^6+47$,所以最后剩下的就是47×2=94号传说古代有一批人被蛮族俘虏了,敌人命令他们排成圆圈,编上号码1,2,3,…然后把1号杀了,把3号杀了,总之每隔一个人杀一个人,最后剩下一个人,这个人就是约瑟夫斯。
约瑟夫问题
倒推问题!
例:n=5, m=3
(0+3) mod 5 = 3 5:0(a),1(b),2(c),3(d),4(e) 2 号 c 是5个人中第一个出列 剩下4个人从3(d)开始报数,环为:3(d),4(e),0(a),1(b) 4:0(d),1(e),2(a),3(b) (1+3) mod 4 = 0 2 号 a 是4个人中第一个出列 剩下3个人从3(b)开始报数,环为:3(b),0(d),1(e) 3:0(b),1(d),2(e) (1+3) mod 3 = 1 2 号 e 是3个人中第一个出列 剩下2个人从0(b)开始报数,环为:0(b),1(d) 2:0(b),1(d) (0+3) mod 2 = 1 0 号 b 是2个人中第一个出列 剩下1个人从1(d)开始报数,环为:1(d) 0 1:0(d) 0 号 d 是1个人中第一个出列,也是最后一个出列。
n 个人构成一个环, 0, 1, 2, …, n-2, n-1 称为 n元约瑟夫环。 从 0 开始报数,假设 x 是最后一个出列的人的编号。
假设 k = m mod n 第一个出列的人编号为 k-1(当元素出现在边界时特殊处理) 0, 1, 2,…, k-2, k-1, k, k+1,…, n-2, n-1 他出列之后,剩下的 n-1 个人也构成了一个(n-1)元环: k, k+1,…, n-2, n-1, 0, 1, 2,…, k-2 从 k 开始报 0。 这 n-1 个人最后一个出列的人也是 x
// 出列n-1个人
/* 初始化状态数组 */ for i <- 1 to n step 1 do person[i] <- 1 /* n-1 个人出列 */ i <- 0 for p <- 1 to n-1 step 1 do begin j <- 0 while ( j < m ) do begin i <- (i mod n) + 1 if ( person[i] <> 0 ) then j <- j + 1 end person[i] <- 0 end /* 查找没有出列的人 */ i <- 1 while ( person[i] = 0 ) do i <- i+1 write( i )
Josephu(约瑟夫)问题解析
Josephu(约瑟夫)问题解析Josephu问题为:设置编号为1,2,3,......n的n个⼈围坐⼀圈,约定编号为k(1<=k<=n)的⼈从1看是报数,数到m的那个⼈出列,它的下⼀位⼜从1开始报数,数到m的那个⼈出列,以此类推,直到所有⼈出列为⽌,由此产⽣⼀个出队编号的序列。
提⽰:⽤有个不带头的循环链表来处理Josephu问题:先构成⼀个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下⼀个结点⼜从1开始计数,直到最后⼀个结点从链表中删除算法结束。
代码:public class Demo{public static void main(String[] args){CycLink cyclink=new CycLink();cyclink.setLen(5);cycLink.createLink();cyclink.show();}}//⼩孩class Child{//编号int no;//结点Child nextChild=null;public Child(int no){//给编号this.no=no;}}//环形链表class CycLink{//先定义⼀个指向链表第⼀个⼩孩的引⽤//指定第⼀个⼩孩的引⽤不能动,不然以后找不到他了Child firstChild=null;//定义⼀个游标Child temp=null;//表⽰共有⼏个⼩孩int len=0;//设置链表⼤⼩public void setLen(int len){this.len=len;}//初始化环形链表public void createLink(){for(int i=1;i<=len;i++){if(i==1){//创建第⼀个⼩孩Child ch=new Child(i);this.firstChild=ch;this.temp=ch;}else{//创建最后⼀个⼩孩if(i==len){Child ch=new Child(i);temp.nextChild=ch;temp=ch;temp.nextChild=this.firstChild;}else{//继续创建⼩孩Child ch=new Child(i);//连接,搭桥temp.nextChild=ch;//temp向前⾛⼀步,指向刚刚进来的孩⼦temp=ch;}}}}//打印该环形链表public void show(){Child temp=this.firstChild;do{System.out.println(temp.no);temp=temp.nextChild;}while(temp!=this.fistChild);}}优化:代码:public class Demo{public static void main(String[] args){CycLink cyclink=new CycLink();cyclink.setLen(50);cycLink.createLink();cycLink.setK(2);cycLink.setM(3);cyclink.show();cyclink.play();}}//⼩孩class Child{//编号int no;//结点Child nextChild=null;public Child(int no){//给编号this.no=no;}}//环形链表class CycLink{//先定义⼀个指向链表第⼀个⼩孩的引⽤//指定第⼀个⼩孩的引⽤不能动,不然以后找不到他了 Child firstChild=null;//定义⼀个游标Child temp=null;//表⽰共有⼏个⼩孩int len=0;int k=0;int m=0;//设置mpublic void setM(int m){this.m=m;}//设置链表⼤⼩public void setLen(int len){this.len=len;}//设置从第⼏个⼈开始数数public void setK(int k){this.k=k;}//开始playpublic void play(){Child temp=this.fistChild;//1.先找到开始数数的⼈//int i=1;i<k;因为⾃⼰也要数⼀下,所以i不能为k for(int i=1;i<k;i++){temp=temp.nexChild;}while(this.len!=1){//2.数m下for(int j=1;j<m;j++){temp=temp.nextChild;}//找到要出圈的前⼀个⼩孩,有待优化Child temp2=temp;while(temp2.nextChild!=temp){temp2=temp2.nextChild;}//3.将数到m的⼩孩,退出圈temp2.nextChild=temp.nextChild;//让temp指向数数的⼩孩temp=temp.nextChild;this.len--;}//最后⼀个⼩孩(验证)System.out.println(temp.no);}//初始化环形链表public void createLink(){for(int i=1;i<=len;i++){if(i==1){//创建第⼀个⼩孩Child ch=new Child(i);this.firstChild=ch;this.temp=ch;}else{//创建最后⼀个⼩孩if(i==len){Child ch=new Child(i);temp.nextChild=ch;temp=ch;temp.nextChild=this.firstChild;}else{//继续创建⼩孩Child ch=new Child(i);//连接,搭桥temp.nextChild=ch;//temp向前⾛⼀步,指向刚刚进来的孩⼦ temp=ch;}}}}//打印该环形链表public void show(){Child temp=this.firstChild;do{System.out.println(temp.no);temp=temp.nextChild;}while(temp!=this.fistChild);}}。
约瑟夫问题及变种
“约瑟夫”问题及若干变种例1、约瑟夫问题(Josephus)[问题描述]M只猴子要选大王,选举办法如下:所有猴子按1…M编号围坐一圈,从第1号开始按顺序1,2,…,N 报数,凡报到N的猴子退出到圈外,再从下一个猴子开始继续1~ N报数,如此循环,直到圈内只剩下一只猴子时,这只猴子就是大王。
M和N由键盘输入,1≤N,M≤10000,打印出最后剩下的那只猴子的编号。
例如,输入8 3,输出:7。
[问题分析1]这个例题是由古罗马著名史学家Josephus提出的问题演变而来的,所以通常称为Josephus(约瑟夫)问题。
在确定程序设计方法之前首先来考虑如何组织数据,由于要记录m只猴子的状态,可利用含m个元素的数组monkey来实现。
利用元素下标代表猴子的编号,元素的值表示猴子的状态,用monkey[k]=1表示第k只猴子仍在圈中,monkey[k]=0则表示第k只猴子已经出圈。
程序采用模拟选举过程的方法,设变量count表示计数器,开始报数前将count置为0,设变量current 表示当前报数的猴子编号,初始时也置为0,设变量out记录出圈猴子数,初始时也置为0。
每次报数都把monkey[current]的值加到count上,这样做的好处是直接避开了已出圈的猴子(因为它们对应的monkey[current]值为0),当count=n时,就对当前报数的猴子作出圈处理,即:monkey[current]:=0,count:=0,out:=out+1。
然后继续往下报数,直到圈中只剩一只猴子为止(即out=m-1)。
参考程序如下:program josephus1a {模拟法,用数组下标表示猴子的编号}const maxm=10000;var m,n,count,current,out,i:integer;monkey:array [1..maxm] of integer;beginwrite('Input m,n:');readln(m,n);for i:=1 to m do monkey[i]:=1;out:=0; count:=0; current:=0;while out<m-1 dobeginwhile count<n dobeginif current<m then current:=current+1 else current:=1;count:=count+monkey[current];end;monkey[current]:=0; out:=out+1; count:=0end;for i:=1 to m doif monkey[i]=1 then writeln('The monkey king is no.',i);readlnend.[运行结果]下划线表示输入Input m,n:8 3The monkey king is no.7 {时间:0秒}Input m,n:10000 1987The monkey king is no.8544 {时间:3秒}[反思]时间复杂度很大O(M*N),对于极限数据会超时。
约瑟夫环设计的知识点
约瑟夫环设计的知识点约瑟夫环(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个人完成,可以通过构建一个约瑟夫环来确定每个任务分配给哪个人。
最新实验一约瑟夫问题实验报告
最新实验一约瑟夫问题实验报告实验目的:探究约瑟夫问题(Josephus Problem)的数学规律及其在不同参数下的表现,验证相关算法的效率和准确性。
实验背景:约瑟夫问题是一个著名的理论问题,源自于罗马时代的一个传说。
问题可以描述为:n个人围成一圈,从第一个人开始报数,每数到第m个人,该人出圈,然后从下一个人重新开始报数,如此循环,直到所有人出圈。
本实验旨在通过编程模拟这一过程,并分析结果。
实验方法:1. 采用编程语言(如Python)编写约瑟夫问题的模拟程序。
2. 设定不同的n和m值,运行程序,记录每个人的出圈顺序及最后剩下的人的位置。
3. 分析不同n和m值下的出圈顺序规律。
4. 对比不同算法(如递归法、迭代法)的运行时间,评估效率。
实验步骤:1. 初始化参数:确定模拟的总人数n和报数间隔m。
2. 创建一个循环队列模拟人们围成的圈。
3. 通过循环和条件判断模拟报数和出圈过程。
4. 记录每次出圈的人的编号和最终剩下的人的位置。
5. 改变n和m的值,重复步骤1至4,收集多组数据。
6. 分析数据,寻找出圈规律。
7. 对模拟过程进行计时,比较不同算法的运行时间。
实验结果:1. 通过大量实验数据,发现当n和m的值较小时,可以直观看出出圈顺序的规律。
2. 随着n和m值的增大,出圈顺序变得更加复杂,但依然存在一定的规律性。
3. 实验中使用的迭代法在处理大规模数据时,相比递归法具有更高的效率,递归法在深度较大时可能会导致栈溢出。
4. 通过图表展示了不同n和m值下,最后剩下的人的位置的概率分布。
实验结论:1. 约瑟夫问题的出圈顺序并非完全随机,存在一定的数学规律。
2. 迭代法在解决大规模约瑟夫问题时更为高效和稳定。
3. 本实验为进一步研究约瑟夫问题提供了实验数据和算法优化方向。
建议:对于未来的研究,可以尝试将约瑟夫问题推广到更多变种,如双向报数、不同方向报数等,以及探索其在实际问题中的应用,如网络协议设计、资源分配等。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
“约瑟夫”问题及若干变种例1、约瑟夫问题(Josephus)[问题描述]M只猴子要选大王,选举办法如下:所有猴子按1…M编号围坐一圈,从第1号开始按顺序1,2,…,N 报数,凡报到N的猴子退出到圈外,再从下一个猴子开始继续1~ N报数,如此循环,直到圈内只剩下一只猴子时,这只猴子就是大王。
M和N由键盘输入,1≤N,M≤10000,打印出最后剩下的那只猴子的编号。
例如,输入8 3,输出:7。
[问题分析1]这个例题是由古罗马著名史学家Josephus提出的问题演变而来的,所以通常称为Josephus(约瑟夫)问题。
在确定程序设计方法之前首先来考虑如何组织数据,由于要记录m只猴子的状态,可利用含m个元素的数组monkey来实现。
利用元素下标代表猴子的编号,元素的值表示猴子的状态,用monkey[k]=1表示第k只猴子仍在圈中,monkey[k]=0则表示第k只猴子已经出圈。
程序采用模拟选举过程的方法,设变量count表示计数器,开始报数前将count置为0,设变量current 表示当前报数的猴子编号,初始时也置为0,设变量out记录出圈猴子数,初始时也置为0。
每次报数都把monkey[current]的值加到count上,这样做的好处是直接避开了已出圈的猴子(因为它们对应的monkey[current]值为0),当count=n时,就对当前报数的猴子作出圈处理,即:monkey[current]:=0,count:=0,out:=out+1。
然后继续往下报数,直到圈中只剩一只猴子为止(即out=m-1)。
参考程序如下:program josephus1a {模拟法,用数组下标表示猴子的编号}const maxm=10000;var m,n,count,current,out,i:integer;monkey:array [1..maxm] of integer;beginwrite('Input m,n:');readln(m,n);for i:=1 to m do monkey[i]:=1;out:=0; count:=0; current:=0;while out<m-1 dobeginwhile count<n dobeginif current<m then current:=current+1 else current:=1;count:=count+monkey[current];end;monkey[current]:=0; out:=out+1; count:=0end;for i:=1 to m doif monkey[i]=1 then writeln('The monkey king is no.',i);readlnend.[运行结果]下划线表示输入Input m,n:8 3The monkey king is no.7 {时间:0秒}Input m,n:10000 1987The monkey king is no.8544 {时间:3秒}[反思]时间复杂度很大O(M*N),对于极限数据会超时。
这已经是优化过的程序,大家可以去看未做任何优化的程序josephus1b.pas,这个程序的时间复杂度为O(M*N*K),K是一个不确定的系数,对应着程序中的repeat循环花费的时间。
空间复杂度为O(M)。
program josephus1b;{模拟法,用数组下标表示猴子的编号}const maxm=10000;var m,n,count,current,out,i:integer;monkey:array [1..maxm] of integer;beginwrite('Input m,n:');readln(m,n);for i:=1 to m do monkey[i]:=1;out:=0; count:=1; current:=1;while out<m-1 dobeginwhile count<n dobeginrepeat{寻找圈上的下一只猴子}current:=current+1;if current=m+1 then current:=1until monkey[current]=1;count:=count+1end;monkey[current]:=0; out:=out+1; count:=0end;for i:=1 to m doif monkey[i]=1 then writeln('The monkey king is no.',i);readlnend.[问题分析2]在组织数据时,也可以考虑只记录仍在圈中的猴子的情况。
用一个线性表按编号由小到大依次记录圈中所有猴子的编号,每当有猴子出圈时,即从线性表中删除对应元素,表中元素减少一个。
程序中用变量rest 表示圈中剩余的猴子数,即线性表中元素的总数。
参考程序如下:program josephus2a; {模拟法,用数组元素的值表示猴子的编号}const maxm=10000;var m,n,current,rest,i:integer;monkey:array [1..maxm] of integer;beginwrite('Input m,n:');readln(m,n);for i:=1 to m do monkey[i]:=i;rest:=m; current:=1;while rest>1 dobegincurrent:=(current + n - 1) mod rest;if current=0 then current:=rest;for i:=current to rest-1 do monkey[i]:=monkey[i+1];rest:=rest-1end;writeln('The monkey king is no.',monkey[1]);readlnend.[运行结果]下划线表示输入Input m,n:10000 1987The monkey king is no.8544 {时间:0.8秒}[反思] 时间复杂度为O(M*K),但K远远小于N,是for循环花费的时间,速度教快。
空间复杂度仍然为O(M)。
也可以用monkey[j]存放第j个猴子的后继编号。
当第j个出圈时,只要把monkey[j]的值赋给它的前驱,这样就自然跳过了j,以后再也不会访问到它了,但是时间上反而不如前面的方法。
程序如下:program josephus2b; {模拟法,用数组元素的值表示后继猴子的编号}const maxm=10000;var m,n,current,rest,i:integer;monkey:array [1..maxm] of integer;beginwrite('Input m,n:');readln(m,n);for i:=1 to m-1 do monkey[i]:=i+1;monkey[m]:=1;current:=m;rest:=m;while rest>1 dobeginfor i:=1 to n-1 do current:=monkey[current];monkey[current]:=monkey[monkey[current]];end;writeln('The monkey king is no.',monkey[current]);readlnend.[问题分析3]本题用单向循环链表做,模拟的更形象。
时间复杂度为O(m*n),空间复杂度也是O(m),极限数据比方法2稍慢。
程序如下:program josephus3; {模拟法3,单向循环链表}TYPEpoint=^node;node=recorddata:longint;next:pointend;VAR m,n,s:longint; {s为计数器}p,q,head:point;BEGINreadln(m,n);new(head);q:=head;head^.data:=1;for s:=2 to m dobeginnew(p);p^.data:=s; {建一个结点,并且赋上数据域}q^.next:=p;q:=p; {把p接到q的后面,再把p作为新的q}end;q^.next:=head; {做成循环队列}s:=1;q:=head; {从队头开始报数,计数器也设为1}repeats:=s+1;if s mod n=0 then begin {报到了n}q^.next:=p^.next; {p出圈}dispose(p)endelse q:=p;until q^.next=q;writeln('The monkey king is no.',q^.data);readlnEND.[运行结果]下划线表示输入Input m,n:10000 9873The monkey king is no.8195[问题分析4]能不能不通过模拟而直接求出第k次出圈的猴子的编号呢?这就是递推法的思想。
用递推来求约瑟夫问题的方案,主要是找到m个猴子选大王过程与m-1个猴子选大王过程之间的关系。
假如m=5,n=3,有5个猴子:然后进行第一步选猴子,从第一个数起,3出圈,剩下4只猴子(图2,4被标上红色表示下一次从4数起):接下来的选择过程与m=4,n=3(即四只猴子选大王,图3,从1数起)的情况非常相似。
图2中的4相当于图3中的1,图2中的5相当于图3中的2……。
以此类推,得到如下对应关系(左边一列是图2中的数字,右边一列是图3中的数字):4←1 (注意:4-1=3 ,4=(1+3-1)mod 5 +1)5←2 ( 5=(2+3-1)mod 5 +1)1←3 ( 1=(3+3-1)mod 5 +1)2←4 ( 2=(4+3-1)mod 5 +1)仔细研究一下这些对应关系,便不难发现,从1数到4要数2、3、4三个数,从2数到5要数3、4、5三个数,从3数到1要数4、5、1三个数,从4数到2要数5、1、2三个数——都是三个数,而这个―三‖是由第一个对应关系确定的:delta=4-1=3。
现在假设我们知道m’=4,n=3的方案(a[i])为3、2、4、1,那么按照上面介绍的对应关系, 我们就可以推出m=5,n=3的方案为b[i]=(a[i]+delta-1)mod m +1),即:得到的新序列为1、5、2、4,再在这个序列前加上第一个出队的n(=3)号猴子,就得到3、1、5、2、4,而这就是m=5,n=3的出圈序列。