约瑟夫斯问题求解
- 格式:doc
- 大小:712.00 KB
- 文档页数:10
[阅读材料]世界名题与小升初之:抽杀问题(約瑟夫问题)--马到成功老师在各类竞赛中,各类小升初考试中相关的世界名题出现的概率极高,这是由小升初与数学竞赛的特点决定,这特点便是:知识性,趣味性,思想性相结合。
先给大家介绍这一问题的由来。
据说著名犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特後,39 個犹太人与Josephus及他的朋友躲到一個洞中,39個犹太人決定宁愿死也不要被人抓到,于是決定了一个自杀方式,41個人排成一个圆圈,由第1個人开始报数,每报数到第3人该人就必須自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。
然而Josephus 和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他將朋友与自己安排在第16個与第31個位置,于是逃过了这场死亡游戏。
解法約瑟夫问题可用代数分析來求解,将这个问题扩大好了,假设现在您与m个朋友不幸参与了这个游戏,您要如何保护您的朋友?只要画两个圆圈就可以让自己与朋友免于死亡游戏,这两个圆圈是排列顺序,而外圈是自杀顺序,如下图所示:使用程式来求解的话,只要将阵列当作环状来处理就可以了,在列中由计数1开始,每找到三个无资料区就填入一个计数,直接计数來求解的話,只要將阵列当作环状来处理就可以了,在阵列中由計数1开始,每找到三个无资料区就填入一个計数,直而計数达41为止,然后將阵列由索引1开始列出,就可以得知每个位置的自杀順序,这就是約瑟夫排列,41個人报数3的約瑟夫排列如下所示:14 36 1 38 15 2 24 30 3 16 34 4 25 17 5 40 31 6 18 26 7 37 19 8 35 27 9 20 32 10 41 21 11 28 39 12 22 33 13 29 23由上可知,最后一個自杀的是在第31个位置,而倒数第二个自杀的要排在第16个位置,之前的人都死光了,所以他们也就不知道約瑟夫与他的朋友并没有遵守游戏规则了。
一问题描述1 题目内容:约瑟夫(Joseph)问题的一种描述是:编号为1,2,..., n的n 个人按顺时针方向围坐一圈, 每人持有一个密码(正整数)。
一开始选任一个正整数作为报数上限值m,从第一个人开始按顺时针方向自1开始顺序报数,报到m时停止报数。
报m的人出列,将它的密码作为新的m值。
试设计一个程序求出出列顺序。
2 基本要求:利用单项循环链表存储结构模拟此过程,按照出列的顺序印出各人的编号。
3 测试数据:m的初值为20;n=7,7个人的密码依次为:3,1,7,2,4,8,4(正确的出列顺序应为6,1,4,7,2,3,5)。
二需求分析程序运行后,首先要求用户指定初始报数上限值,然后读取个人的密码。
输入数据:建立输入处理输入数据,输入m的初值,n ,输入每个人的密码,建立单循环链表。
输出形式:建立一个输出函数,将正确的输出序列三概要设计利用单项循环链表存储结构模拟此过程1 循环链表的抽象数据类型循环链表是单链表的一种变化形式,把单链表的最后一个节点的next指针指向第一个节点,整个链表就形成了一个环。
2 循环链表的基本操作(仅列出用在本程序的)creat(n)操作结果:构造一个长度为n的无头节点的循环链表,并返回指向最后一个节点的指针find(m,s)初始条件:循环链表存在操作结果:找到当前元素(即s)后面第m个元素print(&m,&n,&s)初始条件:循环链表存在操作结果:从s中删除约舍夫问题中下一个被删除的元素,并将此元素显示在屏幕上3 本程序包括4个模块:主程序模块;创建循环链表模块;找节点模块;删节点模块;各模块调用关系如下图所示:4 约舍夫问题的伪码算法void main( ){输入参与的人数;输入第一个密码;创建无头节点的循环链表;输出第一个出列元素;输出剩余出列元素;}四详细设计1 实现概要设计的数据类型typedef struct LNode{int data;int num;struct LNode *next;}LNode,*linklist; //无头节点的循环链表的节点类型2 每个子函数的算法linklist creat(int n){/*构造一个长度为n的无头节点的循环链表,并返回指向最后一个节点的指针*/linklist head,s; //head为头节点标记s为链表中节点int i;s=head=(linklist)malloc(sizeof(LNode)); //创建头节点for(i=1;i<n;i++) //建立循环链表{s->data=i;printf("num%d: ",i);scanf("%d",&(s->num));/*输入第i个人的密码*/while(s->num<=0){/*如果输入的s->num小于等于0,要求重新输入*/ printf("请重新输入\nnum%d: ",i);scanf("%d",&s->num);}s->next=(linklist)malloc(sizeof(LNode)); //开辟下一个节点s=s->next;}s->data=i;printf("num%d: ",i);scanf("%d",&(s->num));s->next=head;return(s);}linklist find(int m,linklist s) //找到当前元素后面第m个元素{int i;for(i=0;i<m-1;i++)s=s->next;return(s); //返回找到元素的指针}void print(into &mint &n,linklist &s){linklist p;s=find(m,s); //找到待删除的元素printf("%d ",s->next->data);/*输出找到的元素*/m=s->next->num;/*将此元素从链表中删除,并释放此节点*/ p=s->next;s->next=s->next->next;free(p);--n; //约舍夫环中节点数少一}3 主程序算法void main( ){/*解决约舍夫问题的主函数*/int n,m; //n为约舍夫环内初始人数m为初始密码printf("type in n :");scanf("%d",&n);/*输入n*/while(n<=0){/*如果输入的n小于等于0,要求重新输入*/printf("please type n in again \ntype in n :");scanf("%d",&n);}printf("type in m :");scanf("%d",&m);/*输入m*/while(m<0){/*如果输入的m小于0,要求重新输入*/printf("please type m in again \ntype in m :");scanf("%d",&m);}linklist s;s=creat(n);/*创建无头节点的循环链表,返回指向最后一个元素的指针*/printf("the sequence is ");print(m,n,s);//输出第一个出列的元素while(n){print(m,n,s);//输出剩余出列的元素}printf("\n");}4 函数调用关系图五调试分析调试过程中出现过如下问题:1 开始编程序时没考虑输入错误的问题,导致输入错误后程序出错2 编程序时删除节点子程序结束条件出错3 对开辟的节点用完后没有释放六使用说明程序运行后按提示输入n和m的值,在输入约舍夫环中每个人的密码,运行即可得到出列顺序七测试结果进入程序后要求输入n的值然后输入m的值再输入每个人的密码最后得到出列顺序八附录(源程序)这里附上两种源程序,本质上相同,只是第一个程序按老师要求写为很多子函数形式,第二个是我已开始编的,一个大函数。
约瑟夫问题的经典三个例子以下是 9 条关于约瑟夫问题的经典例子:例子 1:想想看,一群小朋友围成一圈玩游戏,就像我们小时候那样。
这时候说从某个小朋友开始报数,每隔一个人淘汰,最后剩下的那个就是胜利者。
这不就是约瑟夫问题嘛。
就好像在一个神秘的游戏圈子里,大家都紧张又兴奋地等待着命运的裁决。
例子 2:你能想象军队里士兵们站成一圈,然后用这种方式来决定谁去执行特殊任务吗?哎呀呀,那场面肯定很刺激。
每个士兵心里都七上八下的,不知道自己是不是那个“幸运儿”,这和约瑟夫问题如出一辙。
例子 3:假如在一场盛大的聚会中,大家玩这样的游戏,是不是超级有趣?就像一个魔法圈,把大家的注意力都吸引过来了。
每淘汰一个人,大家就会一阵惊呼,这不正是约瑟夫问题带来的独特体验嘛。
例子 4:你看过那种生存挑战节目吗?选手们围成一圈,然后通过类似约瑟夫问题的规则来淘汰人。
哇塞,那紧张的氛围,可不就是在经历一场残酷的竞争,这就是约瑟夫问题在现实中的精彩呈现呀!例子5:好比一群探险家在荒岛上,为了分配重要资源而采取这种方式。
每个人都祈祷自己不要被先淘汰掉,这种感觉是不是很奇妙?这就是约瑟夫问题带来的不确定性啊。
例子6:想象一下公司团建的时候玩这个,大家既期待又担心。
“哎呀,可别先轮到我呀!”“哇,我居然留下来了。
”这种种反应,不就是约瑟夫问题的魅力所在吗?例子 7:学校运动会上,各班学生围成一圈进行比赛,多刺激呀!有人欢喜有人忧,这不就是约瑟夫问题所引发的情绪波澜吗?例子 8:在一个神秘的魔法学院里,学生们也用这种方式来选拔优秀学员。
每一个人都全神贯注,这和约瑟夫问题一样充满了悬念呢!例子9:如果在一个古老的部落中,用约瑟夫问题来决定首领的继承人,那该是多么惊心动魄的场面啊。
大家的心都提到了嗓子眼,。
Wikioi : 1282 约瑟夫问题题目描述Description有编号从1到N的N个小朋友在玩一种出圈的游戏。
开始时N个小朋友围成一圈,编号为I+1的小朋友站在编号为I小朋友左边。
编号为1的小朋友站在编号为N的小朋友左边。
首先编号为1的小朋友开始报数,接着站在左边的小朋友顺序报数,直到数到某个数字M时就出圈。
直到只剩下1个小朋友,则游戏完毕。
现在给定N,M,求N个小朋友的出圈顺序。
输入描述Input Description唯一的一行包含两个整数N,M。
(1<=N,M<=30000)输出描述Output Description唯一的一行包含N个整数,每两个整数中间用空格隔开,第I个整数表示第I个出圈的小朋友的编号。
分析:由题意也可以看出,算法明确的分为两部分:模拟约瑟夫+ 求取原始序号。
1. 约瑟夫模拟:使用相对坐标(相对于环内),例如当前被踢位置为k,下一个被踢的是+m,则k被踢掉以后,原本的k+1就成了k(相对坐标嘛)。
这样下一个位置就应该是新K + m-1 。
再考虑到循环意义:next = (k + m-1 - 1) % 人数+ 1。
当m为负向时,同理,只是要注意保证求得的下一次位置值是个正值。
2. 取原始序号:这个直接看下面的代码( update() )Code:#include <iostream>#include <cstdio>using namespace std;#define LL long longconst int N = 30001*4;#define lson l,m,rt<<1#define rson m+1,r,rt<<1|1int sum[N<<2];int tree[N<<2][2];void PushUp(int rt){sum[rt] = sum[rt<<1] + sum[rt<<1|1];}void build(int l,int r,int rt){tree[rt][0] = l;tree[rt][1] = r;if(l == r){sum[rt] = 1;return;}int m = (l+r)>>1;build(lson);build(rson);PushUp(rt);}int update(int p,int rt){sum[rt] --;if(tree[rt][0] == tree[rt][1]){sum[rt] = 0;return tree[rt][0];//从绝对位置剔除}if(p <= sum[rt<<1]) return update(p,rt<<1);else return update(p-sum[rt<<1],rt<<1|1);PushUp(rt);}int main(){int n,m;scanf("%d%d",&n,&m);build(1,n,1);int pos = 1;int seq = 1;for(int i = 0 ; i < n ; i++){seq = (seq + m - 1) % sum[1];//seq 只是相对位置if(seq == 0) seq = sum[1];pos = update(seq,1);cout<<pos<<" ";}return 0;}。
阅读材料世界名题与小升初之:抽杀问题约瑟夫问题--马到成功老师在各类竞赛中,各类小升初考试中相关的世界名题出现的概率极高,这是由小升初与数学竞赛的特点决定,这特点便是:知识性,趣味性,思想性相结合;先给大家介绍这一问题的由来;据说着名犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特後,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止;然而Josephus 和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏;解法约瑟夫问题可用代数分析来求解,将这个问题扩大好了,假设现在您与m个朋友不幸参与了这个游戏,您要如何保护您的朋友只要画两个圆圈就可以让自己与朋友免于死亡游戏,这两个圆内圈是排列顺序,而外圈是自杀顺序,如下图所示:使用程式来求解的话,只要将阵列当作环状来处理就可以了,在陈列中由计数1开始,每找到三个无资料区就填入一个计数,直接计数来求解的话,只要将阵列当作环状来处理就可以了,在阵列中由计数1开始,每找到三个无资料区就填入一个计数,直而计数达41为止,然后将阵列由索引1开始列出,就可以得知每个位置的自杀顺序,这就是约瑟夫排列,41个人报数3的约瑟夫排列如下所示:14 36 1 38 15 2 24 30 3 16 34 4 25 17 5 40 31 6 18 26 7 37 19 8 35 27 9 20 32 10 41 21 11 28 39 12 22 33 13 29 23由上可知,最后一个自杀的是在第31个位置,而倒数第二个自杀的要排在第16个位置,之前的人都死光了,所以他们也就不知道约瑟夫与他的朋友并没有遵守游戏规则了;小升初常见抽杀考题例举:例1:把1~999这999个自然数按顺时针的方向依次排列在一个圆圈上如下图;从1开始按顺时针的方向,保留1,擦去2;保留3,擦去4……这样每隔一个数擦去一个数,转圈擦下去;问:最后剩下一个数时,剩下的是哪个数马到成功解析:可通过找规律得出,如果有2n个数,那么转一圈擦去一半,剩下2n-1个数,起始数还是1;再转一圈擦去剩下的一半,又剩下2n-2个数,起始数还是1……转了n圈后,就剩下一个数是1;如果有2n+dd<2n个数,那么当擦去d个数时,剩下2n个数,此时的第一个数是最后将剩下的数;因为擦去的第d个数是2d,所以2d+1就是最后剩下的整数;999=29+487,最后剩下的一个数是487×2+1=975;例2:1000个学生坐成一圈,依次编号为1,2,3,…,1000;现在进行1,2报数:1号学生报1后立即离开,2号学生报2并留下,3号学生报1后立即离开,4号学生报2并留下……学生们依次交替报1或2,凡报1的学生立即离开,报2的学生留下,如此进行下去,直到最后还剩下一个人;问:这个学生的编号是几号分析:这个问题与上面这题非常相似,只不过本例是报1的离开报2的留下,而上题相当于报1的留下报2的离开,由上题的结果可以推出本例的答案;本例中编号为1的学生离开后还剩999人,此时,如果原来报2的全部改报1并留下,原来报1的全部改报2并离开,那么,问题就与上面这题完全一样了;因为剩下999人时,第1人是2号,所以最后剩下的人的号码应比上题大1,是975+1=976号;为了加深理解,我们重新解这道题;解:如果有2n个人,那么报完第1圈后,剩下的是2的倍数号;报完第2圈后,剩下的是22的倍数号……报完第n圈后,剩下的是2n的倍数号,此时,只剩下一人,是2n号;如果有2n+d1≤d<2n人,那么当有d人退出圈子后还剩下2n人;因为下一个该退出去的是2d+1号,所以此时的第2d+1号相当于2n人时的第1号,而2d号相当于2n人时的第2n 号,所以最后剩下的是第2d号;由1000=29+488知,最后剩下的学生的编号是488×2=976号;例3:有100张的一摞卡片,玲玲拿着它们,从最上面的一张开始按如下的顺序进行操作:把最上面的第一张卡片舍去,把下一张卡片放在这一摞卡片的最下面;再把原来的第三张卡片舍去,把下一张卡片放在最下面;反复这样做,直到手中只剩下一张卡片,那么剩下的这张卡片是原来那一摞卡片的第几张分析与解:这100张卡片如果用线串起来,其实还是一个围成一圈的约瑟夫问题;如果上面几题的解法看不太懂,可学学这题,从最简单的情况开始找规律;下面从简单的不失题目性质的问题入手,寻找规律;列表如下:设这一摞卡片的张数为N,观察上表可知:1当N=2aa=0,1,2,3,…时,剩下的这张卡片是原来那一摞卡片的最后一张,即第2a张;2当N=2a+mm<2a时,剩下的这张卡片是原来那一摞卡片的第2m张;取N=100,因为100=26+36,2×36=72,所以剩下这张卡片是原来那一摞卡片的第72张;总结上题及例1例2:可归纳为两种情况:1、留1,杀2类:剩下号=总数-小于总数最大的2的次方数×2+12、杀1,留2类:剩下号=总数-小于总数最大的2的次方数×2记住留1要加1,杀1不用加1,总发现有学生在这点上分辨不清;因此可对照:例1:为“留1”类,可用:999-512×2+1=975例2:为“杀1”类,可用1000-512×2=976例3:为“杀1”类,可用100-64×2=72上面的512,64都是小于总数的最大的2的次方数;再看一道经变化的逆推题:例4:如下左图,七枚棋子围成一个圆圈,从①开始,每隔一个取一个,依次取走①、③、⑤、⑦、④、②,最后剩下⑥.二十枚棋子围成一个圆圈如右图,从开始,每隔一个取一个,最后将只剩下一枚棋子是⑥.实际上例就是抽杀问题的“杀1留2类”,右图可假设先从1开始取起,那根据规律留下的为:20-16×2=8号,想留下6号得逆时针倒推2枚棋子;则最后结果为19号开始; 试试我们玩的扑克牌:例5:有两副扑克牌,每副牌的排列顺序均按头两张是大王、小王,然后是黑桃、红桃、方块、梅花四种花色排列;每种花色的牌又按1,2,3,…,J,Q,K 顺序排列;某人把按上述排列的两副扑克牌上下叠放在一起,然后把第一张丢掉,把第二张放在最底层,再把第三张丢掉,把第四张放在最底层,…….如此进行下去,直至最后只剩下一张牌;试问所剩的这张牌是哪一张解:注意到:如果手中只有64张牌,按这样规则丢牌,那么后剩下的应该是第64张牌;现在手中有108张牌,多出108-64=44张,我们只需按此规定丢掉44张后,把88张牌放在手中牌的最底层时,这时手中牌恰为64张;这样,再丢下去,最后留下的就是原牌顺序的第88张,接下来的难点就涉及周期问题了,是哪张牌呢先去掉一副,再去掉黑桃、红桃各十三张,即为88-54-2×26=6;按照花色排列应为方块6;来个再难点的三个数一组的题:例6:连续自然数1,2,3,…,8899排成一列;从1开始,留1划掉2和3,留4划掉5和6……这么转圈划下去,最后留下的是哪个数可仿例1与例2;这道题留1划2和3,每次留下三分之一,显然与3的N 次方有关了;当有3n 个数时,留下的数是1号;小于8899的形如3n 的数是38=6561,故从1号开始按规则划数,划了8899-6561=2338个数后,还剩下6561个数;这划去的数中的最后一个2338÷2×3=3507,故最后留下6561个数中的第一个就是3508;这道题也可归纳出一个规律:“留1,杀2,3”型留下的这个数为=总数-小于总数的最大的3的次方数÷2×3+1考一考:连续自然数1,2,3,…,8899排成一列;从1开始,划掉1和2,留下3,划掉4和5留下6……这么转圈划下去,最后留下的是哪个数这道题可定为“杀1,2留3”型,其中的规律与答案就留给你自己去研究了;另外在最前面约瑟夫的介绍中的类型可说成为“留1、2杀3型”你探索一下这道题有什么规律; 最后见识一下隐形抽杀问题:例7:在纸上写着一列自然数1,2,……,99,100;一次操作是指将这列数中最前面的两个数划去,然后把这两个数的和写在数列的最后面,例如一次操作后得到3,4,…,99,100,3;而两次操作后得到5,6,…,99,100,3,7;这样不断进行下去,最后将只剩下一个数;问:最后剩下的数是多少最初的100个数连同后面写下的数,纸上出现的所有数的总和是多少① ③ ② ④ ⑤ ⑥ ⑦ ① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ ⑾ ⑿ ⒀ ⒁ ⒂ ⒃ ⒄ ⒅ ⒆ ⒇马到成功解析:在每次操作过程中,数列中添加的数等于划去的两个数之和,因此数列中所有数的和保持不变,于是当最后只剩下一个数时,它就是原来的100个数之和,为1+2+…+99+100=5050;当数列中有2n个数时,经过n次操作后将被全部划去,同时出现n个新数,并且这n 个新数之和等于原来2n个数的和;这提示我们去考虑数列包含2,2 ×2,2 ×2 ×2,…项的时刻;6个2连乘是64,当经过100-64=36次操作后,原来的数1,2,…,71,36×2=72被划去,划去的数的和是1+2+…+71+72=2628;此时数列中共有64个数,并且这64个数的和与原来100个数的和相等,是5050;从该时刻起,依次再经过32,16,8,4,2,1次操作后,纸上出现的新数的个数依次为32,16,8,4,2,1;根据前面的分析,每一轮出现的所有新数的和都是5050;从数列中有64个数变为只有1个数,操作共进行了6轮;综上所述,纸上写出的所有数之和为2628+5050+5050×6=37978;学会了抽杀问题的思路再来理解这题的设计就比较容易了;。
约瑟夫问题详解(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]。
实验一:约瑟夫斯问题求解一、问题描述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)约瑟夫问题求解流程图3)主函数流程图四、详细设计1. 实现每个操作的伪码,重点语句加注释主程序:void main(){SLNODE *head;int n,m;head=(SLNODE *)malloc(sizeof(SLNODE));printf("/*************************************/\n"); //初始界面 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);}2.创建循环单链表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;}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; //读取新的密码值printf("%4d",p->num);q->next=p->next; //删除p节点free(p);p=q->next;}printf("%4d\n",p->num);}4. 函数调用关系图主函数中调用了struct node *create_sl(int n);void Josephus(SLNODE *head,int n,int m);两个函数五、调试分析1. 设计与调试过程中遇到的问题分析、体会1)创建单链表时,一开始写的程序是void create_sl(SLNODE *head ,int n),并没有没有报错,但最后运行时却不是想象的结果,然后尝试在主函数中写一个printf函数看一下创建表是否创建成功,事实证明并没有,后来改成了struct node *create_sl(int n);解决了这个问题,再次就是建表的时候发现最后一个数并不是我输入的数,然后就是开始改那个循环=函数,发现我虽然是读了7个数,但第7个数并没有赋值给链表,原错误函数:p=head;head->next=head;printf("请输入密码值\n");scanf("%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 *next;}SLNODE;3)剩下的还有一些小问题,比如少打了一个字母,打错一个字母,这些程序会报错,不属于逻辑错误,所以解决起来也比较快,2. 主要算法的时间复杂度分析创建单链表的时间复杂度为O(n);约瑟夫斯问题的时间复杂度与n值有关,也与每个人的密码值有关,时间复杂度O(mn);空间复杂度为O(n);六、使用说明程序运行后显示提示信息,提示用户输入人数n,初始报数值m,n个人的密码,用户按照提示输入完毕后,程序自动输出运算结果。
七、测试结果八、附录#include <stdio.h>#include <malloc.h>#include <stdlib.h>#include <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);head=(SLNODE *)malloc(sizeof(SLNODE));printf("/*************************************/\n"); //初始界面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语言已经有很长一段时间,这次确实遇到了很多障碍,不仅仅是对链表的生疏,也包含以前基础的薄弱环节,通过一个程序的设计,慢慢回忆起程序的编写,加强自己的逻辑性,编程是一个很麻烦的事,一个小错误的出项,程序就不会出现预想的结果,如果报错还可以按照提示找到,若有逻辑错误还得一行一行检查,最后发现如果程序需要解决多个问题,最好还是分模块解决,解决完一个问题再去解决另一个问题,如果一个模块有错误及时发现,也比较好改。