数据结构约瑟夫环问题
- 格式:docx
- 大小:12.57 KB
- 文档页数:6
约瑟夫环问题问题描述:有n个⼈,编号分别从0到n-1排列,这n个⼈围成⼀圈,现在从编号为0的⼈开始报数,当报到数字m的⼈,离开圈⼦,然后接着下⼀个⼈从0开始报数,依次类推,问最后只剩下⼀个⼈时,编号是多少?分析:这就是著名的约瑟夫环问题,关于来历不再说明,这⾥直接分析解法。
解法⼀:蛮⼒法。
我曾将在⼤⼀学c语⾔的时候,⽤蛮⼒法实现过,就是采⽤标记变量的⽅法即可。
解法⼀:循环链表法。
从问题的本质⼊⼿,既然是围成⼀个圈,并且要删除节点,显然符合循环链表的数据结构,因此可以采⽤循环链表实现。
解法三:递推法。
这是⼀种创新的解法,采⽤数学建模的⽅法去做。
具体如下:⾸先定义⼀个关于n和m的⽅程f(n,m),表⽰每次在n个编号0,1,...,n-1中每次删除的报数为m后剩下的数字,在这n个数字中,第⼀个被删除的数字是(m-1)%n,为了简单,把(m-1)%n记作k,那么删除k之后剩下的数字为0,1,2,...,k-1,k+1,...,n-1并且下⼀次删除的数字从k+1开始计数,这就相当于剩下的序列中k+1排在最前⾯,进⽽形成k+1,..,n-1,0,1,2,...,k-1这样的序列,这个序列最后剩下的数字应该和原序列相同,由于我们改变了次序,不能简单的记作f(n-1,m),我们可以记作g(n-1,m),那么就会有f(n,m)=g(n-1,m).下⼀步,我们把这n-2个数字的序列k+1,..,n-1,0,1,2,...,k-1做⼀个映射,映射的结果是形成⼀个从0到n-2的序列。
k+1对0,k+2对1,......,n-1对n-k-2,0对n-k-1,1对n-k,....,k-1对n-2这样我们可以把这个映射定义为p,则p(x)=(x-k-1)%n,它表⽰如果映射前的数字是x,映射后为(x-k-1)%n,从⽽这个映射的反映射问为p-1(x)=(x+k+1)%n由于映射之后的序列和原始序列具有相同的形式,都是从0开始的序列,所以可以⽤函数f来表⽰,即为f(n-1,m),根据映射规则有:g(n-1,m)=p-1[f(n-n,m)]=[f(n-1,m)+k+1]%n,最后把之前的k=(m-1)%n带⼊式⼦就会有f(n,m)=g(n-1,m)=[f(n-1,m)+m]%n.这样我们就可以得出⼀个递推公式,当n=1时,f(n,m)=0;当n>1时,f(n,m)=[f(n-1,m)+m]%n;有了这个公式,问题就变得多了。
约瑟夫环知识点总结1. 约瑟夫环的数学模型约瑟夫环可以用数学的方式进行建模和解决。
通常情况下,我们把约瑟夫环的问题理解为一个数学公式的求解。
假设n个士兵分别编号为1、2、3、...、n,m为出列的间隔数。
首先,我们可以得到第一个出列的士兵编号为(m-1)%n+1,例如当n=7,m=3时,第一个出列的士兵为(3-1)%7+1=3。
之后,每次出列后的编号变换规律为:下一个出列士兵的编号为前一个出列士兵编号加上m在n取模后的结果,并且再对n取模,即f(i)=f(i-1)+m)%n。
以上公式是解决约瑟夫环问题的核心,因为根据这个公式可以有效地计算出每一轮出列的士兵的编号。
然后我们只需要循环迭代这个公式,直到最后只有一个士兵为止,这个士兵的编号就是最后的结果。
2. 约瑟夫环的递归解法除了上述的数学模型,还可以使用递归的方法来解决约瑟夫环的问题。
递归是一种非常高效的解决问题的方法,适用于很多数学问题,包括约瑟夫环的计算。
递归方法的求解思路是:先假设已知了n-1个士兵的约瑟夫环问题的解f(n-1, m),那么我们要求的n个士兵的约瑟夫环的解f(n, m)可以通过以下方式推导得到。
首先,第一个出列的士兵编号为(m-1)%n+1,之后剩下的n-1个士兵重新排列成一个圆圈,编号重新从1到n-1。
将这n-1个士兵的解f(n-1, m)映射到n个士兵的解f(n, m)上,此时,再回到上述的数学模型进行计算,找到最终的结果。
递归的思路虽然清晰,但是在实际求解的过程中,由于递归的不断嵌套,计算量会非常庞大,不适合解决大规模的约瑟夫环问题。
3. 约瑟夫环的迭代解法在解决实际问题的时候,我们更多地使用迭代的方法来求解约瑟夫环的问题。
迭代的思路是从最简单的情况开始,然后不断迭代得到更加复杂的情况的解。
对于约瑟夫环问题,迭代的思路是逐步得出每一轮出列的士兵的编号并记录下来,直到剩下最后一个士兵为止。
通常情况下,我们会使用一个数组或者链表来保存每一轮出列的士兵的编号,最后得出最后一个士兵的编号。
约瑟夫环数据结构实验报告约瑟夫环数据结构实验报告引言约瑟夫环是一种经典的数学问题,它涉及到一个有趣的数据结构。
本次实验旨在通过实现约瑟夫环数据结构,深入理解该问题,并探索其在实际应用中的潜力。
本报告将介绍实验的设计和实现过程,并分析实验结果。
实验设计在本次实验中,我们选择使用链表来实现约瑟夫环数据结构。
链表是一种非常灵活的数据结构,适合用于解决约瑟夫环问题。
我们设计了一个Josephus类,其中包含了创建环、添加元素、删除元素等操作。
实验实现1. 创建环在Josephus类中,我们首先需要创建一个循环链表。
我们使用一个头节点来表示环的起始位置。
在创建环的过程中,我们可以选择指定环的长度和起始位置。
2. 添加元素在创建环之后,我们可以通过添加元素来向约瑟夫环中插入数据。
我们可以选择在环的任意位置插入元素,并且可以动态地调整环的长度。
3. 删除元素根据约瑟夫环的规则,每次删除一个元素后,下一个元素将成为新的起始位置。
我们可以通过删除元素的操作来模拟约瑟夫环的运行过程。
在删除元素时,我们需要考虑环的长度和当前位置。
实验结果通过实验,我们得出了以下结论:1. 约瑟夫环数据结构可以有效地模拟约瑟夫环问题。
通过创建环、添加元素和删除元素的操作,我们可以模拟出约瑟夫环的运行过程,并得到最后剩下的元素。
2. 约瑟夫环数据结构具有一定的应用潜力。
除了解决约瑟夫环问题,该数据结构还可以用于其他类似的问题,如任务调度、进程管理等。
3. 约瑟夫环数据结构的时间复杂度较低。
由于约瑟夫环的特殊性质,我们可以通过简单的链表操作来实现该数据结构,使得其时间复杂度较低。
结论本次实验通过实现约瑟夫环数据结构,深入理解了该问题,并探索了其在实际应用中的潜力。
通过创建环、添加元素和删除元素的操作,我们可以模拟出约瑟夫环的运行过程,并得到最后剩下的元素。
约瑟夫环数据结构具有一定的应用潜力,并且具有较低的时间复杂度。
通过本次实验,我们对数据结构的设计和实现有了更深入的理解,并为将来的研究和应用奠定了基础。
数据结构实验报告题目:约瑟夫环问题一.设计内容[问题描述]约瑟夫环问题的一种描述是:编号为1, 2, 3,…,n的n个人按顺时针方向围坐一圈,每人手持一个密码(正整数)。
一开始任选一个整数作为报数上限值,从第一人开始顺时针自 1 开始顺序报数,报到m 时停止报数。
报m 的人出列, 将它的密码作为新的m 值,从他在顺时针方向上的下一个人开始重新从 1 报数, 如此下去直到所有人全部出列为止。
试设计程序实现之。
[基本要求] 利用循环链表存储结构模拟此过程,按照出列的顺序打印各人的编号。
[ 实验提示] 程序运行后首先要求用户指定初始报数上限值。
然后读取各人的密码。
设n<=30 。
程序执行后,要求用户在计算机终端上显示“提示信息”后,用键盘输入“提示信息”中规定的命令,以“回车符”为结束标志。
相应的输入数据和运算结果显示在其后。
二、设计目的1. 达到熟练掌握C++ 语言的基本知识和技能;2. 能够利用所学的基本知识和技能,解决简单的面向对象程序设计问题。
3. 把课本上的知识应用到实际生活中,达到学以致用的目的。
三、系统分析与设计(确定程序功能模块)1、为实现上述程序的功能,应以有序链表表示集合。
基本操作:InitList(&L)操作结果:构造一个空的有序表L。
DestroyList(&L)初始条件:有序表L 已存在。
操作结果:销毁有序表L。
ListEmpty(L)初始条件:有序表L 已存在。
操作结果:若L为空表,则返回TRUE,否则返回FALSE。
ListLength(L)初始条件:有序表L 已存在。
操作结果:返回L 中数据元素个数。
GetElem(L,i)初始条件:有序表L已存在,并且K i< ListLength(L)。
操作结果:返回L 中第i 个数据元素。
LocatePos(L,e)初始条件:有序表L已存在,e和有序表中元素同类型的值。
操作结果:若L中存在和e相同的元素,则返回位置;否则返回0。
数据结构实验报告约瑟夫环约瑟夫环是一个古老而有趣的问题,也是数据结构中一个经典的应用。
它的故事发生在公元前1世纪,当时犹太人正面临罗马的入侵。
为了避免被俘虏,一群犹太士兵决定以一种特殊的方式自杀,而不是被罗马人俘虏。
他们围成一个圈,按照某个规则进行自杀,直到只剩下一个人为止。
这就是著名的约瑟夫环问题。
在这个问题中,我们有n个人,编号从1到n,围成一个圈。
按照一定的规则,从第一个人开始报数,每次报到m的人将被淘汰。
然后,从下一个人开始重新报数,如此循环,直到只剩下一个人为止。
这个问题的解决方法有很多,其中最常见的是使用链表数据结构。
我们可以将每个人表示为一个节点,节点之间通过指针连接,形成一个环形链表。
每次淘汰一个人后,只需要将指针跳过被淘汰的节点,重新连接链表。
为了更好地理解这个问题,我们可以通过一个简单的例子来演示。
假设有10个人,编号从1到10,每次报数到3的人将被淘汰。
首先,我们将这10个人表示为一个环形链表:1->2->3->4->5->6->7->8->9->10->1。
按照规则,第一次报数到3的人是3号,所以我们将3号节点从链表中删除:1->2->4->5->6->7->8->9->10->1。
接下来,从4号节点开始重新报数。
第二次报数到3的人是6号,所以我们再次将6号节点从链表中删除:1->2->4->5->7->8->9->10->1。
以此类推,直到只剩下一个人为止。
通过这个例子,我们可以看到约瑟夫环问题的解决方法非常简单直观。
使用链表数据结构,每次淘汰一个人后,只需要将指针跳过被淘汰的节点,重新连接链表。
这种方法的时间复杂度为O(n*m),其中n为人数,m为报数的次数。
除了链表,还有其他数据结构可以用来解决约瑟夫环问题。
约瑟夫环算法数据结构《约瑟夫环算法数据结构》想象一下这样一个场景,在一个充满欢乐氛围的校园里,有一群朝气蓬勃的学生。
我呢,就是其中一个对算法充满好奇的家伙。
这一天,我们的数学老师,一个戴着厚厚眼镜,头发有点稀疏但眼神中透着智慧光芒的中年人,要给我们讲一个超级有趣的数学游戏,这个游戏就和约瑟夫环算法有关。
老师站在讲台上,手里拿着粉笔,眼睛扫视着我们,脸上带着神秘的笑容。
他说:“同学们,今天我们来玩个特别的游戏。
”只见他在黑板上画了一个大大的圈,然后在圈里依次写上1到n这些数字,代表着n个同学。
老师接着说:“现在假设你们就是这些数字所代表的人,我们要进行一个淘汰游戏。
从数字1开始,按顺时针方向报数,每报到m的人就离开这个圈子,就像被淘汰出局一样,然后下一个人再从1开始报数,一直这样循环下去,直到最后只剩下一个人。
这个游戏的过程其实就反映了约瑟夫环算法哦。
”同学们开始交头接耳,有的跃跃欲试,有的则皱着眉头,感觉有点懵。
我心里想:“这听起来有点复杂呢,不过好像很有趣。
”我们开始玩这个游戏,我是数字3。
第一轮开始报数,我紧张地听着前面同学报数,就像等待命运审判一样。
“1,2,3”,当报到3的时候,我心里一凉,不过按照规则,我只能沮丧地离开圈子,走到教室后面。
我看着剩下的同学们继续报数,就像一个被抛弃的小兵。
从这个游戏中,我们可以看到约瑟夫环算法的数据结构就像是一个环形的队列。
每个同学就像是队列中的一个元素,在这个环形结构里,有顺序地进行报数操作。
每淘汰一个同学,就相当于从这个数据结构中移除一个元素。
这个过程中,位置的计算和元素的移动是关键。
如果从数据结构的角度来看,我们可以用数组或者链表来实现约瑟夫环算法。
就好比把同学们排成一排(数组)或者用链子把同学们串起来(链表),然后按照规则进行操作。
如果用数组的话,每次有人被淘汰,我们可能需要移动后面的元素来填补空缺,这就像同学们之间互相挪动位置,有点麻烦呢。
而链表就不一样了,链表中的节点可以很方便地删除,就像同学们可以轻松地从链子上解开,不需要挪动其他人的位置。
数据结构实验报告约瑟夫环约瑟夫环是一个经典的问题,涉及到数据结构中的循环链表。
在本次数据结构实验中,我们将学习如何使用循环链表来解决约瑟夫环问题。
约瑟夫环问题最早出现在古代,传说中的犹太历史学家约瑟夫斯·弗拉维奥(Josephus Flavius)在围攻耶路撒冷时,为了避免被罗马人俘虏,与其他39名犹太人躲进一个洞穴中。
他们决定宁愿自杀,也不愿被敌人俘虏。
于是,他们排成一个圆圈,从第一个人开始,每次数到第七个人,就将他杀死。
最后剩下的人将获得自由。
在这个问题中,我们需要实现一个循环链表,其中每个节点表示一个人。
我们可以使用一个整数来表示每个人的编号。
首先,我们需要创建一个循环链表,并将所有人的编号依次添加到链表中。
接下来,我们需要使用一个循环来模拟每次数到第七个人的过程。
我们可以使用一个指针来指向当前节点,然后将指针移动到下一个节点,直到数到第七个人为止。
一旦数到第七个人,我们就将该节点从链表中删除,并记录下该节点的编号。
然后,我们继续从下一个节点开始数数,直到只剩下一个节点为止。
在实现这个算法时,我们可以使用一个循环链表的数据结构来表示约瑟夫环。
循环链表是一种特殊的链表,其中最后一个节点的指针指向第一个节点。
这样,我们就可以实现循环遍历链表的功能。
在实验中,我们可以使用C语言来实现循环链表和约瑟夫环算法。
首先,我们需要定义一个节点结构体,其中包含一个整数字段用于存储编号,以及一个指针字段用于指向下一个节点。
然后,我们可以实现创建链表、添加节点、删除节点等基本操作。
接下来,我们可以编写一个函数来实现约瑟夫环算法。
该函数接受两个参数,分别是参与游戏的人数和每次数到第几个人。
在函数内部,我们可以创建一个循环链表,并将所有人的编号添加到链表中。
然后,我们可以使用一个循环来模拟每次数到第几个人的过程,直到只剩下一个节点为止。
在每次数到第几个人时,我们可以删除该节点,并记录下其编号。
最后,我们可以返回最后剩下的节点的编号。
约瑟夫环问题的三种解法约瑟夫问题是个著名的问题: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位幸存者。
约瑟夫环问题知识点总结约瑟夫环问题的描述如下:有n个人(编号从1到n),他们围成一个环形。
从第一个人开始报数,数到m的人被杀掉,然后从被杀掉的人的下一个人开始重新报数,直到所有人都被杀掉为止。
问题的解是最后剩下的那个人的编号。
约瑟夫环问题在计算机科学和数学领域都有着广泛的应用,因为它涉及到循环队列、递归、数学归纳法等多个概念。
以下是约瑟夫环问题的一些重要知识点总结:1. 约瑟夫环问题的递归解法递归是解决约瑟夫环问题的一种常见的方法。
基本思路是将问题分解为规模更小的子问题,并通过解决子问题来解决原始问题。
对于约瑟夫环问题来说,递归的解法是通过递归地计算每轮的幸存者,直到只剩下一个人为止。
递归解法的关键是找到问题的递归关系。
具体而言,对于约瑟夫环问题,如果用f(n, m)表示n个人中最后幸存者的编号,那么可以得出如下的递归关系:f(n, m) = (f(n-1, m) + m) % n其中,%表示取模运算。
该递归关系表明,当有n个人的时候,最后幸存者的编号可以通过n-1个人的最后幸存者的编号计算得到。
2. 约瑟夫环问题的迭代解法除了递归解法之外,约瑟夫环问题还可以通过迭代的方式进行求解。
迭代解法的基本思路是模拟报数和杀人的过程,直到最后只剩下一个人为止。
迭代解法的关键是找到每一轮报数的规律。
具体而言,对于约瑟夫环问题,可以用一个循环队列来模拟报数的过程,每次报数到第m个人就将其从队列中移除。
通过不断循环这个过程,最终可以得到最后幸存者的编号。
3. 约瑟夫环问题的数学解法约瑟夫环问题还可以通过数学的方法进行求解。
具体而言,可以利用数学归纳法来推导出约瑟夫环问题的解析表达式。
这种方法的优点是可以更快地得到结果,但是需要一定的数学推导能力。
通过数学推导,可以得到约瑟夫环问题的解析表达式:f(n, m) = (f(n-1, m) + m) % n其中,f(1, m) = 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就能得到原来的编号。
线性表的操作及其应用一、问题描述线性表、队列是一种常用的数据结构,有顺序和链式两种存储结构,在实际中应用十分广泛,而链表又分为单链表和循环链表,队列又分为链式队列和循环队列。
这些数据结构都可用来解决约瑟夫环问题。
约瑟夫环问题是算法设计中的一个经典问题,是顺序编号的一组n个人围坐一圈,从第1个人按一定方向顺序报数,在报到m时该人出列,然后按相同方法继续报数,直到所有人出列。
设计算法求约瑟夫环中人员的出列顺序。
二、基本要求1、选择合适的存储结构,建立线性表;2、利用顺序存储结构求解约瑟夫环问题;3、利用单链表和循环链表分别求解约瑟夫环问题;4、利用队列求解约瑟夫环问题。
三、测试数据约瑟夫环的测试数据为7,报数为1至3。
四、算法思想由于用到四种不同的存储结构,它们的算法思想依次是:1、首先建立一个顺序表模拟整个约瑟夫环,手动输入顺序表长(即参加约瑟夫循环的人数)和循环的次数和表元素。
用已经输出总人数和顺序表长作比较,作为外层循环条件。
并对每一个输出后的元素重新赋值以为标记。
对于每次循环,首先检查顺序表此次是不是我们设立的标记,如果不是则循环次数加1,当达到要求的循环次数时就将循环次数设置为0,输出该元素到屏幕并将总输出元素加1。
每次外循环我们都会移到表的下一个位置,作为新的判断条件,每次报到表尾的时候,我们都将重新设置到表尾,作为下次循环的表元素。
2、首先采用链式循环链表建立整个约瑟夫环,手动输入第一次的循环次数和每个人所持下一个循环次数。
设立判断指针指向表头,并将该指针是否为空作为外层循环条件。
做一个内层循环,将判断指针移动到循环要输出的数,并设立一个前指针指向该指针的前一个位置,输出该元素后,将循环次数重新赋值成该元素。
接着判断前指针和判断指针比较,如果相等说明整个表已经输出完毕,否则将删除该位置的元素。
3、用链式队列建立循环约瑟夫环,手动输入人数,第一次的循环次数和每个人所持下一个循环次数。
并将每一个元素依次入队列,根据第一次循环次数,建立一个for循环,每一次循环都出队列,如果达到要求的循环次数就输出,否则进队列,这样这个数字就出现在队尾。
《数据结构》实验报告班级:姓名:学号:日期:08-10-20题目:约瑟夫环一、上机实验的问题和要求:问题描述:编号为1到n的n个人围成一圈,每人带一个密码c,以m为报数上限。
然后从第一个人开始顺时针自1开始报数,报到m的人出列,将其密码作为新的m值,从他的下一个人开始,同样顺时针自1开始报数,依次循环下去,直到所有的人都出列!要求得到依次出列的那些人的编号序列!基本要求:用C代码实现此活动,要求使用一定的算法进行操作,并最终通过程序运算出最后的结果!二、程序设计的基本思想,原理和算法描述:(包括程序的模块结构,数据结构,输入/输出设计,符号名说明等)基本思想:利用不带头结点单向循环链表模拟该活动,在实现了一切动作之后,运用一定的算法得到最终的结果。
程序的模块结构:定义了相关的结构体之后,主要有以下几大模块:1.建立由头指针指示的有n个结点的不带头结点的单向循环链表crt_CLinkList(H,n);2.寻找、输出、删除H单循环链表的第m个结点locfor(H,m);3.最后通过main函数体处理参数的输入与显示,并调用以上两函数,最终解决问题。
主要数据结构:单链的循环链表,即线性表的链式存储结构。
算法的伪码描述:结构体定义如下:typedef struct Lnode /*定义单链表*/{int number; /*编号*/int cipher; /*密码*/struct Lnode *next; /*指针*/}Lnode,*CLinklist; /*重定向命名*/CLinklist H; /*H为全局单链表*/算法的实现详见源程序。
输入/输出设计本程序并未采用任何二进制文件出入的方式,这点说明将在最后总结提到。
至于函数的出入口问题,在源程序及注释中将得到详细说明,这里不再赘述。
三、源程序及注释:(说明函数功能、入口及出口参数,其他)#include <stdio.h> /* 头文件*/#include <stdlib.h>#include <alloc.h>typedef struct Lnode /*定义单链表*/{int number; /*编号*/int cipher; /*密码*/struct Lnode *next; /*指针*/}Lnode,*CLinklist; /*重定向命名*/CLinklist H; /*H为全局单链表*/struct Lnode *crt_CLinkList(CLinklist H,int m) /*创建一个不带头结点的单向循环链表*/{ /*其中,H为全局单链表,m为链表结点总数*/ int i; /*循环记数用*/struct Lnode *ptr1; /*用于索引*/if((ptr1=(struct Lnode *)malloc(sizeof(struct Lnode)))==NULL) /*一旦动态内存分配失败,报错退出!*/ {perror("malloc");return ptr1;}H=ptr1; /*形成单个结点的单循环链表*/ptr1->next=H;for(i=1;i<m;i++) /*形成m个结点的不带头结点的单循环链表*/ {if((ptr1->next=(struct Lnode *)malloc(sizeof(struct Lnode)))==NULL){perror("malloc");ptr1->next=H;return H;}ptr1=ptr1->next; /*其中H指头,ptr指尾*/ptr1->next=H;}return H; /*返回成功新建的单链表H*/}void locfor(CLinklist H,int m) /*寻找输出删除链表H中的第m个结点*/{ /*H为全局链表,m为要查找删除的结点位置*/ int i; /*循环计数*/struct Lnode *ptr;for(i=1;i<=5;i++) /*考虑图形界面的美观问题*/printf("number\tcipher\t");i=1; /*初始化*/while(H->next!=H) /*只剩单个结点时停止循环,进行最后输出!*/ {if(m==1) /*考虑进行自身删除的情况,即m=1*/{for(ptr=H->next;ptr->next!=H;ptr=ptr->next);/*正因为是自身删除才要费大劲寻找其父结点*/printf("%-6d\t",H->number); /*找到后,先输出*/printf("%-6d\t",H->cipher);m=H->cipher; /*确定下一次寻找的m值*/ptr->next=H->next; *删除结点,即自身结点*/ptr=ptr->next;free(H); /*因为对于链表中的结点,每个之前都分配了内存,所以free是必要的*/H=ptr; /*不同于以下普通结点的删除,自身删除还要还原H*/i=1; /*i重置,以方便下一次的循环操作*/}else if(i==m-1) /*因为从自身开始即为1号,因为m-1,而不是m*/{ptr=H->next; /*结点的删除操作同于上面的情况!*/printf("%-6d\t",ptr->number);printf("%-6d\t",ptr->cipher);m=ptr->cipher;H->next=ptr->next;H=H->next;free(ptr);i=1;}else{H=H->next; /*若未找到,则继续遍历!*/i++;}}printf("%-6d\t",H->number); /*对于单个结点的单循环链表,进行最终的输出操作!*/ printf("%-6d",H->cipher);free(H); /*完成所有任务并释放所有内存占用!*/}void main() /*主函数接口*/{ /*调用所有函数,进行实验模拟,并得出实验结果!*/ int i,j,n=30,m,k;struct Lnode *ptr;randomize(); /*因为下面调用了random函数,故此处的初始化很有必要!*/ printf("Now the experiment will begin.You have two choices!\n");/*数据输入可以电脑随机,也可以自行输入*/printf("If you want to enter the datas all by yourself,please enter 1.\n");/*自行输入(方便检测程序问题)*/ printf("If you want to get the datas by the computer,please enter 0.\n"); /*电脑随机产生数据*/printf("Now your choice:"); /*用户选择*/scanf("%d",&j);while(j!=0&&j!=1) /*考虑程序的完善性,对于误输入的提示并报警!*/ {printf("\nYou enter is unillage!Please try again!\n");printf("Now your choice:"); /*重新输入*/scanf("%d",&j);}H=crt_CLinkList(H,n); /*初始化链表*/if(j==0) /*电脑随机充入数据*/for(i=1;i<=30;i++){H->number=i;H->cipher=rand(); /*随机函数*/H=H->next;}else /*此时选择实验者输入数据!*/{for(i=1;i<=30;i++){H->number=i;printf("Now number %d,please enter the cipher:",i);scanf("%d",&k);H->cipher=k;H=H->next;}}m=rand(); /*默认情况下,m随机产生*/printf("Do you want to enter the number m?Enter 1 to set or others cancel!\n");/*当然,如果想自已输,可以进行覆盖*/scanf("%d",&k);if(k==1) /*自行输入m值*/{printf("Please enter the number m:");scanf("%d",&m);}system("cls"); /*考虑屏幕大小,进行分屏显示*/printf("All followed are your datas!\n"); /*界面友善*/for(i=1;i<=5;i++)printf("number\tcipher\t");for(i=0;i<30;i++) /*显示所有处理前数据*/{printf("%-6d\t",H->number);printf("%-6d\t",H->cipher);H=H->next;}printf("And the number m is :%d\n",m);printf("\nAfter the program,the result is:\n");locfor(H,m); /*对所有数据进行实验处理,直至所有结点处理完!*/ getchar();printf("\nPress any key return!"); /*TC环境下,方便查看结果!*/getchar();}四、用户使用说明与测试运行结果:1.运行程序后,首先弹出界面,截图如右:理性化的选择:如果打1,则所有的cipher均由用户输入,这样方便对特殊数据进行程序测试!如果打0,那么所有的数据均由电脑产生!那如果打其他的呢?考虑到误输入,加了一个循环,以提高程序的健壮性!2.首先自行输入数据进行测试。
一、实验目的1. 理解并掌握约瑟夫环问题的基本原理。
2. 通过编程实现约瑟夫环问题,加深对循环链表的理解和应用。
3. 提高数据结构与算法的设计和实现能力。
二、实验环境1. 操作系统:Windows 102. 编程语言:Java3. 开发工具:Eclipse三、实验原理约瑟夫环问题是一个著名的数学问题,其基本模型如下:n个人围成一圈,从第一个人开始报数,每数到m的人出列,然后下一个人继续从1开始报数,直到所有人都出列。
该问题可以用循环链表来解决。
循环链表是一种线性链表,其特点是最后一个节点的指针指向链表的头节点,形成一个环。
在约瑟夫环问题中,每个节点代表一个人,节点的指针指向下一个节点,形成一个圆圈。
四、实验步骤1. 创建一个循环链表,用于存储所有人。
2. 添加一个方法,用于模拟报数过程,并输出出列顺序。
3. 添加一个方法,用于输出所有人的编号。
五、实验代码```javapublic class JosephusCircle {// 循环链表节点static class Node {int number; // 人的编号Node next; // 指向下一个节点public Node(int number) {this.number = number;this.next = null;}}// 创建循环链表public static Node createCircle(int n) {Node head = new Node(1);Node current = head;for (int i = 2; i <= n; i++) {current.next = new Node(i);current = current.next;}current.next = head; // 形成循环return head;}// 模拟报数过程public static void simulate(int n, int m) {Node head = createCircle(n);Node current = head;Node pre = null;while (current.next != current) { // 仍有节点在链表中for (int i = 1; i < m; i++) { // 报数m-1次pre = current;current = current.next;}pre.next = current.next; // 移除当前节点System.out.println("出列:" + current.number);current = current.next; // 继续下一个节点}System.out.println("最后出列的人编号:" + current.number); }// 输出所有人编号public static void printNumbers(int n) {Node head = createCircle(n);Node current = head;System.out.print("所有人编号:");while (current.next != current) {System.out.print(current.number + " ");current = current.next;}System.out.println(current.number);}public static void main(String[] args) {int n = 10; // 人数int m = 3; // 报数simulate(n, m);printNumbers(n);}}```六、实验结果1. 当人数为10,报数为3时,出列顺序为:3 6 9 2 5 8 1 4 7 10。
约瑟夫环问题的两种解法(循环链表和公式法)问题描述这⾥是数据结构课堂上的描述: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),显然,这不是⼀个好的算法。
数据结构实验报告
题目:约瑟夫环问题
一.设计内容
[问题描述]
约瑟夫环问题的一种描述是:编号为1,2,3,…,n的n个人按顺时针方向围坐一圈,每人手持一个密码(正整数)。
一开始任选一个整数作为报数上限值,从第一人开始顺时针自1开始顺序报数,报到m时停止报数。
报m的人出列,将它的密码作为新的m值,从他在顺时针方向上的下一个人开始重新从1报数,如此下去直到所有人全部出列为止。
试设计程序实现之。
[基本要求]
利用循环链表存储结构模拟此过程,按照出列的顺序打印各人的编号。
[实验提示]
程序运行后首先要求用户指定初始报数上限值。
然后读取各人的密码。
设n<=30。
程序执行后,要求用户在计算机终端上显示“提示信息”后,用键盘输入“提示信息”中规定的命令,以“回车符”为结束标志。
相应的输入数据和运算结果显示在其后。
二、设计目的
1. 达到熟练掌握C++语言的基本知识和技能;
2. 能够利用所学的基本知识和技能,解决简单的面向对象程序设计问题。
3.把课本上的知识应用到实际生活中,达到学以致用的目的。
三、系统分析与设计(确定程序功能模块)
1、为实现上述程序的功能,应以有序链表表示集合。
基本操作:
InitList(&L)
操作结果:构造一个空的有序表L。
DestroyList(&L)
初始条件:有序表L已存在。
操作结果:销毁有序表L。
ListEmpty(L)
初始条件:有序表L已存在。
操作结果:若L为空表,则返回TRUE,否则返回FALSE。
ListLength(L)
初始条件:有序表L已存在。
操作结果:返回L中数据元素个数。
GetElem(L,i)
初始条件:有序表L已存在,并且1≤i≤ListLength(L)。
操作结果:返回L中第i个数据元素。
LocatePos(L,e)
初始条件:有序表L已存在,e和有序表中元素同类型的值。
操作结果:若L中存在和e相同的元素,则返回位置;否则返回0。
InsertElem(&L,e)
初始条件:有序表L已存在
操作结果:在L中,按有序关系插入值和e相同的数据元素。
DeleteElem(&L,i)
初始条件:有序表L已存在。
操作结果:删除L中第i个数据元素。
ListTraverse(L,visit())
初始条件:有序表L已存在。
操作结果:依次对L的每个数据元素调用函数visit()。
一旦visit()失败,则操作失败。
} ADT OrderedList
2、本程序包含两个模块:
(1)主程序模块:
void main(){
初始化;
调用函数(接受命令,处理命令)}
(2)有序表单元模块——实现有序表的抽象数据类型。
(3)输出函数模块——有序表的输出。
四、源程序代码
#include"stdio.h"
#include"malloc.h"
//1.元素类型,结点类型和指针类型
typedef struct LNode //定义结构体,
{
int num,pwd; //num用来存储人的序号,pwd用来存储人的密码 struct LNode *next;
};
struct LNode *head,*p,*pt; //定义结点
//2、创建循环链表函数
int creatLinkList(int n) //参数n传递人数,
{
int i;
head=(struct LNode*)malloc(sizeof(struct LNode));
//创建一个带头结点的链表
if(!head) {return 0;} //创建不成功,返回0
p=head;
for(i=1;i<n;i++)
{
pt=(struct LNode*)malloc(sizeof(struct LNode));
if(!pt) {return 0;}
p->next=pt;
p=pt;
}
p->next=head; //构成循环链表
pt=head;
return 0;
}
//3.创建输入密码函数
int enterPwd(int n) //参数n传递人数
{
int i,j;
printf("\n请输入密码: \n");
for( i=1;i<=n;i++)
{
scanf("%d",&j);
pt->num=i; //num存储人的序号
pt->pwd=j; //pwd存储人的密码
pt=pt->next;
}
pt=p;
return j;
}
//4、创建输出函数
int outList(int m,int n) //参数m、n传递报数上限值和人数{
int i,a;
for(i=1;i<=n;i++) //用一个for循环搜索循环链表
{
for(a=1;a<m;a++) //删除结点
{
pt=pt->next;
}
p=pt->next;
m=p->pwd;
printf("%d ",p->num); //输出人的序号
pt->next=p->next;
free(p); //释放动态申请的结点空间
}
return 0;
}
//主函数
void main()
{ int m,n; //m为报数上限值,n为人数
printf("\n参数m、n传递报数上限值和人数;\n");
printf("\n请输入 m 和 n: \n");
scanf("%d %d",&m,&n);
creatLinkList( n); //调用创建链表函数
enterPwd( n); //调用输入密码函数
printf("\n出队的人依次是:\n");
outList( m,n); //调用输出链表函数
}
五、测试结果及功能说明
输入报数上限值为m为6 ,人数n为7;
7个人数密码依次是1,2,3,4,5,6,7;
出队的人号码依次是:6,5,4,3,2,1,7
六、设计体会
数据结构是一门比较抽象的课程,但是也是一门最基础的课程学的过程。
在现实生活的应用也非常广泛通过设计该实验让我更加深刻的掌握了掌握了,有关链表的知识,同时也认识到了自己在平时学习当中的盲点,通过这次实验发现了自己在数据结构这门功课上存在严重的不足。
链表中的指针理解不够,运用不够熟练,常常出现一些莫名其妙的问题,调试费时太多。
今后一定会多编程,多思索。
在以后的学习过程中还需要不断的完善学习,希望能把数据结构这门课学习的更加深入,更加透彻。
最后,非常感谢老师在实验的过程所提供的帮助和指导。