项目三 解决约瑟夫环问题PPT课件
约 瑟 夫 环 问 题 的 三 种 解 法

约瑟夫环问题python解法约瑟夫环问题:已知n个人(以编号1,2,3.n分别表示)围坐在一张圆桌周围。
从编号为k的人开始报数,数到k的那个人被杀掉;他的下一个人又从1开始报数,数到k的那个人又被杀掉;依此规律重复下去,直到圆桌周围的人只剩最后一个。
思路是:当k是1的时候,存活的是最后一个人,当k=2的时候,构造一个n个元素的循环链表,然后依次杀掉第k个人,留下的最后一个是可以存活的人。
代码如下:class Node():def __init__(self,value,next=None):self.value=valueself.next=nextdef createLink(n):return Falseif n==1:return Node(1)root=Node(1)tmp=rootfor i in range(2,n+1):tmp.next=Node(i)tmp=tmp.nexttmp.next=rootreturn rootdef showLink(root):tmp=rootwhile True:print(tmp.value)tmp=tmp.nextif tmp==None or tmp==root: def josephus(n,k):if k==1:print('survive:',n)root=createLink(n)tmp=rootwhile True:for i in range(k-2):tmp=tmp.nextprint('kill:',tmp.next.value) tmp.next=tmp.next.nexttmp=tmp.nextif tmp.next==tmp:print('survive:',tmp.value)if __name__=='__main__':print('-----------------')josephus(10,2)print('-----------------')josephus(10,1)print('-----------------')输出结果如下:-------------------------------------分界线-----------------------------------------感谢大家建议,第一种方法是直观暴力裸搞,确实不太简洁,下面写出我的第二种方法,求模来搞起,代码少了一些,如下:def josephus(n,k):if k==1:print('survive:',n)people=list(range(1,n+1))while True:if len(people)==1:p=(p+(k-1))%len(people)print('kill:',people[p])del people[p]print('survive:',people[0])if __name__=='__main__':josephus(10,2)josephus(10,1)运行结果和上面一样。
经典问题约瑟夫环讲解

1.需求分析以无歧义的陈述说明程序设计的任务,强调的是程序要做什么?并明确规定:(1) 输入的形式和输入值的范围;输入的值为数字(2) 输出的形式;输出的形式为一串数字如:6,1,4,7,2,3,5(3) 程序所能达到的功能;编号为1,2,…,n的n个人按顺时针方向围坐一圈,每人持有一个密码(正整数)。
一开始任选一个正整数作为报数上限值m,从第一个人开始按顺时针方向自1开始顺序报数,报到m时停止报数。
报m的人出列,将他的密码作为新的m值,从他在顺时针方向上的下一个人开始重新从1报数,如此下去,直至所有人全部出列为止。
(4) 测试数据:包括正确的输入及其输出结果和含有错误的输入及其输出结果。
输入错误时一般不会显示结果。
总体归纳为:1.约瑟夫环(Joseph)问题的一种描述是:编号为1,2……,n的n个人按顺时针方向围坐一圈,每人持有一个密码(正整数)。
一开始任选一个正整数作为报数上限值m,从第一个人开始按顺时针方向自1开始顺序报数,报到m时停止报数。
报m的人出列,将他的密码作为新的m值,从他在顺时针方向上的下一个人开始重新从1报数,如此下去,直至所有人全部出列为止。
2.演示程序以用户和计算机的对话方式执行,即在计算机终端上显示“提示信息”之后,有用户在键盘上输入演示程序中规定的运算命令,相应的输入数据和运算结果显示在其后。
3.程序执行的命令包括:1)输入初始密码和人数 2)输入所有人的密码 3)显示输入的所有人的编号及相应的密码4)输出出列密码及编号 5)结束4.测试数据(1)m=20, n=7, 7个人的密码依次为3,1,7,2,4,8,4(2)m=7,n=20,20个人的密码依次为3,1,7,2,4,8,4(2)组数据为错误数据测程序的健壮性。
2.概要设计说明本程序中用到的所有抽象数据类型的定义、主程序的流程以及各程序模块之间的层次(调用)关系。
ADT LinearList{数据元素:D={ai| ai∈D0, i=1,2,…,n n≥0 ,D0为某一数据对象}关系:S={<ai,ai+1> | ai, ai+1∈D0,i=1,2, …,n-1}该程序只有主程序。
约瑟夫环

约瑟夫环问题
主讲人:nuanran
Flavius Josephus
弗拉维奥·约瑟夫(37-100)是第一世纪时的 著名的犹太历史学家,也是军官及辩论家。 《犹太古史》(The Antiquities of the Jews):记录了由圣经创世记至公元66年的 犹太人历史,以旧约圣经为蓝图以及古人的 传说,编写而成的犹太巨著。由于当时的犹 太人散居各地,此书成为各地土生犹太人重 要学习典籍,亦为当代神学学者及历史学者 所采用。
约瑟夫环问题三
不要妄想再找到公式了,模拟是唯一的选择, 但是直接模拟的话,该算法的复杂度将达到 O(n^2). 事实上,我们可以用线段数对此做一个优化, 用线段数来统计每个区间上还剩下人的个数, 从而使算法的复杂度降低到O(n*logn)。 线段数??一棵平衡二叉树,它的每个节点 都是一个线段,这里就不做详细介绍了。
约瑟夫环问题三
问题描述:编号从1到n的n个人,站成一个环,每个 人手里拿着一个卡片,卡片上写着一个非零的数,首 先去掉编号为k的人,然后看他手里的卡片上的数字 m[k],如果m[k]>0,则去掉他左手边的第m[k]个人, 如果m[k]<0,则去掉他右手边的第m[k]个人。重复上 述步骤,直至只剩下一个人,问这个人的编号是多少。
约瑟夫环问题二
为了方便,在这里我们把这n个人的编号改为从0到n-1, 第一个去掉的人总是m%n-1,剩下n-1个人,这n-1个人 又组成了一个从第m%n个人开始的新的约瑟夫环问题。 m%n 0 m%n+1 1 … … n-1 n-m%n-1 0 n-m%n … … m%n-2 n-2
J(1,m)=0; J(n,m)=(m%n+J(n-1,m))%n, n>=2. 最后的结果加1就OK了。 这个问题可以用O(n)的算法去解决。
约瑟夫问题多种解决方法

• • • • • • • • • • •
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个人被扔下海为止
细究“约瑟夫环”

细究“约瑟夫环”0 引言17世纪的法国数学家加斯帕在《数目的游戏问题》中讲了这样一个故事:15个教徒和15 个非教徒在深海上遇险,必须将一半的人投入海中,其余的人才能幸免于难,于是想了一个办法:30个人围成一圆圈,从第一个人开始依次报数,每数到第九个人就将他扔入大海,如此循环进行直到仅余15个人为止。
问怎样排法,才能使每次投入大海的都是非教徒。
1 问题描述有n个人围成一圈,顺序排号。
从第一个人开始报数(从1到3报数),凡报到3的人退出圈子,问最后留下的是原来第几号的那位。
示例一:输入:n = 3输出:“2”解释:首先轮流报数,3就被退出了,之后1,2,1,1就被退出了,最后只剩下了2。
2 算法描述解题思路:首先因为考虑到是不断的有规律退出数字则首先要考虑到循环的使用,我们从索引上看,如果将每次循环的三人看成一组,则被退出的人的索引为2,此时我们就知道了我们删去的就应该是索引为2的人。
但我们此时又想到该如何让其满足我们想得到的方式呢?我们不妨将其横排排列,123删去3后变成1212,此时我们发现可以将之前还未删去的数值排列在其之后,我们可以多举几个例子如1234变成12412。
那么此时我们就解决了第一个如何将其围成圈的问题,而之后就到了最关键的时候了,如何删去这些值?我们又举123为例,若想得到1,我们可以有很多的做法,而取余则是一种很巧的运算方式,如:“1”的位置是1,所以0%3(3是这三个值的长度(1,2,3))得到0,而1 的索引就是0,同理我们可得其他的值。
根据规律可得,若k=2的值为删去的数,那么我们只需进行k = k+2得到下一个删去的值。
(简单讲就是本事索引除以长度得到自身位置,本身长度加1除以长度得到下一个位置,同理加2)3 实验结果与讨论通过编程最终求出了约瑟夫环的问题。
附件代码清单用python求出杨辉三角数4 结语约瑟夫环是一个很经典的数学问题,其中的解法多种多样,通过这种复杂的循环可以使我们很轻松的解决一些问题。
抽杀问题-约瑟夫问题课件(16张PPT)

隔一个取一个,最后将只剩下一枚棋子是⑥.
⒆⒇ ①②
⒅
③
⒄
④
⒃
⑤
⒂
⑥
⒁
⒀ ⑿⑾ ⑩
⑦
⑧ ⑨
2×(20-24)=8(号)
故事引入
提出问题
推导公式
变式拓展
【变式2】
连续自然数1,2,3,…,8899围成一圈。从1开始, 留1划掉2和3,留4划掉5和6……这么转圈划下去,最 后留下的是哪个数?
1 8
1 2
71
2
12
13
12
21
6
1
4
5
故事引入
提出问题
推导公式
变式拓展
16 15
14
1 2 3
4
13
12
11 10 9
5
6 7 8
故事引入
提出问题
推导公式
变式拓展
1.都是最后一位同学被剩下; 2.每一轮个数都会减少一半; 3.8号,16号在游戏的每一轮都报2.
2n位同学做游戏,会剩下编号最的那位同学.
律,可以得到最后留下的编号的
是37。
2 38
37 1
36 2
35
34
23 4 5
……
故事引入
提出问题
推导公式
变式拓展
若x位同学做游戏,最后留下来的同学编号为y. 设2n<x<2n+1
y=2(x-2n) ,去1留2 y=2(x-2n)+1,留1去2
故事引入
提出问题
推导公式
变式拓展
【变式1】
如下图,二十枚棋子围成一个圆圈,从 开始,每
人”职责,抓好党建工作和经济社会协 调发展 。 XX年年底,我当选村支部书记,由于是 新当选 支部书 记,初担 大任,在 班子执 行力和 群 众信服力方面存在较大问题。一年来,在各级 领导、 指导和 帮助下,我团结 村“两 委” 工作人员,紧密依靠全村党员和群众,以 践行党 的群众 路线教 育实践 活动为 契机,知
约瑟夫环(顺序表)

test:下标值。本次报数完之后,下一个 值为本单元中存放的值,即下一个 test =
P[test]
#include <stdio.h>// main(){
int i, p[17],test, head; // 设置循环控制变量、数组及两个数组下标指针 for(i=0;i<16;i++
p[i]=i+1; //存放下一个单元的下标值(位置号) p[16]=0; test=0; //起始位置(从哪开始报数) while(test!=p[test]){ // 位置号和该位置的下一位置相同时退出
(位置号) i + 1,即 P[ i ] = i + 1;到最后一个人的时候就循 环到第一个人,设置P[16] =0。
3
P[i] 1 2 3 4 5 6 7 8
0
i
01 2 34 5 6 7
16
head:下标值。由于报到3的倍数的 人要退出,为了保持循环顺序表的形 式,要改变退出的人的前一个的值, 例如:要改变P[1]的值使之变为3。 head用来标识报数的前一个人的下标 值,当报到3的倍数的人退出时,改 变前一个人的单元值为报到3的倍数 的人的单元值,使之保持循环顺序表 的形式。如此下去,当只有一个人的 时候test=p[test]。
指针指向的下一位置
test=p[head]; //记住出列位置
} printf(″\n%5d″,test); // 最后一个出列位置
}
❖ 例:设计一个程序求约瑟夫环的出列顺序。约瑟夫 (Joseph)问题:有17个人按顺时针方向围坐一周 (编号为0~16),从第0号的人开始从1报数,凡报到 3的倍数的人离开圈子,一直数下去,直到最后只剩下 一个人为止,问此人原来的位置是多少号?
模板约瑟夫环(Joseph)问题.ppt

最新 文档
10
4.详细设计
main()函数
Joseph()函数
从循环链表中按初始密码 依次找出对应出列序列
输出每个人持有的密码c
所有密码c输出后,删除相应 的节点,并释放所占的存储
空间
图5 输出序列的实现
最新 文档
11
5.测试报告
//尾插入法创建链表
void CreateLinkList(LinkList *&L,int n)
最新 文档
3
2.问题描述
编号是1,2,……,n的n个人按照顺时针方向围 坐一圈,每个人只有一个密码(正整数)。一 开始任选一个正整数作为报数上限值m,从第一 个人开始顺时针方向自1开始顺序报数,报到m 时停止报数。报m的人出列,将他的密码作为 新的m值,从他在顺时针方向的下一个人开始 重新从1报数,如此下去,直到所有人全部出 列为止。设计一个程序来求出出列顺序。
int i = 1;
c = L;
printf("输出出对序列:");
while (n)
{
while (i != m)
{
s = c;
c = c->next;
i++;
}
printf("%-3d",c->data);
m = c->cipher;
s->next = c->next;
free(c);
c = s->next;
8 这就是第三步的位置, 这时他的密码作为新的 m值,即m=9,同时得 到的第二个密码为9;9 号出去向下走9,到这 儿;继续走就行了(这 儿剩余的就是:1,2,
3,5,6,7,8,9)
约 瑟 夫 环 问 题 的 三 种 解 法

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开始计数,直到最后一个结点从链表中删除算法结束。
约 瑟 夫 环 问 题 的 三 种 解 法

约瑟夫环问题的简单解法(数学公式法)关于约瑟夫环问题,无论是用链表实现还是用数组实现都有一个共同点:要模拟整个游戏过程,不仅程序写起来比较烦,而且时间复杂度高达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-2 – n-2k-1 – n-1解x’ —- 解为x注意x’就是最终的解变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解:例如x是最终的胜利者,那么根据上面这个表把这个x变回去不刚好就是n个人情况的解吗?!!变回去的公式很简单,相信大家都可以推出来:x’=(x+k)%n如何知道(n-1)个人报数的问题的解?对,只要知道(n-2)个人的解就行了。
(n-2)个人的解呢?当然是先求(n-3)的情况—- 这显然就是一个倒推问题!下面举例说明:假设现在是6个人(编号从0到5)报数,报到(2-1)的退出,即 m=2。
那么第一次编号为1的人退出圈子,从他之后的人开始算起,序列变为2,3,4,5,0,即问题变成了这5个人报数的问题,将序号做一下转换:现在假设x为0,1,2,3,4的解,x’设为那么原问题的解(这里注意,2,3,4,5,0的解就是0,1,2,3,4,5的解,因为1出去了,结果还是一个),根据观察发现,x与x’关系为x’=(x+m)%n,因此只要求出x,就可以求x’。
约瑟夫环问题详解

约瑟夫环问题详解很久以前,有个叫Josephus的⽼头脑袋被门挤了,提出了这样⼀个奇葩的问题:已知n个⼈(以编号1,2,3...n分别表⽰)围坐在⼀张圆桌周围。
从编号为k的⼈开始报数,数到m的那个⼈出列;他的下⼀个⼈⼜从1开始报数,数到m的那个⼈⼜出列;依此规律重复下去,直到圆桌周围的⼈全部出列这就是著名的约瑟夫环问题,这个问题曾经风靡⼀时,今天我们就来探讨⼀下这个问题。
这个算法并不难,都是纯模拟就能实现的。
思路⼀:⽤两个数组,mark[10000]和num[10000],mark这个数组⽤来标记是否出队,num这个⽤来存值,然后每次到循环节点的时候就判断mark是否为0(未出队),为0则输出num[i],并标记mark[i]=1,直到所有的num都出队。
附上C++代码:#include<iostream>#include<cstring>#include<cstdio>#include<cstdlib>#include<cctype>#include<cmath>#include<algorithm>using namespace std;char num[1000];bool mark[1000];int main(){while(true){memset(mark,0,sizeof(mark));int n;int k,m;int i,j;int del=k-1;cin>>n>>k>>m;for(i=0;i<n;++i){cin>>num[i];}int cnt=n;for(i=cnt;i>1;--i){for(int j=1;j<=m;){del=(del+1)%n;if(mark[del]==0)j++;}cout<<num[del]<<" ";mark[del]=1;}for(i=0;i<n;++i){if(mark[i]==0)break;}cout<<endl<<"The final winner is:"<<num[i]<<endl;}return 0;}思路⼆:⽤⼀个数组就可,每次到了循环节点了就将num[i]输出,然后将后⾯的值往前移动⼀位,直到所有的节点出列。
约瑟夫环问题(Josephus)

• b[c]=a[i]; • a[i]=0; • c++; • if(c==n) break; • } • System.out.print(“最后出列的 3人: "); • this.show(b,g); • } • }
• 1.数据选择: 要求:n<2^15; 1<=k<=n; 2.数据和结果显示:
问题描述
• 已知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
• public void SortArray(int[]a,int n,int m,int k,int g){ • int[] b=new int[n]; • int c=0; • int i=k-2; • while(true){ • for(int j=0;j<m;){ • i=(i+1)%n; • if(a[i]!=0){ • j++; • } • }
总人数n 起始号码k 循环数m 最后出列的3人 总人数n 起始号码k 5 2 2 2 1 4 20 6
数据测试
总人数n 起始号码k 循环数m 最后出列的3人 总人数n 起始号码k 循环数m 最后出列的3人
10 2 3 6 1 5 30 4 7 18 23 26
循环数m
最后出列的3人
8
3 14 6
数据测试
最后出列的3人 总人数n
约 瑟 夫 环 问 题 的 三 种 解 法

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

目录1.需求分析说明1.Joseph环的总体功能要求2. 各功能模块的功能描述3. 需要测试的数据2.概要设计说明1. 程序流程图2. 模块调用图3.详细设计说明1. 主函数模块2. 节点数据结构体定义模块3.单向循环链表创建模块4.结点删除模块5.输入子模块4.调试分析5.用户使用说明6.课程设计总结7.测试结果8.参考书目一、需求分析说明1. Joseph环的总体功能要求:Joseph环的总体目标:在Microsoft Visual C++ 6.0 的开发环境下,利用所学C语言和数据结构的相关知识,编写约瑟夫环(Joseph)问题的程序,要求如下:编号是1,2,……,n的n个人按照顺时针方向围坐一圈,每个人只有一个密码(正整数)。
一开始任选一个正整数作为报数上限值m,从第一个仍开始顺时针方向自1开始顺序报数,报到m时停止报数。
报m的人出列,将他的密码作为新的m值,从他在顺时针方向的下一个人开始重新从1报数,如此下去,直到所有人全部出列为止。
设计一个程序来求出出列顺序。
利用单向循环链表存储结构模拟此过程,按照出列的顺序输出各个人的编号。
其中的功能主要分为四项:(1)界面友好,易与操作。
(2)要求使用单向循环链表模拟过程(3)输入报数上限值m和人数上限n,密码值,均限定为正整数,输入的形式为一个以“回车符”为结束标志的正整数。
(4)演示程序以人机对话的形式进行,提供用户从键盘输入,Joseph 约瑟夫环的必要数据,并显示出列顺序2. 各功能模块的功能描述:(1)主函数模块本模块的主要功能是初始化图形界面,调用各模块,实现软件功能。
(2)节点数据结构体定义模块本模块的主要功能是进行数据对象及数据关系的创建(3)单向循环链表创建模块本模块的主要功能是用一个循环链表表示joseph环,结构中有两个成员,其一为指向下一个人的指针,已构成joseph环的链,其二为该人的标记。
(4)结点删除模块本模块的主要功能是将结点的下一结点复制到该结点后后将其释放(5)输入子模块本模块的主要功能是进行光标定位,输出提示文字,并对用户输入数据进行处理。
约瑟夫环问题(最简单的数学解法)

约瑟夫环问题(最简单的数学解法)基本问题描述:已知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即可。
约瑟夫环问讲解

用指针实现
• 定义两个数列:一个表示队列(person),一个存放出列顺序(pout) • 先把数组person赋值(0表示已出列)
• 找出出列顺序 • 用temp表示循环计数 • 用指针p的移动计算出列顺序 • 1.temp在0到m间循环, • 指针p在数组上随着temp的增加而移动 • 当temp等于m时,标记指针所指向的值为0,表示该元素已出列m-• 把已出列的元素移动到数组po数组 • 2.当指针移动到队尾的时候,指针重新指向开头 • 当指针指向已出列的元素(值为0)时,指针向后移动temp不增加 • • 打印po得到出列顺序
{ //指向队尾时 //指针重新回到头
if(p==(pp+n)) p=pp ; else { p=p+1 ; }
temp=temp+1 ;
} else { if(p==(pp+n))
• //如果到达队尾,指针重 • • • • • •
新回到队头 p=pp ; p=p+1; //跳过出列的元素 } } p=p-1 ; //队列长度减一 po[i]=*p ; //生成输出队列顺序 *p=0 ; //标记成已经出队 } }
我们可以把点名看成是不重复的,比如,有N个人,第一 轮后剩N1个人、第二轮剩N2个人……以此类推;那么, 第二轮的地M个人可以看做第N+M次被点名。因此被淘汰 的总是能被3整除的。
如何实现最后一位数完又从第一位开始 数的问题?
很简单,我们可以用模运算(%)来完成。 比如: 1%3=1; 2%3=2; 3%3=0; 4%3=1。 因此我们可以用 j=i%n+1 (j表示人的序号,n表示场上还剩的人数,i表示点名次数) 来实现上述循环
•
• •
约瑟夫环问题的两种解法(循环链表和公式法)

约瑟夫环问题的两种解法(循环链表和公式法)问题描述这⾥是数据结构课堂上的描述: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),显然,这不是⼀个好的算法。
趣味算法--约瑟夫环问题

趣味算法--约瑟夫环问题问题描述已知n个⼈(以编号1,2,3,...,n分别表⽰)围坐在⼀张圆桌上。
指定编号为k的⼈开始从1报数,数到m的那个⼈出列;出列那个⼈的下⼀位⼜从1开始报数,数到m的那个⼈出列;以此规则重复下去,直到圆桌上的⼈全部出列。
分析解决解决⽅法主要有逻辑分析、数学分析法。
逻辑分析:就是按照游戏规则⼀个个报数,报到m的⼈出局,结构层次简单清晰明了。
这种⽅式实现主要采⽤顺序表实现数学分析:采⽤数学⽅式归纳统计分析出每次出局⼈的规律,直接得出每次出局的⼈,然后以代码实现。
这种⽅法需要较强的数学分析能⼒,代码较简单。
这种实现主要采⽤数学公式。
逻辑分析先以C语⾔数组为例,由于C语⾔数组不能像Java、Python这种⾯向对象语⾔的数组那样使⽤删除数组元素的⽅法,每次删除数组中的⼀个元素,都要将后⾯元素前移⼀位,这⾥简单的将删除的元素值设为0。
假设总⼈数n=7,从编号k=2的⼈开始,数到m=3的⼈出局,下⾯是分析过程。
1 #include <stdio.h>23int main(void)4 {5int total; //define total people6int out; //define out number7int start; //define start number89/* set the total, out and start number */10 printf("Please enter the total people\n");11 scanf("%d", &total);12 printf("Please enter the out number\n");13 scanf("%d", &out);14 printf("Please enter the start position\n");15 scanf("%d", &start);1617int people[total]; //init the people18int i = start - 1; //init i for people tag19int count = 0; //init count for the people out20int remain = total; //init the remain number of people2122/* init josephus ring */23int j = 0;24for (j = 0; j < total; j++)25 people[j] = j + 1;2627/* begin to solve josephus problem */28 printf("begin to solve josephus's problem.\n");29/* print josephus ring */30for (j = 0; j < total; j++)31 printf("%d ", people[j]);32 printf("\n");3334while (1)35 {36if(people[i] > 0)37 {38 count++;39 }else40 {41 i++;42if (i == total)43 i = 0;44continue;45 }4647if(count == out)48 {49 printf("The people of %d is out.\n", people[i]);50 people[i] = 0;51 count = 0;52 remain--;53 }5455 i++;56if (i == total)57 i = 0;5859if (remain == 0)60break;6162 }6364 printf("Josephus has solved his problem\n");6566return0;67 }C 数组版运⾏后,结果与上⾯分析⼀致下⾯给出Java的集合对象实现和Python的列表实现。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
模块1 数组的使用
新课引入
▪ 例301:(不使用数组)从键盘输入5个学 生的成绩,输出总分和平均分。
数组的概念
▪ 数组是一个变量,是具有相同数据类型的 一系列数据元素的集合。
▪ 数组元素可以通过数组名和下标来引用。 ▪ 一旦声明数组的大小,就不能再修改。 ▪ 数组中第一个元素的索引号从0开始。
➢ 注意,不可在数组名后的方括号内指定数组元素的个 数,如int sum[10];是错误的。
▪ 二、分配空间:
➢ 声明了数组只是得到了一个存放数组的变量,并没有 为数组元素分配空间,不能够使用,因此要为数组分 配空间,这样数组的每个元素才有一个空间进行存储。
➢ 语法:数组名=new 数据类型[数组长度];
例如:
➢ for (int index = 0; index < args.length; index++) {
➢
double num = Double.parseDouble(args[index]);
➢}
➢ 方法三
直接创建数组,将声明数组、分配空间和赋值合并 完成。
语法:数据类型 数组名[ ]={值1, 值2, 值3, …, 值 n };
➢ 数组的下标范围从0到(数组长度-1),如果超过 这个范围,就会造成数组越界。
▪ 四、一维数组的引用
➢ 访问数组中的元素 ➢ 格式为: 数组名[下标] ➢ 下标可以为整型常量或表达式,下标从0开始。
如: int a[]=new int[10]; int b=a[0]+a[9];
此处,数组下标为从0到9。如果调用了a[10],程 序运行时将提示错误:
声明、创建 和初始 化
声名数组,为其 分配内存并 对元素初始 化
数据类型[] 数组名={值1, 值 2, …, 值n };
数据类型[] 数组名=new 数据 类型[]{值1, 值2, …, 值n };
▪ 定义数组注意事项:
➢ 定义数组时不要漏写数组长度。
➢ 数组中存储的数据称为数组元素。
➢ 不管数组中含有多少个元素,该数组都只有一 个名称,即数组名称。
数组分类
▪ 一维数组 ▪ 多维数组
一维数组
▪ 一、声明:
➢ 数组要先声明然后才能使用。 ➢ 语法:数据类型 数组名[ ];或数据类型[ ] 数组名; ➢ 数据类型既可以是基本数据类型也可以是引用数据类
型,数组名可以是任意的合法变量名。 ➢ 如:
int [ ] score; double height[ ]; String[ ] name;
int [ ] array_name[ ];
▪ 二、为二维数组分配空间
➢ (1)直接为每一维分配空间
例如:int a[][]=new int[3][2];//建立3行 2列数组
➢ (2)高维开始,分别为每一维分配空间
例如:
➢ int a[][]=new int[3][];//指定最高维的长度为3, 然后分别为每一维分配空间
▪ 在Java中二维数组被看作是数组的数组。
▪ 一、声明二维数组
➢ 语法:数据类型 数组名[ ] [ ];或 数组类型[][] 数组名;或 数组类型[] 数组名[];
➢ 例如:
float boy[][];
char array[][]; int array_name[ ][ ]; 或 int [ ][ ] array_name; 或
➢ 如:int sum[ ]=new in值。 ➢ 方法一
语法:数组名[下标值]=值; 例如:
➢ score[0]=89; ➢ score[1]=56; ➢ score[2]=75;
➢ 方法二
每一次赋值时,只有下标在变,因此,可以使用循环给 数组赋值。
➢ a[0]=new int[2];
➢ a[1]=new int[3];
➢ a[2]=new int[4];
▪ 三、二维数组的初始化
➢ (1)直接为数组元素赋值
例如: int a[][]=new int[2][2]; a[0][0]=1; a[0][1]=2; a[1][0]=3; a[1][1]=4;
➢ 其中,数组长度就是能够存放的元素个数,显然应该 是大于0的整数。
➢ 例如:
score=new int [30];
height =new double[10];
name=new String [30];
➢ 也可以在声明数组时就给它分配空间。
➢ 语法:数据类型 数组名[ ]=new 数据类型[数 组长度];
➢ int[ ] score; ➢ score={60,75,64,73,45};//错误!!
创建数组方法小结
方法
说明
语法
仅声明
仅声明数组
使用保留字new
声明和创建
声明,并为 数组元素分
配空间
数据类型[ ] 数组名; 或数据类型 数组名[ ] ;
数据类型[ ] 数组名=new 数组 类型[数组长度];
例如:使用下列方式来创建score数组
➢ int score[]={60,75,64,73,45};//创建一个长度为5的数组 score
➢ 同时它也等价于下面的代码:int score=new int[ ]{60,75,64,73,45};
注意,直接创建并赋值的方式一般在数组元素比较 少的情况下使用。它必须一并完成,如下代码是不 合法的:
➢ (2)在数组声明时为数组初始化
例如:int a[][]={{1},{2,3},{4,5,6}};
▪ 四、二维数组的引用
➢ 格式为:数组名[下标1][ 下标2];
➢ 下标1,下标2分为二维数组的第一、二维下标, 同一维数组一样,也可为整型常量和表达式, 并且数组下标都从0开始。
▪ 例304 创建类,实现打印二维数组的功能。
ng.ArrayIndexOutOfBoundsException
▪ 例302 (用数组编程)从键盘输入5个学生 的成绩,输出总分和平均分。
▪ 例303 求数组中元素的最大值、数组元素 的和,再为各元素从小到大排序。
二维数组
▪ 二维数组在编程中应用广泛。常用于表示 表,表中信息以行和列的形式组织,第一 个下标表示元素所在的行,第二个下标代 表元素所在的列。