约瑟夫问题求解

合集下载

约瑟夫问题(Joseph)的求解

约瑟夫问题(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);}运行结果:四、实验总结通过此次试验,加深了我对线性表(链式存储)的学习,使我对线性表(链式存储)有了深刻地认识,尤其是循环链表,并学会了用线性链表解决实际应用问题;同时也提高了自己的编程能力,知道了自己在编程方面的不足之处以及常犯的错误,使自己得到了很好的锻炼。

约瑟夫问题与循环链表解决环状数据结构的应用问题

约瑟夫问题与循环链表解决环状数据结构的应用问题

约瑟夫问题与循环链表解决环状数据结构的应用问题约瑟夫问题是一个经典的数学问题,涉及到环状数据结构的应用。

在这个问题中,有n个人围成一圈,从第一个人开始依次报数,每报到第m个人就出列,直到剩下最后一个人。

本文将介绍约瑟夫问题的背景和解决方法,并探讨循环链表在解决环状数据结构问题中的应用。

一、约瑟夫问题的背景约瑟夫问题最早可追溯到两千多年前。

根据传说,犹太历史学家弗拉维奥·约瑟夫斯(Flavius Josephus)当时被罗马军队围困在一个洞穴中,他和其他39个犹太人决定宁愿自杀也不被俘。

他们决定站成一个圆圈,从第一个人开始,数到第三个人就将他杀死,直到最后一个人留下来。

通过解决这个例子中的问题,人们发现了一种递推模式,即每次被杀的人索引号是前一个被杀人索引号加上一个特定数(m)对人数(n)取余的结果。

二、求解约瑟夫问题1. 暴力法最简单的解法是通过一个数组,将每个人标记为存活或死亡。

然后从第一个人开始,按照指定的m值遍历循环,找到下一个存活的人,直到只剩下最后一个人。

这种方法的问题在于时间复杂度较高,特别是当人数和m值较大时,算法运行效率低下。

2. 循环链表循环链表是解决约瑟夫问题的一种有效数据结构。

在循环链表中,每个节点都包含一个指向下一个节点的指针,最后一个节点指向第一个节点,形成一个闭环。

通过使用循环链表,我们可以轻松地实现约瑟夫问题的求解。

首先,我们将n个人作为节点插入到循环链表中,并将最后一个节点指向第一个节点。

然后,我们从第一个人开始依次报数,每报到第m个人,就将该节点从链表中移除。

此时,链表的结构会自动调整,继续报数直到只剩下最后一个人。

这种方法只需要遍历一次链表,因此时间复杂度为O(n)。

相比暴力法,它的运行效率更高。

三、循环链表解决环状数据结构的应用问题除了约瑟夫问题,循环链表还可以应用于其他环状数据结构的解决方案中。

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),对于极限数据会超时。

实验一、约瑟夫问题

实验一、约瑟夫问题

实验一:约瑟夫问题求解一、问题描述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);/*进行约瑟夫操作*/。

约瑟夫问题

约瑟夫问题

4、狐狸捉兔子
题目:
围绕着山顶有10个洞,狐狸要吃兔子,兔子说:“可以,但必须找到我,我就藏身于这十个洞中,你从10号洞出发,先到1号洞找,第二次隔1个洞找,第三次隔2个洞找,以后如此类推,次数不限。”但狐狸从早到晚进进出出了1000次,仍没有找到兔子。问兔子究竟藏在哪个洞里?
参考程序下载
--------------------------------------------------------------------------------
5、进制转换
题目:
将一个十进制自然数转换成二进制数,一般采取除2取余法。从键盘输入一个十进制自然数(约定该数小于等于Maxlongint),输出相应的二进制数
设有一天平,可用来称物体的质量,同时给出一个正整数n(n<=10)。
问题1:试设计n个砝码的质量,用它们能称出尽可能多的1,2,3,...连续整数质量,约定砝码可以放在天平的左右两个托盘中的任何一个或两个中。例如n=2,此时设计2个砝码的质量分别为1,3,则能同时称取1,2,3,4。
问题2:在给出n个砝码能称出最大质量范围内的一个质量x,试给出称取x的方案。如上例中:
6/7,7/8,1/1
编程求出n级法雷序列,每行输出10个分数。n的值从键盘输入。
--------------------------------------------------------------------------------
13、砝码设计
题目:
①把组成这个四位数的4个数字由小到大排列,形成由这四个数字组成的最大的四位数;
②把组成这个四位数的4个数字由大到小排列,形成由这四个数字组成的最小的四位数(如果含有数字0,则不足四位);

信息学竞赛3 - 2约瑟夫问题

信息学竞赛3 - 2约瑟夫问题
信息学竞赛
约瑟夫问题
著名犹太历史学家 Josephus有过以下的故事:
在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他 的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌 人抓到,于是决定了一个自杀方式,41个人排成一个圆圈 ,由第1个人开始报数,每报数到第3人该人就必须自杀, 然后再由下一个重新报数,直到所有人都自杀身亡为止。
Page
6
约瑟夫问题第1种解法
设一个包括m个元素的数组,
初始值将数组的每个元素放1。 从第一个元素开始,依次取数组元素相加,当其和为 n时,输出该元素的下标(它即是应该出圈人的编号 ),然后将该元素清0。使以后相加时不再起作用, 相当于该人已出圈。 再从下一个元素开始,依次取数组元素相加,当其和 为n时,再输出该元素的下标。
设一个包括m个元素的数组,在每个数组元素中存放 与其相连的下一个元素的编号。 当某人出圈时,将对应元素的值放入前一元素中,使 得前一元素跳过该元素与再下一元素相连。
Page
9
数组构成的环
A1 2
A2 3
A3 4
A4 5
A5 1
4 3 5
A2
A1
2
Page 10
1
数组构成的环
A1 2
A2 3
Page
4
狼追兔子
一只兔子躲进了n个环形分布的洞的某一个中。
狼在第一个洞没有找到兔子,就隔一个洞,到第三个 洞去找;也没有找到,就隔两个洞,到第六个洞去找 。 以后每次多一个洞去找兔子……,这样下去,如果一 直找不到兔子,请问兔子可能在哪个洞中?
Page
5
猴子选大王
一堆猴子都有编号,编号是1,2,3 ...m,这群猴子 (m个)按照1-m的顺序围坐一圈,从第1开始数,每 数到第N个,该猴子就要离开此圈,这样依次下来, 直到圈中只剩下最后一只猴子,则该猴子为大王。

约瑟夫问题大全

约瑟夫问题大全

“约瑟夫”问题及若干变种林厚从例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),对于极限数据会超时。

约 瑟 夫 环 问 题 的 三 种 解 法

约 瑟 夫 环 问 题 的 三 种 解 法

Java 约瑟夫环问题的两种解法(循环数组,单向环形链表)Josephu 问题为:设编号为1,2,… n的n个人围坐一圈,约定编号为k(1=k=n)的人从1开始报数,数到m 的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。

2.解决方法一:循环数组提示:每次报数,如果满足出圈的条件就将数组元素设置为-1,当下次报数时跳过-1。

直至数组最后一个元素变为-1,循环结束,数组的循环使用取模来完成。

--解决方法二:数组+取模public static void JosephuByArr(int total,int startNum,int m){ int []Arr=new int[total];int leave=total; --剩下的数量int count=0; --报数(0-1-2-3-4-5····)int index=startNum; --第一个元素--初始化数组(为了方便取模,最后一个元素放在数组的第0个,也可以先加一再取模)Arr[0]=total;for(int i=1;iArr.length;i++){Arr[i]=i;while(leave0){count++; --报数--找到报数为count的数组元素if(Arr[index%total]==-1){while(Arr[index%total]==-1){index++;--如果满足条件,输出(元素设置为-1)if(count%m==0){System.out.print(Arr[index%total]+"t");Arr[index%total]=-1;leave--;--下一个元素开始index++;3.解决方法二:单向环形链表提示:用一个不带头结点的循环链表来处理Josephu 问题:先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从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号杀了,总之每隔一个人杀一个人,最后剩下一个人,这个人就是约瑟夫斯。

实验一约瑟夫问题求解实验二停车场问题

实验一约瑟夫问题求解实验二停车场问题

实验一约瑟夫问题求解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个⼈围成⼀圈,第⼀个⼈从1开始报数,报到k的⼈将被杀掉,接着下⼀个⼈⼜从1开始报,直到最后剩下⼀个,求最后留下的⼈的下标。

题⽬集合解法1:暴⼒可以直接暴⼒求解,时间复杂度为O(nk)解法2:递推设f(n,k)为当n个⼈围成⼀圈时,最后留下的⼈的下标。

对于f(n-1,k)来说,其结果相当于f(n,k)的结果向前移动k\%(n-1)位。

因为对于f(n,k)来说,去掉第⼀轮报的数(k\%n)后,现在就只剩下n-1个数,并且是以(k\%(n-1)+1)作为第⼀个数,即所有数向前移动k\%(n-1)位。

现在的结果就为f(n-1,k)对于f(5,3)来说,其结果为4。

当其去掉第⼀轮报的数后,其向前移动了(3\%4)位,以4为起始,f(4,3)结果为1,对应着f(5,3)的结果4向前移动了3位所以反过来看即为,即为f(n-1,k)的结果向后移动k\%(n-1)位即f(n+1,k)=(f(n,k)+k\%n)\%n (x下标从0开始,因为取模结果为[0,n-1])时间复杂度为O(n)ll josephus2(ll n,ll k){ll pos=0;for(int len=1;len<=n;len++){pos = (pos+k)%len;}return pos+1;}递推代码解法3:如果当前这⼀位⼈没被杀掉,则他可以放在幸存者的末尾,直到幸存者数量为1所以对于下标为i的⼈,如果在他前⾯已经被杀掉了q个⼈,那么他的新的下标为n+q(k-1)+x,(1\leq x <k)如下图所⽰,最后被淘汰的编号⼀定是n*k,所以幸存者最后的编号是n*k我们现在需要从幸存者最后的编号中恢复出最初编号假设幸存者这⼀次的编号为p os_{i},在他后⾯包括他还有x位幸存者,则[pos_{i-1},pos_{i})间⼀定有x个不能被k整除的数这样才能使在他后⾯包括他还有x位幸存者。

约瑟夫问题多种解决方法

约瑟夫问题多种解决方法
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)。

约 瑟 夫 环 问 题 的 三 种 解 法

约 瑟 夫 环 问 题 的 三 种 解 法

约瑟夫问题(数学解法及数组模拟)约瑟夫问题(有时也称为约瑟夫斯置换,是一个出现在计算机科学和数学中的问题。

在计算机编程的算法中,类似问题又称为约瑟夫环。

又称“丢手绢问题”.)据说著名犹太历史学家 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就能得到原来的编号。

约瑟夫环问题(最简单的数学解法)

约瑟夫环问题(最简单的数学解法)

约瑟夫环问题(最简单的数学解法)基本问题描述:已知n个⼈(以编号1,2,3...n分别表⽰)围坐在⼀张圆桌周围。

从编号为1的⼈开始报数,数到m的那个⼈出列;他的下⼀个⼈⼜从1开始报数,数到m的那个⼈⼜出列;依此规律重复下去,直到圆桌周围的⼈全部出列。

(也类似于变态杀⼈狂问题)通常解决这类问题时我们把编号从0~n-1,最后结果+1即为原问题的解。

通常,我们会要求输出最后⼀位出列的⼈的序号。

那么这⾥主要研究的是最后⼀个出列的⼈的序号要怎么确定。

当n,m数据量很⼩的时候,我们可以⽤循环链表模拟约瑟夫环的过程。

当模拟到⼈数等于1的时候,输出剩下的⼈的序号即可。

这种⽅法往往实现起来⽐较简单,⽽且也很容易理解。

但是时间复杂度却是很糟糕的,达到了O(n m),这样的话,其实在n,m⽐较⼤的时候(n m达到10^8或者更⼤),那么要得出结果往往需要耗费很长的时间,但是我们可以运⽤⼀点数学上的技巧,将最后结果推导出来。

为了简化出列的过程:⾸先我们把这n个⼈的序号编号从0~n-1(理由很简单,由于m是可能⼤于n的,⽽当m⼤于等于n时,那么第⼀个出列的⼈编号是m%n,⽽m%n是可能等于0的,这样编号的话能够简化后续出列的过程),当数到m-1的那个⼈出列,因此我们编号完成之后,开始分析出列的过程:第⼀次出列:⼀开始的时候,所有⼈的编号排成序列的模式即为:0,1,2,3,4,5...n-2,n-1那么第⼀次出列的⼈的编号则是(m-1)%n1,那么在第⼀个⼈出列之后,从他的下⼀个⼈⼜开始从0开始报数,为了⽅便我们设k1 =m%n1(n1为当前序列的总⼈数)那么在第⼀个⼈出列之后,k1则是下⼀次新的编号序列的⾸位元素,那么我们得到的新的编号序列为:k1,k1+1,k1+2,k1+3...n-2,n-1,0,1,2...k1-3,k1-2 (k1-1第⼀次已出列)那么在这个新的序列中,第⼀个⼈依旧是从0开始报数,那么在这个新的序列中,每个⼈报的相应数字为:0,1,2,3....n-2那么第⼆次每个⼈报的相应数字与第⼀次时⾃⼰相应的编号对应起来的关系则为:0 --> k11 --> k1+12 --> k1+2...n-2 ---> (k1+n-2)%n1(n1为当前序列的总⼈数,因为是循环的序列,k1+n-1可能⼤于总⼈数)那么这时我们要解决的问题就是n-1个⼈的报数问题(即n-1阶约瑟夫环的问题)可能以上过程你还是觉得不太清晰,那么我们重复以上过程,继续推导剩余的n-1个⼈的约瑟夫环的问题:那么在这剩下的n-1个⼈中,我们也可以为了⽅便,将这n-1个⼈编号为:0,1,2,3,4...n-2那么此时出列的⼈的编号则是(m-1) % n2(n2为当前序列的总⼈数),同样的我们设k2 = m % n2,那么在这个⼈出列了以后,序列重排,重排后新的编号序列为:k2,k2+1,k2+2,k2+3...n-2,n-1,0,1,2...k2-3,k2-2 (k2-1第⼀次已出列)那么在这个新的序列中,第⼀个⼈依旧是从1开始报数,那么在这个新的序列中,每个⼈报的相应数字为:1,2,3,4....n-2那么这样的话是不是⼜把问题转化成了n-2阶约瑟夫环的问题呢?后⾯的过程与前两次的过程⼀模⼀样,那么递归处理下去,直到最后只剩下⼀个⼈的时候,便可以直接得出结果当我们得到⼀个⼈的时候(即⼀阶约瑟夫环问题)的结果,那么我们是否能通过⼀阶约瑟夫环问题的结果,推导出⼆阶约瑟夫环的结果呢?借助上⾯的分析过程,我们知道,当在解决n阶约瑟夫环问题时,序号为k1的⼈出列后,剩下的n-1个⼈⼜重新组成了⼀个n-1阶的约瑟夫环,那么假如得到了这个n-1阶约瑟夫环问题的结果为ans(即最后⼀个出列的⼈编号为ans),那么我们通过上述分析过程,可以知道,n阶约瑟夫环的结果(ans + k)%n(n为当前序列的总⼈数),⽽k = m%n则有:n阶约瑟夫环的结果(ans + m % n)%n,那么我们还可以将该式进⾏⼀下简单的化简:当m<n时,易得上式可化简为:(ans + m)% n⽽当m>=n时,那么上式则化简为:(ans % n + m%n%n)% n即为:(ans % n + m%n)% n⽽(ans + m)% n = (ans % n + m%n)% n因此得证(ans + m % n)%n = (ans + m)% n这样的话,我们就得到了递推公式,由于编号是从0开始的,那么我们可以令f[1] = 0; //当⼀个⼈的时候,出队⼈员编号为0f[n] = (f[n-1] + m)%n //m表⽰每次数到该数的⼈出列,n表⽰当前序列的总⼈数⽽我们只需要得到第n次出列的结果即可,那么不需要另外声明数组保存数据,只需要直接⼀个for循环求得n阶约瑟夫环问题的结果即可由于往往现实⽣活中编号是从1-n,那么我们把最后的结果加1即可。

约瑟夫环问题的两种解法(循环链表和公式法)

约瑟夫环问题的两种解法(循环链表和公式法)

约瑟夫环问题的两种解法(循环链表和公式法)问题描述这⾥是数据结构课堂上的描述:N people form a circle, eliminate a person every k people, who is the final survior?Label each person with 0, 1, 2, ..., n - 1, denote(表⽰,指代) J(n, k) the labels of surviors when there are n people.(J(n, k)表⽰了当有 n 个⼈时幸存者的标号)First eliminate the person labeled k - 1, relabel the rest, starting with 0 for the one originally labeled k.0 1 2 3 ... k-2 k-1 k k+1 ... n-1... k-2 0 1 ...Dynamic programmingJ(n, k) = J(J(n - 1, k) + k) % n, if n > 1,J(1, k) = 0⽤中⽂的⽅式简单翻译⼀下就是 (吐槽:为啥课上不直接⽤中⽂呢?淦!) 有 n 个⼈围成⼀圈,从第⼀个⼈开始,从 1 开始报数,报 k 的⼈就将被杀死,然后从下⼀个⼈开始重新从 1 开始报数,往后还是报 k 的⼈被杀掉,杀到最后只剩⼀个⼈时,其⼈就为幸存者。

(上⾯的英⽂是从 0 开始的,是因为我们写程序时使⽤了数组,所以下标从 0 开始)解决⽅案循环链表⽅法算法思路很简单,我们这⾥使⽤了循环链表模拟了这个过程:节点 1 指向节点 2,节点 2 指向节点 3,...,然后节点 N 再指向节点 1,这样就形成了⼀个圆环。

如图所⽰,n 取 12,k 取 3,从 1 开始报数,然后依次删除 3, 6, 9, 12:#include<stdio.h>#include<stdlib.h>typedef struct Node // 节点存放⼀个数据和指向下⼀个节点的指针{int data;struct Node *next;} *NList; // NList为指向 Node 节点的指针// 创建⼀个节点数为 n 的循环链表NList createList(int n){// 先创建⼀个节点NList p, tmp, head;p = (NList)malloc(sizeof(struct Node));head = p; // 保存头节点p->data = 1; // 第⼀个节点for (int i = 2; i <=n ; i++){tmp = (NList)malloc(sizeof(struct Node));tmp->data = i;p->next = tmp;p = tmp;}p->next = head; // 最后⼀个节点指回开头return head;}// 从编号为 1 的⼈开始报数,报到 k 的⼈出列,被杀掉void processList(NList head, int k){if (!head) return;NList p = head;NList tmp;while (p->next != p){for (int i = 0; i < k - 1; i++){tmp = p;p = p->next;}printf("%d 号被杀死\n", p->data);tmp->next = p->next;free(p);p = NULL; // 防⽌产⽣野指针,下同p = tmp->next;}printf("幸存者为 %d 号", p->data);free(p);p = NULL;}int main(){NList head = createList(11);processList(head, 3);return 0;}测试结果:易知,这个算法的时间复杂度为O(nk),显然,这不是⼀个好的算法。

数据结构实验报告—约瑟夫问题求解

数据结构实验报告—约瑟夫问题求解

数据结构实验报告—约瑟夫问题求解《计算机软件技术基础》实验报告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值,输出出列序列。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

约瑟夫问题求解
一、问题描述。

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、本程序实现求解约瑟夫问题,
2、程序运行后,要求用户指定初始报数上限值,然后读取个人密码。

输入数据:建立输入处理输入数据,输入m的初值,n,输入每个人的密码,建立单循环链表。

输出形式:建立一个输出函数,将正确的序列输出。

三、概要设计
定义的抽象数据类型:
栈的抽象数据类型定义:
ADT Stack{
数据对象:D={ai|ai∈ElemSet,i=1,2,...,n, n≥0,ElemSet为元素集合}
数据关系:R={<ai-1,ai>|ai-1,ai ∈D,i=1,2,...,n}
基本操作:
InitStack(&S); //构造空栈
DestroyStack(&S); //销毁栈
ClearStack(&S); //将栈置空
StackEmpty(S); //检查栈是否为空
StackLength(S); //返回栈中元素个数
GetTop(S,&e); //返回栈顶元素赋予e
Push(S,e); //插入e为新的栈顶元素
Pop(S,&e); //删除栈顶元素并赋予e StackTravers(S); //依次输出栈中元素。

}
四、详细设计
定义了以下结构体:
typedef struct LNode
{
int data;
struct LNode *next;
}LNode;
建立链表:
head=new LNode;
p=head;
for(i=1;i<n;i++) /*创建链表*/ {
q=new LNode;
p->next=q;
p=q;
}
tail=q;
tail->next=head;
p=head;
主函数如下:
void main( )
{
int i,j,m,n,usless;LNode *head,*tail,*p,*q; printf("\nPlease input n:");
scanf("%d",&n);
printf("\nPlease input m:");
scanf("%d",&m);
head=new LNode;
p=head;
for(i=1;i<n;i++)
{
q=new LNode;
p->next=q;
p=q;
}
tail=q;
tail->next=head;
p=head;
for(i=1;i<=n;i++)
{
p->data=i;
/*printf("\nPlease enter No.%d's data:",i);
scanf("%d",&p->data);*/
p=p->next;
}
printf("\nResult is as follow:\n");
p=tail;
for(i=1;i<=n;i++)
{ for(j=1;j<m;j++)
p=p->next;
q=p->next;
printf("%d ",q->data);
p->next=q->next;
j=1;
}
scanf("%d",&usless);
}
五调试分析
出现的问题:
刚开始调试时,输出出现问题,最后一步DOS窗口消失,后在助教帮助下找出错误,解决了问题。

六、使用说明
输入数据:m=20,n=7,7 个人的密码依次为:3,1,7,2,4,8,4
输出数据:6,1,4,7,2,3,5。

七、调试结果
输入:
m=20,n=7, 3,1,7,2,4,8,4
输出:6,1,4,7,2,3,5。

八:附录
约瑟夫chen.cpp。

相关文档
最新文档