约 瑟 夫 环 问 题 的 三 种 解 法
fellenius法的解析解法
fellenius法的解析解法
求解非线性方程组是数学中一个重要的问题,它可以用来求解复杂的物理系统和工程系统。
Fellenius法是一种用于求解非线性方程组的解析解法,它是由瑞典数学家Fellenius于1930年提出的。
Fellenius法是一种迭代法,它的基本思想是:首先将非线性方程组转化为一个线性方程组,然后用迭代法求解线性方程组,最后用求得的线性方程组的解来求解原非线性方程组。
Fellenius法的具体步骤如下:
(1)给定非线性方程组:
F(x)=0
(2)将非线性方程组转化为线性方程组:
F(x+h)=F(x)+J(x)h=0
其中,J(x)是非线性方程组的雅可比矩阵。
(3)用迭代法求解线性方程组:
h=h-J(x)^(-1)F(x)
(4)用求得的线性方程组的解来求解原非线性方程组:
x=x+h
Fellenius法是一种有效的求解非线性方程组的解析解法,它的优点是简单易行,可以快速求解复杂的非线性方程组。
但是,Fellenius法也有一些缺点,比如它的收敛性不够好,而且它只能求解某些特定类型的非线性方程组。
总之,Fellenius法是一种有效的求解非线性方程组的解析解法,它的优点是简单易行,可以快速求解复杂的非线性方程组,但也有一些缺点,比如它的收敛性不够好,而且它只能求解某些特定类型的非线性方程组。
约瑟夫环问题
约瑟夫环问题问题描述:有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;有了这个公式,问题就变得多了。
约瑟夫环递推推导过程
约瑟夫环递推推导过程问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人继续从0开始报数。
求胜利者的编号。
下面利用数学推导,如果能得出一个通式,就可以利用递归、循环等手段解决。
下面给出推导的过程:(1)第一个被删除的数为(m-1)%n;(2)第二论的开始数字为k,那么这n-1个数构成的约瑟夫环为k,k+1,k+2,...k-3,k-2做一个简单映射。
(p(x)=(x-k)%n)k--->0k+1--->1k+2--->2------k-2--->n-2这是一个n-1个人的问题,如果能从n-1个人的问题的解退出n 个人问题的解,从而得到一个递推公式,那么问题就解决了。
假如我们已经知道了n-1个人时,最后胜利者的编号为x,利用映射关系逆推,就可以得出n个人时,胜利者的编号为(x+k)%n。
其中k=m%n。
代入(x+k)%n<=>(x+(m%n))%n<=>(x%n + (m%n)%n)%n<=> (x%n+m%n)%n <=> (x+m)%n(3)第二个被删除的数为(m-1)%n-1(4)假设第三轮的开始数字为o,那这n-2个数构成的约瑟夫环为o,o+1,o+2,...,o-3,o-2。
继续做映射p(x)=(y-o)%(n-2)o--->0o+1--->1o+2--->2------o-2--->n-3这是一个n-2个人的问题。
假设最后胜利者为y,那么n-1个人时,胜利者为(y+o)%(n-1),其中o等于m%(n-1)。
代入可得(y+m)%(n-1)要得到n-1个人问题的解,只需要得到n-2个人问题的解,倒退下去。
只有一个人时,胜利者就是编号0.小面给出递推式:f(1)=0;f(i)=(f[i-1]+m)%i;(i>1)有了递推公式,实现就非常简单了,给出循环的两种实现。
约瑟夫环知识点总结
约瑟夫环知识点总结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. 约瑟夫环的迭代解法在解决实际问题的时候,我们更多地使用迭代的方法来求解约瑟夫环的问题。
迭代的思路是从最简单的情况开始,然后不断迭代得到更加复杂的情况的解。
对于约瑟夫环问题,迭代的思路是逐步得出每一轮出列的士兵的编号并记录下来,直到剩下最后一个士兵为止。
通常情况下,我们会使用一个数组或者链表来保存每一轮出列的士兵的编号,最后得出最后一个士兵的编号。
约 瑟 夫 环 问 题 的 三 种 解 法
约瑟夫环问题的简单解法(数学公式法)关于约瑟夫环问题,无论是用链表实现还是用数组实现都有一个共同点:要模拟整个游戏过程,不仅程序写起来比较烦,而且时间复杂度高达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’。
约 瑟 夫 环 问 题 的 三 种 解 法
约瑟夫环问题的简单解法(数学公式法)关于约瑟夫环问题,无论是用链表实现还是用数组实现都有一个共同点:要模拟整个游戏过程,不仅程序写起来比较烦,而且时间复杂度高达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’。
约瑟夫斯问题
约瑟夫斯问题约瑟夫问题维基百科,⾃由的百科全书跳到导航跳到搜索约瑟夫问题(有时也称为约瑟夫斯置换),是⼀个出现在计算机科学和数学中的问题。
在计算机编程的算法中,类似问题⼜称为约瑟夫环。
⼈们站在⼀个等待被处决的圈⼦⾥。
计数从圆圈中的指定点开始,并沿指定⽅向围绕圆圈进⾏。
在跳过指定数量的⼈之后,处刑下⼀个⼈。
对剩下的⼈重复该过程,从下⼀个⼈开始,朝同⼀⽅向跳过相同数量的⼈,直到只剩下⼀个⼈,并被释放。
问题即,给定⼈数、起点、⽅向和要跳过的数字,选择初始圆圈中的位置以避免被处决。
历史这个问题是以弗拉维奥·约瑟夫命名的,他是1世纪的⼀名犹太历史学家。
他在⾃⼰的⽇记中写道,他和他的40个战友被罗马军队包围在洞中。
他们讨论是⾃杀还是被俘,最终决定⾃杀,并以抽签的⽅式决定谁杀掉谁。
约瑟夫斯和另外⼀个⼈是最后两个留下的⼈。
约瑟夫斯说服了那个⼈,他们将向罗马军队投降,不再⾃杀。
约瑟夫斯把他的存活归因于运⽓或天意,他不知道是哪⼀个。
[1]解法⽐较简单的做法是⽤循环单链表模拟整个过程,时间复杂度是O(n*m)。
如果只是想求得最后剩下的⼈,则可以⽤数学推导的⽅式得出公式。
且先看看模拟过程的解法。
Python版本-- coding: utf-8 --class Node(object):def init(self, value):self.value = valueself.next = Nonedef create_linkList(n):head = Node(1)pre = headfor i in range(2, n+1):newNode = Node(i)pre.next= newNodepre = newNodepre.next = headreturn headn = 5 #总的个数m = 2 #数的数⽬if m == 1: #如果是1的话,特殊处理,直接输出print (n)else:head = create_linkList(n)pre = Nonecur = headwhile cur.next != cur: #终⽌条件是节点的下⼀个节点指向本⾝for i in range(m-1):pre = curcur = cur.nextprint (cur.value)pre.next = cur.nextcur.next = Nonecur = pre.nextprint (cur.value)using namespace std;typedef struct _LinkNode {int value;struct _LinkNode* next;} LinkNode, *LinkNodePtr;LinkNodePtr createCycle(int total) {int index = 1;LinkNodePtr head = NULL, curr = NULL, prev = NULL;head = (LinkNodePtr) malloc(sizeof(LinkNode));head->value = index;prev = head;while (--total > 0) {curr = (LinkNodePtr) malloc(sizeof(LinkNode));curr->value = ++index;prev->next = curr;prev = curr;}curr->next = head;return head;}void run(int total, int tag) {LinkNodePtr node = createCycle(total);LinkNodePtr prev = NULL;int start = 1;int index = start;while (node && node->next) {if (index == tag) {printf("%d\n", node->value);prev = node->next;node->next = NULL;node = prev;} else {prev->next = node->next;node->next = NULL;node = prev->next;}index = start;} else {prev = node;node = node->next;index++;}}}int main() {if (argc < 3) return -1;run(atoi(argv[1]), atoi(argv[2]));return 0;}数学推导解法我们将明确解出{\displaystyle k=2}k=2时的问题。
约瑟夫环问题的两种解法(详解)
约瑟夫环问题的两种解法(详解)约瑟夫环问题的两种解法(详解)题⽬:Josephus有过的故事:39 个犹太⼈与Josephus及他的朋友躲到⼀个洞中,39个犹太⼈决定宁愿死也不要被敌⼈抓。
于是决定了⾃杀⽅式,41个⼈排成⼀个圆圈,由第1个⼈开始报数,每报数到第3⼈该⼈就必须⾃杀。
然后下⼀个重新报数,直到所有⼈都⾃杀⾝亡为⽌。
然⽽Josephus 和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与⾃⼰安排在第16个与第31个位置,于是逃过了这场死亡游戏。
对于这个题⽬⼤概两种解法:⼀、使⽤循环链表模拟全过程⼆、公式法我们假设这41个⼈编号是从0开始,从1开始报数,第3个⼈⾃杀。
1、最开始我们有这么多⼈:[ 0 1 2 3 4 5 ... 37 38 39 40 ]2、第⼀次⾃杀,则是(3-1)%41=2 这个⼈⾃杀,则剩下:[ 0 1 3 4 5 ... 37 38 39 40 ]3、然后就是从编号为3%41=3的⼈开始从1报数,那么3号就相当于头,既然是头为什么不把它置为0,这样从它开始就⼜是与第1,2步⼀样的步骤了,只是⼈数少了⼀个,这样不就是递归了就可以得到递归公式。
想法有了就开始做:4、把第2步中剩下的⼈编号减去3映射为:[ -3 -2 0 1 2 ... 34 35 36 37 ]5、出现负数了,这样不利于我们计算,既然是环形,37后⾯报数的应该是-3,-2,那么把他们加上⼀个总数(相当于加上360度,得到的还是它)[ 38 39 0 1 2 3 ... 34 35 36 37 ]6、这样就是⼀个总数为40个⼈,报数到3杀⼀个⼈的游戏。
这次⾃杀的是第5步中的(3-1)%40=2号,但是我们想要的是第2步中的编号(也就是最初的编号)那最初的是多少?对应回去是5;这个5是如何得到的呢?是(2+3)%41得到的。
⼤家可以把第5步中所有元素对应到第2步都是正确的。
7、接下来是[ 35 36 37 38 0 1 2... 31 32 33 34 ]⾃杀的是(3-1)%39=2,先对应到第5步中是(2+3)%40=5,对应到第2步是(5+3)%41=8。
约瑟夫环问题的三种解法
约瑟夫环问题的三种解法约瑟夫问题是个著名的问题: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位幸存者。
约瑟夫问题多种解决方法
著名约瑟夫问题一
• 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到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的⽼头脑袋被门挤了,提出了这样⼀个奇葩的问题:已知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 problem)是一个非常经典的数学问题,其得名于公元1世纪的犹太历史学家约塞夫斯(Josephus)。
约瑟夫环问题描述如下:n个人围坐成一个圆圈,从一些人开始依次报数,每报到第m个人,该人就被淘汰出圆圈,然后从下一个人重新开始报数。
直到剩下最后一个人时,即为问题的解。
例如,当n=7,m=3时,最后剩下的是4号人。
本次实验的目的是研究约瑟夫环问题的解决方法,并通过编程实现给定n和m的情况下找到最后的获胜者。
首先,我们需要分析问题的特点。
当n=1时,该问题的解即为最后剩下的人;当n>1时,最后剩下的人可以通过前一轮问题的解(剩下n-1个人的情况下)推导出来。
我们可以将解决该问题的方法分为两种:递归法和迭代法。
一、递归法递归法是通过问题的子问题来解决原问题。
对于约瑟夫环问题来说,递归法的解题思路如下:1.当n=1时,问题的解即为1;2.当n>1时,问题的解为(找到n-1个人时的解+m-1)对n取模,即((f(n-1,m)+m-1)%n)+1二、迭代法迭代法通过循环来解决问题,不断更新当前的解,直到问题得到解决。
对于约瑟夫环问题来说,迭代法的解题思路如下:1.初始化一个长度为n的数组a,a[i]=1表示第i个人还在圆圈中,a[i]=0表示第i个人已经被淘汰出圆圈;2. 从第一个人开始计数,每报数到第m个人,则将该人设为已淘汰,并计数器count加1;3. 重复步骤2,直到count=n-1;4.循环遍历数组a,找到最后剩下的人。
为了更加直观地展示实验结果,我们通过Python编写下述代码:```python#递归法解决约瑟夫环问题def josephus_recursive(n, m):if n == 1:return 1else:return (josephus_recursive(n - 1, m) + m - 1) % n + 1#迭代法解决约瑟夫环问题def josephus_iterative(n, m):a=[1]*ncount = 0i=0while count < n - 1:if a[i] == 1:j=0while j < m:if a[(i + j) % n] == 1:j+=1else:j=0i=(i+1)%na[(i-1)%n]=0count += 1for i in range(n):if a[i] == 1:return i + 1#测试递归法解决约瑟夫环问题print(josephus_recursive(7, 3)) # 输出4 #测试迭代法解决约瑟夫环问题print(josephus_iterative(7, 3)) # 输出4 ```通过以上代码,我们可以得到n=7,m=3时,最后剩下的人是4号人。
约 瑟 夫 环 问 题 的 三 种 解 法
约瑟夫问题(数学解法及数组模拟)约瑟夫问题(有时也称为约瑟夫斯置换,是一个出现在计算机科学和数学中的问题。
在计算机编程的算法中,类似问题又称为约瑟夫环。
又称“丢手绢问题”.)据说著名犹太历史学家 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开始报。
如此反复,最后剩下一个,求最后的胜利者的编号。
约瑟夫环是一个经典的数学问题,我们不难发现这样的依次报数是有规律可循的。
为了方便导出递推公式,我们重新定义一下题目。
问题:N 个人编号为1,2,……,N ,依次报数,报到M 时杀掉那个人,下一个人从1开始报,求最后胜利者的编号。
先给出公式:(,)((1,))%f N M f N M M N =-+,其中(,)f N M 表示N 个人报数,报到M 的人被杀掉,最后胜利者的编号;(1,)f N M -表示N-1个人报数,报到M 的人被杀掉,最后胜利者的编号。
下面我们用数字来演示一下这个过程,其中N=11,M=3。
将上面表格的每一行看成数组,这个公式描述的是:胜利者在这一轮的下标位置。
(1,3)f :下标位置为0;(2,3)f :下标位置为1;(3,3)f :下标位置为1; (4,3)f :下标位置为0;(5,3)f :下标位置为3;…… ……(11,3)f :下标位置为6。
满足(,)((1,))%f N M f N M M N =-+。
下面回答三个小问题:问题1: 假设我们已经知道11个人时,胜利者的下标位置为6。
那下一轮10个人时,胜利者的下标位置为多少?答: 其实吧,第一轮删掉编号为3的人后,之后的人都往前面移动了3位,胜利这也往前移动了3位,所以他的下标位置由6变成3。
问题2: 假设我们已经知道10个人时,胜利者的下标位置为3。
那下一轮11个人时,胜利者的下标位置为多少?答: 这可以看成上一个问题的逆过程,大家都往后移动3位,所以(11,3)(10,3)3f f =+。
不过有可能数组会越界,所以最后需要模上当前人数的个数,即(11,3)((10,3)3)%11f f =+。
问题3: 现在改为人数改为N ,报到M 时,把那个人杀掉,那么数组是怎么移动的? 答: 每杀掉一个人,下一个人成为头,相当于把数组向前移动M 位。
约瑟夫环问讲解
用指针实现
• 定义两个数列:一个表示队列(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表示点名次数) 来实现上述循环
•
• •
josephus环公式法
int N = Integer.parseInt(args[0]);
int M = Integer.parseInt(args[1]);
Node t = new Node(1);
Node x = t;
for (int i = 2; i <= N; x = (x.next=new Node(i++)));
f[i]=(f[i-1]+m)%i; (i>1)
有了这个公 式,我们要做的就是从1-n顺序算出f[i]的数值,最后结果是f[n]。因为实际生活中编号总是从1开始,我们输出f[n]+1
由于是 逐级递推,不需要保存每个f[i],程序也是异常简单:
#i nclude <stdio.h>
maபைடு நூலகம்n()
如何知道 (n-1)个人报数的问题的解?对,只要知道(n-2)个人的解就行了。(n-2)个人的解呢?当然是先求(n-3)的情况 ---- 这显然就是一个倒推问题!好了,思路出来了,下面写递推公式:
令f[i]表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是 f[n]
递推公式
f[1]=0;
}
无论是用链表实现还是用数组实现都有一个共同点:要模拟整个游戏过程,不仅程序写起来比较烦,而且时间复杂度高达O(nm),当n,m非常大(例如上百万,上千万)的时候,几 乎是没有办法在短时间内出结果的。我们注意到原问题仅仅是要求出最后的胜利者的序号,而不是要读者模拟整个过程。因此如果要追求效率,就要打破常规,实施 一点数学策略。
k k+1 k+2 ... n-2, n-1, 0, 1, 2, ... k-2
小船渡河问题分析及模型求解方法总结
小船渡河问题分析及模型求解方法总结小船渡河问题是著名的“搜索穷举”(searchforenumeration)问题。
在一条由南至北的河流上,有一艘小船,上面有三个乘客,分别是一个牧师、一个撒谎者和一个犯人,这三个乘客的目的地都不同,他们需要利用这艘小船才能跨越河流到达他们的目的地。
根据他们各自的特性,要求三个乘客同时搭乘小船时必须满足两个条件:1.师和犯人不能同时在船上;2.谎者不能和牧师在一起。
在小船渡河问题中,首先要考虑的是如何分析和分类其状态空间,即要建立一套有效的状态空间模型。
对于每一个节点状态,其状态可以通过三个乘客的位置来确定,可以用一个三元组(P,L,S)来代表,其中P表示牧师的位置,L表示犯人的位置,S表示撒谎者的位置。
根据这种状态空间模型,小船渡河问题可以抽象成一棵带有深度限制的有向无环图,其节点表示可能的状态,边表示可能的操作策略,从而将问题转化为深度优先搜索的问题。
深度优先搜索法是小船渡河问题最常用的求解方法。
它是一种搜索穷举策略,即按照状态节点深度的增加顺序,从根节点出发,沿着有向无环图中的路径穷举所有可能的状态结果,最终找到满足要求的解所在的路径,从而解决问题。
具体的操作步骤如下:1.从源节点出发,并将其放入一个“搜索表”中;2.从“搜索表”中取出节点,将其扩展出所有可能的子节点,并将其放入搜索表中;3.重复上述过程,直到搜索表为空;4.根据最终节点是否满足目标条件,通过搜索表中记录的父节点,得到最优解路径。
此外,在解决小船渡河问题时,可以采用一些其他的求解方法,比如蒙特卡洛方法和遗传算法。
蒙特卡洛方法是一种模拟技术,通过仿真模拟大量的实验,最终得到预期的结果,可以有效地求解小船渡河的最优解路径。
而遗传算法则是一种仿生搜索算法,它采用“选择”、“交叉”、“突变”等“进化”过程,将复杂问题转化为数学优化问题,可以有效地求解出最优解路径。
综上所述,小船渡河问题是一个典型的“搜索穷举”问题,可以通过有效构建状态空间模型并采用深度优先搜索法、蒙特卡洛方法和遗传算法等方法求解。
抽杀问题例举[世界名题与小升初之二]
[阅读材料]世界名题与小升初之:抽杀问题(約瑟夫问题)在各类竞赛中,各类小升初考试中相关的世界名题出现的概率极高,这是由小升初与数学竞赛的特点决定,这特点便是:知识性,趣味性,思想性相结合。
先给大家介绍这一问题的由来。
据说著名犹太历史学家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、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
约瑟夫环问题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=value
self.next=next
def createLink(n):
return False
if n==1:
return Node(1)
root=Node(1)
tmp=root
for i in range(2,n+1):
tmp.next=Node(i)
tmp=tmp.next
tmp.next=root
return root
def showLink(root):
tmp=root
while True:
print(tmp.value)
tmp=tmp.next
if tmp==None or tmp==root: def josephus(n,k):
if k==1:
print('survive:',n)
root=createLink(n)
tmp=root
while True:
for i in range(k-2):
tmp=tmp.next
print('kill:',tmp.next.value) tmp.next=tmp.next.next
tmp=tmp.next
if 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)
运行结果和上面一样。
为了进一步对比性能,我用josephus(100000,4)测试,即n=100000,k=4。
为了去掉IO消耗的时间干扰,把"kill:"的print注释掉,只输出最后的"survive:"结果,测试结果如下:
结果表明,第一种循环链表的方式比第二种取模运算的方式要快,由于比例不是线性的,不能说是几倍,而且这个测试和python内部实现有关,换作C语言O3优化后结果就不一定一样了,所以测试结果不能说明什么哈~
while(p-next != p){ -- 如果 p 的下一个结点指向自己,说明环中只剩一个结点
我们知道第一个人(编号一定是m%n-1) 出列之后,剩下的n-1个人组成了一个新的约瑟夫环(以编号为k=m%n的人开始): 有n只猴子,按顺时针方向围成一圈选大王(编号从1到n),从第1号开始报数,一直数到m,数到m的猴子退出圈外,剩下的猴子再接着从1 开始报数。
就这样,直到圈内只剩下一只猴子时,这个猴子就是猴王,编程求输入n,m后,输出最后猴王的编--将上行改为System.out.println((k+1)+" ");后可去掉下面3行输出语句
print('kill:',tmp.next.value)
如何知道(n-1)个人报数的问题的解?对,只要知道(n-2)个人的解就行了。
(n-2)个人的解呢?当然是先求(n-3)的情况—- 这显然就是一个倒推问题!下面举例说明:
50 public int PlayNum { get; set; }-*每次游戏丢PlayNum 次手绢*-
pcur=(Node*)malloc(sizeof(Node));
序列3:k, k+1, k+2, k+3, …, n-2, n-1, 1, 2, 3,…, k-2, 首先从2开始,因为1个人的时候报的数字的人为0号,结果已经确定了。
不需要从i=0开始,要注意的是序列从0开始编号的,所以最后的输出结果也要加1.。