约瑟夫斯问题求解
3约瑟夫斯问题

后顺时针每隔1枚拿走2枚棋子,连续 转了10周,9次越过A.当将要第10次越 过A处棋子取走其它棋子时,小洪发 现圆周上余下20多枚棋子. 若N是14的 倍数,则圆周上还有多少枚棋子?
作业:
在一个圆周上放一枚黑色 的和1990枚白色棋子.一个 同学进行这样的操作:从 黑色开始,按顺时针方向, 每隔1枚,取走1枚.当他取 到黑子时,圆周上还剩下 多少枚棋子?
• 例2:有1000个学生坐成一圈,依次编号为1,2, 3,…,1000. 现在进行1,2,1,2,…这样报数. 1号学生报1后立即离开,2号学生报2并留下; 三号学生报1后立即离开,4号学生报2并留下; …;如此进行下去,报1的同学离开,报2的同 学留下,直到最后还剩下1个人. 问这个人的编 号是多少?
约瑟夫斯问题
• 在古代,有一个野蛮部落,将抓起来的俘 虏围成一圈,并从某人开始按顺时针方向 以自然数1,2,3, 编号,然后杀掉1号、 3号、5号如此进行下去,隔一个人杀一个 人,直至最后余下一个人为止,这个幸存 者就是约瑟夫斯.
• 例1:有1000人排成一排,依次编号为1,2,3, …,1000. 现在进行1,2,1,2,…这样报数. 1 号学生报1后立即离开,2号学生报2并留下;3号 学生报1后立即离开,4号学生报2并留下;…; 999号学生报1后立即离开,1000号学生报2并留 下;然后从2号开始重新报数,2号学生报1后立 即离开,4号学生报2并留下;…;如此进行下去 ,报1的同学离开,报2的同学留下,直到最后还 剩下1个人. 问这个人的编号是多少?
• 例3:有100张的一摞卡片,玲玲拿着它们,从 最上面的一张开始按如下的顺序进行操作:把 最上面的第一张卡片舍去,把下一张卡片放在 这一摞卡片的最下面。再把原来的第三张卡片 舍去,把下一张卡片放在最下面. 反复这样做直 到手中只剩下一张卡片,那么剩下的这张卡片 是原来那一摞卡片的第几张?
约瑟夫问题(Joseph)的求解

云南大学物理实验教学中心实验报告课程名称:计算机软件技术基础实验项目:线性链表的应用学生姓名:学号:学院系级专业成绩指导教师:实验时间:年时分至时分实验地点:实验类型:教学(演示□验证□综合█设计□)学生科研□课外开放□测试□其它□一、实验目的:在实习四的基础上,用线性链表解决一个应用问题。
二、问题:约瑟夫问题(Joseph)的求解。
问题描述:n只猴子要选大王,选举方法是:所有猴子按1、2、3、……、n编号顺时针方向围坐一圈,从第1号开始按1、2、3、……、m报数,凡报到m号的退出圈外,如此循环报数,直到圈内剩下一只猴子时,这只猴子就是大王。
试设计一个程序,输出猴子出列顺序。
基本要求:利用单循环链表存储结构模拟此过程,n和m值由键盘输入,按照出列的顺序打印各个猴子的编号。
三、程序的编写与调试1、原程序:#include<stdio.h>#include<malloc.h>struct Node{ int data;struct Node *next;};int main(){ struct Node *head, *s, *q, *t;int n, m, count=0, i;printf("请输入猴子总数 m:");scanf("%d",&m);printf("请输入报数数n:");scanf("%d";&n);for(i=0; i<m; i++){s=struct Node *malloc(sizeofstruct Node);//分配空间s->data=i+1;s->next=NULL;if(i==0){head=s;q=head;}else{q->next=s;q=q->next;}}q->next=head;printf("开始顺序:“);q=head;while(q->next!=head){printf("%d ",q->data); //开始时的顺序q=q->next;}printf("%d ",q->data);q=head;printf("\n");printf("出列顺序:");do {count++;if(count==n-1){t=q->next;q->next=t->next;count=0;printf("%d ", t->data); //出列顺序free(t); //释放空间}q=q->next;}while(q->next!=q);printf("\n");printf("猴子大王是: %d\n",q->data);}2、正确程序:#include<stdio.h>#include<malloc.h>struct Node{ int data;struct Node *next;};int main(){ struct Node *head, *s, *q, *t;int n, m, count=0, i;printf("请输入猴子总数 m:");scanf("%d",&m);printf("请输入报数数n:");scanf("%d",&n);for(i=0; i<m; i++){s=(struct Node *)malloc(sizeof(struct Node)); //分配空间s->data=i+1;s->next=NULL;if(i==0){head=s;q=head;}else{q->next=s;q=q->next;}}q->next=head;printf("开始顺序:");q=head;while(q->next!=head){printf("%d ",q->data); //开始时的顺序q=q->next;}printf("%d ",q->data);q=head;printf("\n");printf("出列顺序:");do {count++;if(count==n-1){t=q->next;q->next=t->next;count=0;printf("%d ", t->data); //出列顺序free(t); //释放空间}q=q->next;}while(q->next!=q);printf("\n");printf("猴子大王是: %d\n",q->data);}运行结果:四、实验总结通过此次试验,加深了我对线性表(链式存储)的学习,使我对线性表(链式存储)有了深刻地认识,尤其是循环链表,并学会了用线性链表解决实际应用问题;同时也提高了自己的编程能力,知道了自己在编程方面的不足之处以及常犯的错误,使自己得到了很好的锻炼。
实验报告 约瑟夫问题

pCur->next = pNew;
pCur = pNew;
printf("结点%d,密码%d\n",pCur->id, pCur->cipher);
}
}
printf("完成单向循环链表的创建!\n");
}
(3)运行"约瑟夫环"问题
static void StartJoseph(NodeType **, int)
exit(-1);
}
pNew->id = iId;
pNew->cipher = iCipher;
pNew->next = NULL;
return pNew;
}
(6)测试链表是否为空,空为TRUE,非空为FALSE
static unsigned EmptyList(const NodeType *pHead)
实验内容
利用循环链表实现约瑟夫环求解。
实验说明
1.问题描述
约瑟夫问题的:编号为1,2,....,N的N个人按顺时针方向围坐一圈,每人持有一个密码(正整数),一开始任选一个正整数作为报数上限值M,从第一个人开始按顺时针方向自1开始顺序报数,报到M时停止报数。报M的人出列,将他的密码作为新的M值,从他在顺时针方向上的下一个人开始重新从1报数,如此下去,直至所有人全部出列为止。试设计一个程序求出出列顺序。
{
if(!pHead)
{
return TRUE;
}
return FALSE;
}
实验中遇到的问题及解决方法
实验结果如下:
实验总结(结果和心得体会)
约瑟夫问题多种解决方法

• • • • • • • • • • •
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个人被扔下海为止
约瑟夫问题的链式求解法

实验一约瑟夫问题求解一、实验目的1、掌握上机调试线性表的基本方法;2、掌握线性表的基本操作:插入、删除、查找等运算在顺序存储结构和链式存储结构上的运算。
3、掌握约瑟夫问题求解算法二、实验内容1、认真阅读和掌握本实验的参考程序,并上机运行本程序,可尝试完善删除、查找等运算。
2、参考第二章课件中对瑟夫问题求解算法的描述,选择一种存储结构实现该问题的求解。
三、注意事项:1、在磁盘上创建一个目录,专门用于存储数据结构实验的程序,可通过U盘或邮箱长期保存程序。
2、实验报告要求:(1)算法的完整代码;(2)程序运行结果及分析;(3)实验总结。
四、参考程序1、顺序表的插入(1)程序代码#include "stdio.h"#include "stdlib.h"typedef struct SeqList{int data[100];int length; /* 表长*/}SeqList , *PSeqList;PSeqList creaeNullList_seq()/* 创建空线性表*/{PSeqList palist=(PSeqList)malloc(sizeof(struct SeqList));if(palist!=NULL){palist->length=0;return(palist);}printf("Out of space!!\n");return NULL;}int isNullList_seq(PSeqList palist)/* 判断是否空线性表*/{return (palist->length==0);}int insertPre_seq(PSeqList palist,int p,int x){int q;if(palist->length>=100){printf("overflow!\n");return(0);}if(p<0 || p>palist->length){printf("Not exist!\n");return(0);}if(isNullList_seq(palist)){palist->data[0]=x;palist->length=1;return(1);}for(q=palist->length-1;q>=p;q--)palist->data[q+1]=palist->data[q] ;palist->data[p]=x;palist->length= palist->length+1;return(1);}void main(){int i;PSeqList list;list=creaeNullList_seq(); /* 创建空线性表*/printf("插入前的顺序表为:\n ");for(i=0;i<=9;i++){insertPre_seq(list,i,i*i);printf(" %d " , list->data[i]);}insertPre_seq(list,5,55);/*在下标为5的元素之前插入元素55*/ printf("\n\n插入后的顺序表为:\n ");for(i=0;i<list->length;i++)printf(" %d " , list->data[i]);printf("\n\n");}(2)运行结果2、约瑟夫问题顺序表的实现int josephus_ SeqList (PSeqList josephus_seq, int s, int m){ /*求解约瑟夫问题的出列元素序列入口参数:已经存放数据的顺序表,起始位置s,数m , 出口参数:1表示成功,0表示表中没有元素*/int s1,i,w;if ( ! josephus_seq->length){ printf(“表中无元素”);return (0); }s1=s - 1; /*data数组中下标从0开始*/printf(“输出约瑟夫序列:”);for( i= josephus_seq->length; i>0; i --;){ s1=(s1+m-1)% i; /*找到出列元素的下标*/w= josephus_seq->data[s1];printf(“%d\t”, w)Delete_SeqList(josephus_seq,s1); /*删除出列元素*/} /*for */return(1); /*成功返回*/}3、约瑟夫问题链表的实现int josephus_ LinkList (LinkList josephus_Link, int s, int m){ /*求约瑟夫问题的出列元素序列,入口参数:已经存放数据的链表头指针的地址,起始位置s,数m ,出口参数:1表示成功,0表示表中没有元素*/LinkList p,pre; /*p指向当前结点,pre指向其前驱结点*/int count;if ( ! josephus_Link){ printf(“表中无元素”);return (0);}/*找第s个元素*/p= josephus_Link;for(count=1;count<s;count++) /*查找第s个结点,用p作为第s个结点的指针*/p=p->next;printf(“输出约瑟夫序列:”);while ( p!=p->next) /*输出n-1个元素个结点*/{ for(count=1;count<m;count++){ pre=p;p=p->next;} /*for*/printf(“%d\t”, p->data);pre->next=p->next;free(p);p=pre->next;}/*while*/printf(“%d\t”,p->data); /*输出最后一个元素个结点*/free(p);return 1;}2.约瑟夫问题的求解(1)程序代码#include"stdio.h"#include"stdlib.h"typedef struct node{int data;struct node *next;}lnode,*linklist;linklist Init_josephus(void){linklist head;head=(linklist)malloc(sizeof(lnode));if(!head){printf("Apply place is faliure!\n\n");return NULL;}head->next=NULL;return head;}void Create_josephus(linklist JOS){linklist p,q,H;int x;p=H=JOS;printf("please enter elements:\n");scanf("%d",&x);p->data=x;scanf("%d",&x);while(x!=-1){q=(linklist)malloc(sizeof(lnode));q->data=x;q->next=p->next;p->next=q;p=q;scanf("%d",&x);}p->next=H;p=H;printf("\n");while(p->next!=H){printf("%3d",p->data);p=p->next;}printf("%3d",p->data);printf("\n");}int josephus_linklist(linklist josephus_link,int s,int m){linklist p,pre;int count;if(!josephus_link){printf("There are not have elements in the list!\n");return 0;}p=josephus_link;for(count=1;count<s;count++)p=p->next;printf("The list of josephus is :\n");while(p!=p->next){for(count=1;count<m;count++){pre=p;p=p->next;}printf("%d\t",p->data);pre->next=p->next;free(p);p=pre->next;}printf("%d\t",p->data);free(p);printf("\n");return 1;}main(){int i,j;lnode *Josephus;Josephus=Init_josephus();Create_josephus(Josephus);printf("please enter the first elements number and steps:\n");scanf("%d%d",&i,&j);josephus_linklist(Josephus,i,j);return 2;}(2)程序运行结果(3)实验总结这次实验用的是链式存储结构来解决约瑟夫问题,第一遍编写的程序中,指针使用的有点混乱,头结点不知该如何处理,指针问题解决后,在运行的过程中,头结点的问题一直解决不了即创建的表有问题还没有解决,表的创建中数据的输出里一个很大的数,原因就是头结点中的值域为空;后来将两个结构体类型的指针同时指向头指针,才将问题顺利解决。
约瑟夫斯问题探究

约瑟夫斯问题探究学而思培优陈绍伦问题背景据说著名犹太历史学家约瑟夫斯有过以下的故事:在罗马人占领乔塔帕特后,39个犹太人与约瑟夫斯及他的朋友躲到一个洞中,他们宁死也不愿被敌人抓到,于是决定了一个自杀方式:41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止.可是约瑟夫斯和他的朋友并不想遵从,他们打算选择合适的位置,留到最后避免处决.于是,他将朋友与自己安排在第16个与第31个位置,逃过了这场死亡游戏.也因此,我们以故事的主人公命名这一系列的“抽杀”问题.情景一:直线型让我们先从比较简单的直线型猫吃老鼠着手研究.【问题1】(隔1吃1)猫将4只老鼠排成一行,先隔1只,再吃1只,再隔1只,再吃1只,……一轮结束后再次重新开始,直到最后剩下1只老鼠,这只老鼠是原来的第________只.其实这题更像是一个“脑筋急转弯”:第一只老鼠有可能被吃掉吗?答案显而易见!结论:排成一行,隔1吃1,留下的总是第1只.【问题2】(吃1隔1)(1)猫将4只老鼠排成一行,先吃1只,再隔1只,再吃1只,再隔1只,……一轮结束后再次重新开始,直到最后剩下1只老鼠,这只老鼠是原来的第________只.(2)猫将10只老鼠排成一行,先吃1只,再隔1只,再吃1只,再隔1只,……一轮结束后再次重新开始,直到最后剩下1只老鼠,这只老鼠是原来的第________只.对于数字比较简单的情况,实际动手操作是得到答案的好方法!(1)4只老鼠时:第一轮猫吃掉1、跳过2、吃掉3、跳过4,留下2、4;第二轮猫吃掉2,因此最后剩下的老鼠是原来的第4只.(2)10只老鼠时:第一轮猫吃掉1、跳过2、吃掉3、跳过4、……,留下2、4、6、8、10;第二轮猫吃掉2,跳过4、吃掉6、跳过8、吃掉10,留下4、8;第三轮猫吃掉4,因此最后剩下的老鼠是原来的第8只.观察每轮留下的老鼠位置,我们发现一个有趣的规律:处于第奇数个位置的老鼠,总会被吃掉;而处于第偶数个位置的老鼠留到下一轮.而且,能留下来的老鼠,在下一轮中位置编号也发生了变化:若前一轮在第2k(k为非0自然数)个位置,下一轮将在第k个位置.换句话说,老鼠编号能分解出几个质因数2,这只老鼠就能挺过前几轮!那么结论就显而易见了:结论:排成一行,吃1隔1,留下的是第2n只.(取范围内最大值)【问题3】(吃2隔1)(1)猫将10只老鼠排成一行,先吃2只,再隔1只,再吃2只,再隔1只,……一轮结束后再次重新开始,直到最后剩下1只老鼠,这只老鼠是原来的第________只.(2)猫将20只老鼠排成一行,先吃2只,再隔1只,再吃2只,再隔1只,……一轮结束后再次重新开始,直到最后剩下1只老鼠,这只老鼠是原来的第________只.【问题2】中观察的是因数2的个数,因为“吃1隔1”是以每2只老鼠为一个周期.那么,本题的“吃2隔1”,会不会与因数3的个数有关呢?我们来实际操作一下:(1)10只老鼠时:第一轮猫吃掉1和2、跳过3、吃掉4和5、跳过6,……,留下3、6、9;第二轮猫吃掉3和6,因此最后剩下的老鼠是原来的第9只.还真是!9可以分解出2个因数3,是范围内最多的,它留到了最后.(2)20只老鼠时:第一轮猫吃掉1和2、跳过3、吃掉4和5、跳过6,......,留下3、6、 (18)第二轮猫吃掉3和6、跳过9、吃掉12和15、跳过18,留下9、18;第三轮猫吃掉9、因此最后剩下的老鼠是原来的第18只.这里就有个小问题了:9和18都能分解出2个因数3,为什么留到最后的18呢?重新观察第二轮的结果:9和18都有2个因数3,所以它们都挺过了第2轮.同时,由于都没有第3个因数3,如果猫要继续吃下去的话,它们都无法挺过第3轮!这么看来,18能留到最后的原因,仅仅是因为它的编号比9大而已!于是我们可以得出结论了:结论:排成一行,吃2隔1,留下的是第3n或第23n×只.(取范围内最大值)对于一般情况“排成一行,吃k隔1”,用同样的方法可以得到类似的结论,最终留下的编号是“能分解出因数(1)k+的个数最多且范围内最大的数”.【问题4】(吃1隔1吃1)(1)猫将9只老鼠排成一行,按“吃-隔-吃”的规律吃掉老鼠,一轮结束后再次重新开始,直到最后剩下1只老鼠,这只老鼠是原来的第________只.(2)猫将27只老鼠排成一行,按“吃-隔-吃”的规律吃掉老鼠,一轮结束后再次重新开始,直到最后剩下1只老鼠,这只老鼠是原来的第________只.“吃-隔-吃”也是每3只老鼠为一个周期的.因此我们先从总数为3n时探讨规律:(1)9只老鼠时:第一轮猫吃掉1和3、跳过2、吃掉4和6、跳过5,……,留下2、5、8;第二轮猫吃掉2和8,因此最后剩下的老鼠是原来的第5只.(2)27只老鼠时:第一轮猫吃掉1和3、跳过2、吃掉4和6、跳过5,......,留下2、5、 (26)第二轮猫吃掉2和8、跳过5、吃掉11和17、跳过14、……,留下5、14、23;第三轮猫吃掉5和23,因此最后剩下的老鼠是原来的第14只.答案的5和14,与总数9和27存在什么样的联系呢?这一规律,需要些许灵感才能观察出来:5是1~9的中间数,14是1~27的中间数.当然,这并不只是随意猜测,“中间数”的规律是可以解释的:总数为3的时候,显然留下的是正中间的第2只;总数为9的时候,我们将老鼠们分为3组:{1,2,3}、{4,5,6}、{7,8,9},留到最后的老鼠,它就应该是正中间一组的正中间一个,当然,它也处于所有数的正中间.如此类推,总数为27、81的时候,留下的也是正中间的第14只、第41只老鼠.根据等差数列的知识,我们可以用下列公式将中间数表示出来:结论:3n只,排成一行,“吃-隔-吃”,留下的是第312n+只.也就是说,答案一定在2、5、14、41、……这些数之中.我们得到了一个漂亮的结论!但是,上述结论的前提是“总数为3n只”.如果总数不是3的幂呢?【问题5】(吃1隔1吃1)(1)猫将12只老鼠排成一行,按“吃-隔-吃”的规律吃掉老鼠,一轮结束后再次重新开始,直到最后剩下1只老鼠,这只老鼠是原来的第________只.(2)猫将16只老鼠排成一行,按“吃-隔-吃”的规律吃掉老鼠,一轮结束后再次重新开始,直到最后剩下1只老鼠,这只老鼠是原来的第________只.(1)12只老鼠时:第一轮猫吃掉1和3、跳过2、吃掉4和6、跳过5,……,留下2、5、8、11;第二轮猫吃掉2和8、跳过5、吃掉11,因此最后剩下的老鼠是原来的第5只.(2)16只老鼠时:第一轮猫吃掉1和3、跳过2、吃掉4和6、跳过5,……,留下2、5、8、11、14;第二轮猫吃掉2和8、跳过5、吃掉11、跳过14、……,留下5、14;第三轮猫吃掉5,因此最后剩下的老鼠是原来的第14只.这些结果,在【问题4】中曾经也出现过,也就是说答案仍是312n+.为什么呢?在这里,我们可以用倒推的方法进行思考:(1)显然,当老鼠数量不超过3,最终将留下2;(2)若这一轮老鼠数量不超过3,那么上一轮的老鼠总数不超过9.此时,这一轮的2在上一轮的编号是5;(3)若这一轮老鼠数量不超过9,那么上一轮的老鼠总数不超过27.此时,这一轮的5在上一轮的编号是14;(4)若这一轮老鼠数量不超过27,那么上一轮的老鼠总数不超过81.此时,这一轮的5在上一轮的编号是41;……如此递推可知,活到最终轮的老鼠在每一轮的编号,倒过来数依次是2、5、14、41、……换句话说,答案应该是312n+,而且应取范围内数值最大的一个!结论:排成一行,“吃-隔-吃”,留下的是第312n+只.(取范围内最大值)情景二:封闭型如果把“猫吃老鼠”问题放在圆环上研究,又会有怎么样的变化?【问题6】(吃1隔1)(1)猫将4只老鼠围成一圈,先吃1只,再隔1只,再吃1只,再隔1只,……直到最后剩下1只老鼠,这只老鼠是原来的第________只.(2)猫将8只老鼠围成一圈,先吃1只,再隔1只,再吃1只,再隔1只,……直到最后剩下1只老鼠,这只老鼠是原来的第________只.简单枚举后发现,总数为4时留下的就是第4只;总数为8时留下的就是第8只. 这个结果与直线型的情况完全一致.巧合的是最后一只老鼠总被跳过,所以新的一轮又可以看成是“先吃后隔”的问题,直线型中的“从头开始”,最终结果当然一致.我们还可以用递推的思路理解:“总数为8的隔1吃1问题”,操作一轮后恰好变为“总数为4的隔1吃1问题”.也就是说,最终留下的将是第二轮的第4只,而它也是第一轮的第8只.所以,如果总数为4时留下的是最后一只,那么总数为8时留下的也是最后一只. 同理,总数为16可变为总数为8,总数为32可变为总数为16……因此得出结论:结论:2n 只,围成一圈,吃1隔1,留下的是第2n 只(最后一只).【问题7】(吃1隔1)猫将9只老鼠围成一圈,先吃1只,再隔1只,再吃1只,再隔1只,……直到最后剩下1只老鼠,这只老鼠是原来的第________只.从这里开始,我们要用“化归思想”——将新问题转化为已解决问题——来处理猫吃老鼠问题:总数为2n 的情况我们已经解决过了,那么如果我们考虑让猫吃掉1只,剩下8只时的最后一只,不就是我们要找的答案吗?马上尝试一下:吃掉1、跳过2,此时还有8只老鼠,3是新的起点,那么刚跳过的2就是此时的最后一只了,于是答案为第2只.更一般的情况同样可以归纳出来:(1)总数为M 时,先吃掉一些老鼠使总数变为2n ,也就是要吃掉(2)n M −只. (在这里,为了计算方便,我们希望吃掉的老鼠尽量少,也就意味着2n 要尽量大)(2)找出此时的最后一只老鼠,它正是最后一个“吃-跳”周期中跳过的一只. 由于每2只老鼠为一个“吃-跳”周期,所以最后跳过的是第2(2)n M −只老鼠. 结论:M 只,围成一圈,吃1隔1,留下的是第2(2)n M −只.(n 取范围内最大)【问题8】(隔1吃1)猫将9只老鼠围成一圈,先隔1只,再吃1只,再隔1只,再吃1只,……直到最后剩下1只老鼠,这只老鼠是原来的第________只.继续用化归思想:“从1开始,先隔再吃”,不就是“从2开始的先吃后隔”吗?于是,我们仍然可以按照【问题7】的方法进行计算,最后编号加1就可以了.也就是说,答案是第 32(92)13−+=只.这里还有一个神奇的二进制方法:将总数改写为二进制形式:29(1001)=,将最高位的1移动到最右边,马上可以得到正确答案(0011)3=了!2这个“数位操作”法的原理是这样的:将最高位的1移走时,相当于原数减去了最大的2n;将1放到最右边时,其余各数全部左移一位,即数值乘2;最低位多了1,相当于给数值加1.这正是结论中的2(2)1nM−+!由于这一方法只需对二进制表达进行位移操作,在编程上很容易实现,所以正是计算机解决约瑟夫斯问题的快速算法.【问题9】(吃2隔1)(1)猫将9只老鼠围成一圈,先吃2只,再隔1只,再吃2只,再隔1只,……直到最后剩下1只老鼠,这只老鼠是原来的第________只.(2)猫将18只老鼠围成一圈,先吃2只,再隔1只,再吃2只,再隔1只,……直到最后剩下1只老鼠,这只老鼠是原来的第________只.实际操作发现,(1)的答案为9,(2)的答案为18.也就是说,这个结果也是与直线型完全一致的.与【问题6】类似,我们可以得出结论:结论:3n或23n×只(最后一只).×只,围成一圈,吃2隔1,留下的是第3n或23n【问题10】(吃2隔1)(1)猫将19只老鼠围成一圈,先吃2只,再隔1只,再吃2只,再隔1只,……直到最后剩下1只老鼠,这只老鼠是原来的第________只.(2)猫将20只老鼠围成一圈,先吃2只,再隔1只,再吃2只,再隔1只,……直到最后剩下1只老鼠,这只老鼠是原来的第________只.仿照【问题7】的“化归思想”,如果提前吃掉一部分老鼠,把总数变为3n或23n×,问题将迎刃而解.问题是,应该将总数变成哪一种呢?这里的化归,不仅要看到总数,而且要保证余下的老鼠依然是“吃2隔1”的周期.所以,我们先吃掉、跳过的部分,也必须是整数个“吃2隔1”周期.换句话说,提前吃掉的老鼠总数,必须是一个偶数.那么上述两道题的做法应该是这样的:(1)19只老鼠:19是奇数,吃掉偶数只老鼠,结果为奇数,所以应化归为奇数型即9个.先吃掉19910÷=(个)周期.−=(只),每个周期吃2隔1,即进行了1025此时,最后跳过的老鼠是第5315×=(只),因此答案为第15只.(2)20只老鼠:20是偶数,吃掉偶数只老鼠,结果为偶数,所以应化归为偶数型即18个.先吃掉20182÷=(个)周期.−=(只),每个周期吃2隔1,即进行了221此时,最后跳过的老鼠是第133×=(只),因此答案为第3只.那么,一般结论也可以类似归纳出来了:结论:M只,围成一圈,吃2隔1,若M为奇数,留下的是第(3)23nM−÷×只.(n取范围内最大)若M为偶数,留下的是第(23)23nM−×÷×只.(n取范围内最大)当然,我们可以将类似的结论推广至”封闭型的吃k隔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);/*进行约瑟夫操作*/。
约瑟夫问题详解(CC++)

约瑟夫问题详解(CC++)Josephus 约瑟夫问题假设n个竞赛者排成一个环形,依次顺序编号1,2,…,n。
从某个指定的第1号开始,沿环计数,每数到第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 --> 0k+1 --> 1k+2 --> 2......k-2 --> n-2变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解:例如x 是最终的胜利者,那么根据上面这个表把这个x变回去不刚好就是n个人情况的解吗?变回去的公式很简单:x'=(x+k)%n如何知道(n-1)个人报数的问题的解?显然,只要知道(n-2)个人的解就行了。
(n-2)个人的解呢?当然是先求(n-3)的情况---- 这显然就是一个倒推问题!递推公式:令f[i]表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是f[n]递推公式f[1]=0;f[i]=(f[i-1]+m)%i; (i>1)有了这个公式,我们要做的就是从1-n顺序算出f[i]的数值,最后结果是f[n]。
约瑟夫环问题(Josephus)-文档资料

问题描述
• 已知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
算法设计
Josephus 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]+" "); } }
输出格式:
T行最后min(n,3)个出列的编号。 结果:6 1 5
问题背景
• 这个问题是以弗拉维奥•约瑟夫斯命名的, 它是1世纪的一名犹太历史学家。他在自己 的日记中写道,他和他的40个战友被罗马 军队包围在洞中。他们讨论是自杀还是被 俘,最终决定自杀,并以抽签的方式决定 谁杀掉谁。约瑟夫斯和另外一个人是最后 两个留下的人。约瑟夫斯说服了那个人, 他们将向罗马军队投降,不再自杀。
Josephus问题

for (i=2; i<=n; i++) s=(s+m)%i;
printf ("The winner is %d\n", s+1);
}
这个算法的时间复杂度为O(n),相对于模拟算法已经有了很大的提高。算n,m等于一百万,一千万的情况不是问题了。可见,适当地运用数学策略,不仅可以让编程变得简单,而且往往会成倍地提高算法执行效率。
来比较麻烦,而且时间复杂度高达O(nm),当n,m非常大(例如上百万,上千万)的时候,几乎是没有办法在短时间内出结果的。注意到原问题仅仅是要求出最后的胜利者的序号,而不是要模拟整个过程。因此如果要追求效率,就要打破常规,实施一点数学策略。
为了讨论方便,先把问题稍微改变一下,并不影响原意:
问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人继续从0开始报数。求胜利者的编号。
如何知道(n-1)个人报数的问题的解?显然,只要知道(n-2)个人的解就行了。(n-2)个人的解
呢?当然是先求(n-3)的情况 ---- 这显然就是一个倒推问题!
递推公式:
令f[i]表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是f[n]
递推公式
f[1]=0;
f[i]=(f[i-1]+m)%i; (i>1)
有了这个公式,我们要做的就是从1-n顺序算出f[i]的数值,最后结果是f[n]。因为实际生
活中编号总是从1开始,我们输出f[n]+1
由于是逐级递推ቤተ መጻሕፍቲ ባይዱ不需要保存每个f[i],程序也是很简单: #include <stdio.h>
实验一约瑟夫问题求解实验二停车场问题

实验一约瑟夫问题求解1)内容:约瑟夫(Joseph)问题的一种描述是:编号为1,2,..., n 的n 个人按顺时针方向围坐一圈, 每人持有一个密码(正整数)。
一开始选任一个正整数作为报数上限值m,从第一个人开始按顺时针方向自1开始顺序报数,报到m时停止报数。
报m的人出列,将它的密码作为新的m值,再从下个人开始新一轮报数,如此反复,直到剩下最后一人则为获胜者。
试设计一个程序求出出列顺序。
2)要求:利用单向循环链表存储结构模拟此过程,按照出列的顺序印出各人的编号。
3)测试数据:n=7,7 个人的密码依次为:3,1,7,2,4,8,4 。
m的初值为20,则正确的出列顺序应为6,1,4,7,2,3,5。
4)输入输出:输入数据:建立输入处理输入数据,输入n输入以及每个人的密码;m的初值。
输出形式:建立一个输出函数,输出正确的序列。
实验二停车场问题1)内容:设停车场是一个可停放 n 辆汽车的狭长通道,且只有一个大门可供汽车进出。
汽车在停车场内按车辆到达时间的先后顺序,依次由北向南排列(大门在最南端,最先到达的在最北端),若停车场内已经停满 n辆车,那么后来的车只能在场外等候,一旦有车开走,则等候在第一位的车即可开入(这是一个队列设长度为m);当停车场内某辆车需要开出,则在它之后的车辆必须给它让道,当这辆车驶出停车场后,其他车辆按序入栈。
每辆车按时间收费。
2)要求:以栈模拟停车场,以队列模拟车场外的便道,按照从终端读入数据的序列进行模拟管理。
每一组输入数据包括三个数据:汽车的“到达”(’A’表示)或“离去”(’D’表示)信息,汽车标识(牌照号)以及到达或离去的时刻。
对每一组输入数据进行操作后的输出信息为:若是车辆到达,则输出汽车在停车场内或者便道上的停车位置;若是车辆离去,则输出汽车在停车场停留的时间和应缴纳的费用(便道上不收费)。
栈以顺序结构实现,队列以链表结构实现。
3)测试数据:设 n=3,m=4,停车价格为p=2。
约瑟夫问题

约瑟夫问题约瑟夫问题约瑟夫问题是个有名的问题: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号杀了,总之每隔一个人杀一个人,最后剩下一个人,这个人就是约瑟夫斯。
NOIP算法:约瑟夫问题(C++)

约瑟夫问题也称为约瑟夫斯置换,在计算机编程的算法中,类似问题又称为约瑟夫环,又称“丢手绢问题”。
一、一般形式1.N个人围成一圈,从第一个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉。
例如N=6,M=5,被杀掉的顺序是:5,4,6,2,3,1。
2.有n只猴子,按顺时针方向围成一圈选大王(编号从1到n),从第1号开始报数,一直数到m,数到m的猴子退出圈外,剩下的猴子再接着从1开始报数。
就这样,直到圈内只剩下一只猴子时,这个猴子就是猴王,编程求输入n,m后,输出最后猴王的编号。
算法思路:模拟(1)由于对于每个人只有在与不在环中两种状态(2)开始时每个人都在环中(3)模拟过程,直到环中只剩下一人。
方法一:设立标记,每个元素标记为出队或在队中/ch0302/1748/#include<iostream>#include<algorithm>#include<cstring>#include<cstdio>using namespace std;const int maxn=310;int a[maxn];int main(){int p,num,cnt,n,m,tmp;while(scanf("%d%d",&n,&m)!=EOF&&(n!=0||m!=0)){memset(a,0,sizeof(a));p=1;for(num=1;num<=n;num++){//一共要数n轮for(cnt=1;cnt<=m;cnt++){//每一轮数到mwhile(a[p]==1)p=p%n+1;//如果指针所指已有标记指针后移tmp=p;//此时p指向环中的第cnt只猴子p=p%n+1;//指针后移}a[tmp]=1; //将第m只猴子标记}printf("%d\n",tmp);//第n轮的第m只猴子就是解}return 0;}方法二:使用数组模拟链表codevs1282 约瑟夫问题/problem/1282/#include<iostream>#include<algorithm>#include<cstring>#include<cstdio>#include<vector>using namespace std;const int maxn=30050;int a[maxn];int main(){int n,m,i,j,p;scanf("%d%d",&n,&m);for(i=1;i<n;i++)a[i]=i+1;a[n]=1;p=n;for(i=1;i<=n;i++){for(j=1;j<m;j++)p=a[p];printf("%d ",a[p]);a[p]=a[a[p]];}return 0;}二、深入探讨对于约瑟夫问题,若需要依次记录退出编号的情况:优化方法是使用线段树维护区间和,并进行单点更新。
约 瑟 夫 环 问 题 的 三 种 解 法

约瑟夫问题(数学解法及数组模拟)约瑟夫问题(有时也称为约瑟夫斯置换,是一个出现在计算机科学和数学中的问题。
在计算机编程的算法中,类似问题又称为约瑟夫环。
又称“丢手绢问题”.)据说著名犹太历史学家 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就能得到原来的编号。
约瑟夫问题及变种

“约瑟夫”问题及若干变种例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),对于极限数据会超时。
经典数学故事之约瑟夫问题

有⼀个古⽼的传说,有64名战⼠被敌⼈俘虏了,敌⼈命令它们排成⼀个圈,编上号码1,2,3,……64。
敌⼈把1号杀了,⼜把3号杀了,他们是隔⼀个杀⼀个这样转着圈杀。
最后剩下⼀个⼈,这个⼈就是约瑟夫,请问约瑟夫是多少号?
这就是数学上有名的“约瑟夫问题”。
给⼤家⼀个提⽰,敌⼈从l号开始,隔⼀个杀⼀个,第⼀圈把奇数号码的战⼠全杀死了。
剩下的32名战⼠需要重新编号,⽽敌⼈在第⼆圈杀死的是重新编排的奇数号码。
按照这个思路,看看你能不能解决这个问题?
答案解析:
由于第⼀圈剩下的全部是偶数号2,4,6,8,……64。
把它们全部⽤2除,得1,2,3,4,……32.这是第⼆圈重新编的号码。
第⼆圈杀过之后,⼜把奇数号码都杀掉了,还剩下16个⼈。
如此下去,可以想到最后剩下的必然是64号。
64=2×2×2×2×2×2,它可以连续被2整除6次,是从1到64中质因数⾥2最多的数,因此,最后必然把64号剩下。
从
64=2×2×2×2×2×2还可以看到,是转过6圈之后,把约瑟夫斯剩下来的。
数据结构实验报告—约瑟夫问题求解

数据结构实验报告—约瑟夫问题求解《计算机软件技术基础》实验报告I—数据结构实验⼀、约瑟夫斯问题求解⼀、问题描述1.实验题⽬:编号1,2,....,n的n个⼈顺时针围坐⼀圈,每⼈持有⼀个密码(正整数)。
开始选择⼀个正整数作为报数上限m,从第⼀个⼈开始顺时针⾃1报数,报到m的⼈出列,将他的密码作为新的m值,从他在顺时针⽅向下⼀个⼈开始重新从1报数,直⾄所有⼈全部出列。
2.基本要求:利⽤单向循环链表存储结构模拟此过程,按照出列的顺序印出个⼈的编号。
3.测试数据:n=7,7个⼈的密码依次为:3,1,7,2,4,8,4.m初值为6(正确的出列顺序应为6,1,4,77,2,3)。
⼆、需求分析1.本程序所能达到的基本可能:该程序基于循环链表来解决约瑟夫问题。
⽤循环链表来模拟n个⼈围坐⼀圈,⽤链表中的每⼀个结点代表⼀个⼈和他所代表的密码。
在输⼊初始密码后m,对该链表进⾏遍历,直到第m个结点,令该结点的密码值作为新的密码值,后删除该结点。
重复上述过程,直⾄所有的结点被释放空间出列。
2.输⼊输出形式及输⼊值范围:程序运⾏后提⽰⽤户输⼊总⼈数。
输⼊⼈数n后,程序显⽰提⽰信息,提⽰⽤户输⼊第i个⼈的密码,在输⼊达到预定次数后⾃动跳出该循环。
程序显⽰提⽰信息,提⽰⽤户输⼊初始密码,密码须为正整数且不⼤于总⼈数。
3.输出形式提⽰⽤户输⼊初始密码,程序执⾏结束后会输出相应的出列结点的顺序,亦即其编号。
⽤户输⼊完毕后,程序⾃动运⾏输出运⾏结果。
4.测试数据要求:测试数据n=7,7个⼈的密码依次为:3,1,7,2,4,8,4。
m初值为6(正确的出列顺序应为6,1,4,7,2,3,5)。
三、概要设计为了实现上述功能,应⽤循环链表来模拟该过程,⽤结构体来存放其相应的编号和密码信息,因此需要循环链表结构体这个抽象数据类型。
1.循环链表结构体抽象数据类型定义:ADT Node{数据对象:D={ai,bi,ci|ai∈int, bi∈int,ci∈(Node*),i =1,2...,n,n ≥0}:数据关系:R=?基本操作:CreatList(int n) //构建循环单链表;Order(int m,node *l) //输出函数,输出出列顺序并删除链表中的结点;}ADT node;2. ADT的C语⾔形式说明:typedef struct Node{int num; //结点的数据域,存放编号;int word; //结点的数据域,存放密码;struct Node *next; //结点的指针域,存放指向下⼀结点的指针;}Node;Node *CreatList( ) //建⽴循环单项链表;void Order(Node *h) //输出出列顺序并删除结点;3. 主程序流程及其模块调⽤关系:1).主程序流程:先提⽰⽤户输⼊相关数据:总⼈数,运⾏循环链表结构体模块,输⼊每个⼈持有的密码值,创建出新链表,运⾏输出函数模块,再输⼊初始密码m值,输出出列序列。
约瑟夫题目和算法解决思路

请同学们根据题目和算法解决思路,编程实现该问题的求解。
2.4应用举例—Josephus问题设有n个人围坐在一个圆桌周围,现从第s个人开始报数,数到第m的人出列,然后从出列的下一个人重新开始报数,数到第m的人又出列,…,如此反复直到所有的人全部出列为止。
Josephus问题是:对于任意给定的n,s和m,求出按出列次序得到的n个人员的序列。
循环链表表示1 建立循环单链表2 寻找第s个结点,输出并删除第m个节点。
约瑟夫问题的解法,算法实现伪代码#include〈iostream。
h〉#include“CircList.h”void Josephus ( int n, int s, int m){for(int k=0;k〈s-1;k++)Next( );for ( int i=0;i<n—1;i++ ) {//执行n—1次for ( int j=0;j<m-1;j++ ) Next();cout << “Delete person” 〈<getData ( ) <〈endl; //数m-1个人Remove ( ); //删去}}void main( ) {CircList<int〉clist;int n,m,s;cout〈< “Enter the Number of Contestants?”;cin >> n 〉> m>〉s;for(int i=1;i〈=n;i++ ) clist。
insert (i); //形成约瑟夫环clist.Josephus (n,s,m);//解决约瑟夫问题}。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
实验一:约瑟夫斯问题求解一、问题描述1 •实验题目:约瑟夫斯问题的一种描述是:编号为 1,2,……n的n个人按顺时针方向围坐一圈,每人持有一个密码9(正整数)。
一开始任选一个正整数作为报数上限值m从第一个人开始按照顺时针方向自1开始报数,报到m时停止报数,报m的人出列,将他的密码作为新的m值,从他在顺时针方向下一个人开始重新重新从1报数,如此下去,直至所有的人全部出列为止。
试设计一个程序,按出列顺序印出各人编号。
2. 基本要求:利用单向循环链表模拟此过程,按照出列的顺序印出各人编号。
3. 测试数据:n=7,7个人的密码依次为 3, 1, 7, 2, 4, 8,4.m的初始值为6 (正确的出列顺序应为 6, 1 , 4, 7, 2, 3, 5。
二、需求分析1. 程序所能达到的基本可能:本程序能够按出列顺序印出各人编号, 程序运行后显示提示信息,提示用户输入人数 n, 初始报数值 m, n 个人的密码,程序需自动考虑重复的,用户输入完毕后,程序自动输出运算结果。
2. 输入的形式及输入值范围:输入人数 n, 初始报数值 m, n 个人的密码,所有值均为正整数 int 型。
3. 输出的形式:输出的是按出列顺序印出各人编号,为正整数int 型。
4. 测试数据要求:测试数据要求为 int 型三、概要设计1. 所用到得数据结构及其 ADT为了实现上述功能,应以单向循环链表有序链表表示集合数据类型。
1. 单向循环链表抽象数据类型定义:typedef struct node{ElemType data;ElemType num;struct node *next;}SLNODE;基本操作:struct node *create_sl(int n);// 创建单向循环链表2. 主程序流程及其模块调用关系1)创建循环链表的流程图2)约瑟夫问题求解流程图开始i丄p=head;i=1P-3)主函数流程图1. 实现每个操作的伪码,重点语句加注释 主程序: void mai n() { SLNODE *head; int n,m;head=(SLNODE *)malloc(sizeof(SLNODE));printf (” 输入总人数n=\n"); scan f("%d", &n); printf (”输入初始报数值 m=\n");scan f("%d", &m); head=create_sl( n); Josephus(head, n, m); } 2. 创建循环单链表 struct node *create_sl(i nt n) {四、详细设计*********************************** */\n"); // 初始界面printf (”学号:031350102\n"); printf(”姓名:王亚文\n"); printf (”约瑟夫斯问题求解\n"); prin tf("/* *********************************** */\n");SLNODE *p,*s,*head;ElemType x;int a;head=(SLNODE *)malloc(sizeof(SLNODE)); p=head;head->next=head;for(a=1;a<=n;a++) // 循环直到输入 n 个密码值跳出循环 { s=(SLNODE *)malloc(sizeof(SLNODE));printf(" 请输入第%d个人的密码值\n",a);scanf("%d",&x);s->data=x;s->num=a;if(head->next==head) head=s;else p->next=s;p=s;}p->next=head;return head;}3. 约瑟夫斯问题求解:void Josephus(SLNODE *head,int n,int m){SLNODE *p,*q;int i;p=head;printf(" 出列序列 :");while(p->next!=p){for(i=1;i<m;i++) // 报数报的m时跳出循环{q=p; p=p->next;}m=p->data; // 读取新的密码值prin tf("%4d",p-> num);q_>next=p->next; // 删除 p 节点free(p);p=q _>n ext;}prin tf("%4d\n",p-> num);}4. 函数调用关系图主函数中调用了 struct n ode *create_sl(i nt n);void Josephus(SLNODE *head, int n,i nt m); 两个函数五、调试分析1.设计与调试过程中遇到的问题分析、体会1) 创建单链表时,一开始写的程序是void create_sl(SLNODE *head ,int n), 并没有没有报错,但最后运行时却不是想象的结果,然后尝试在主函数中写一个printf 函数看一下创建表是否创建成功,事实证明并没有,后来改成了struct node *create_sl(i nt n); 解决了这个问题,再次就是建表的时候发现最后一个数并不是我输入的数,然后就是开始改那个循环=函数,发现我虽然是读了7个数,但第7个数并没有赋值给链表,原错误函数:p=head;head->n ext=head;printf(" 请输入密码值\n");scan f("%d", &x);for(a=1;a<n;a++) // 循环直到输入n个密码值跳出循环{ s=(SLNODE *)malloc(sizeof(SLNODE)); printf(" 请输入密码值 \n");scanf("%d",&x);s->data=x;s->num=a;if(head->next==head) head=s;else p->next=s;p=s;}经过修正后的函数: p=head;head->next=head;for(a=1;a<=n;a++) // 循环直到输入 n 个密码值跳出循环{s=(SLNODE *)malloc(sizeof(SLNODE));printf(" 请输入第%d个人的密码值\n",a);scanf("%d",&x);s->data=x;s->num=a;if(head->next==head) head=s;else p->next=s;p=s;}2) 建表成功之后开始解决本次的主问题约瑟夫斯求解问题,本问题主要考虑循环终止条件,一开始写的是 head->next=head; 发现经常运行错误,后来修正用 p->next!=p ,然后最后一个 p 值单独写一句输出 printf("%4d\n",p->num); 中间的句子就是找到报数值然后删除,注意保留要删除节点的密码值,并没有什么大问题。
还有一个问题,就是在开始的时候创建单链表并没有想到要用序号值num最开始定义单链表的语句:typedef struct node{ElemType data;struct node *next;}SLNODE; 然后就会在创建链表赋值时和解决约瑟夫斯问题时都要重新定义一个变量x 进行计数,增加了程序的复杂度最后修正为:typedef struct node{ElemType data;ElemType num;struct node *n ext;}SLNODE;3) 剩下的还有一些小问题,比如少打了一个字母,打错一个字母,这些程序会报错,不属于逻辑错误,所以解决起来也比较快,2. 主要算法的时间复杂度分析创建单链表的时间复杂度为O(n);约瑟夫斯问题的时间复杂度与n值有关,也与每个人的密码值有关,时间复杂度O(mn ;空间复杂度为O(n);六、使用说明程序运行后显示提示信息,提示用户输入人数n,初始报数值 m n个人的密码,用户按照提示输入完毕后,程序自动输出运算结果。
七、测试结果八、附录#i nclude <stdio.h>#i nclude <malloc.h>#i nclude <stdlib.h>#in clude <time.h> typedef int ElemType;typedef struct node{ElemType data;ElemType num; struct node *next;}SLNODE; // 单链表的定义 struct node *create_sl(int n);void Josephus(SLNODE *head,int n,int m); int main() // 主函数{SLNODE *head; int n,m; time_t rawtime;struct tm * timeinfo;time (&rawtime); timeinfo = localtime (&rawtime);printf("/* *********************************** */\n"); // 初始界面head=(SLNODE *)malloc(sizeof(SLNODE));printf(" 学号:031350102\n");printf(" 姓名:王亚文\n");printf(" 约瑟夫斯问题求解\n");printf("/* *********************************** */\n");printf(" 输入总人数n=\n");scanf("%d",&n);printf(" 输入初始报数值m=\n"); scanf("%d",&m);head=create_sl(n); // 创建单链表Josephus(head,n,m);printf ("Current local time and date: %s", asctime(timeinfo));return 0;}struct node *create_sl(int n){SLNODE *p,*s,*head;ElemType x;int a;head=(SLNODE *)malloc(sizeof(SLNODE)); p=head;head->next=head;for(a=1;a<=n;a++) // 循环直到输入 n 个密码值跳出循环 {s=(SLNODE *)malloc(sizeof(SLNODE));printf(" 请输入第%d个人的密码值\n",a);scanf("%d",&x);s->data=x;s->num=a;if(head->next==head) head=s;else p->next=s;p=s;}p->next=head;return head;}void Josephus(SLNODE *head,int n,int m)// 约瑟夫斯问题求解{SLNODE *p,*q;int i;p=head;printf(" 出列序列 :");while(p->next!=p){for(i=1;i<m;i++) // 程序运行到第m个人跳出循环{q=p; p=p->next;}m=p->data; // 读取新的密码值printf("%4d",p->num);q->next=p->next; // 删除 p 节点free(p);p=q->next;}printf("%4d\n",p->num);}九、实验收获和感想通过本实验首先是解决了约瑟夫斯问题,利用计算机快速解决这个问题,其次在程序编写过程中慢慢掌握了循环单链表的一些用法以及注意事项,因为距离上次学习 c 语言已经有很长一段时间,这次确实遇到了很多障碍,不仅仅是对链表的生疏,也包含以前基础的薄弱环节,通过一个程序的设计,慢慢回忆起程序的编写,加强自己的逻辑性,编程是一个很麻烦的事,一个小错误的出项,程序就不会出现预想的结果,如果报错还可以按照提示找到,若有逻辑错误还得一行一行检查,最后发现如果程序需要解决多个问题,最好还是分模块解决,解决完一个问题再去解决另一个问题,如果一个模块有错误及时发现,也比较好改。