用数学方法解约瑟夫环

2024-06-11 14:08
文章标签 约瑟夫 数学方法

本文主要是介绍用数学方法解约瑟夫环,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

5.5.4  用数学方法解约瑟夫环

原文:http://book.51cto.com/art/201403/433941.htm


上面编写的解约瑟夫环的程序模拟了整个报数的过程,程序运行时间还可以接受,很快就可以出计算结果。可是,当参与的总人数N及出列值M非常大时,其运算速度就慢下来。例如,当N的值有上百万,M的值为几万时,到最后虽然只剩2个人,也需要循环几万次(M的数量)才能确定2个人中下一个出列的序号。显然,在这个程序的执行过程中,很多步骤都是进行重复无用的循环。


那么,能不能设计出更有效率的程序呢?


办法当然有。其中,在约瑟夫环中,只是需要求出最后的一个出列者最初的序号,而不必要去模拟整个报数的过程。因此,为了追求效率,可以考虑从数学角度进行推算,找出规律然后再编写程序即可。


为了讨论方便,先根据原意将问题用数学语言进行描述。


问题:将编号为0~(N–1)这N个人进行圆形排列,按顺时针从0开始报数,报到M–1的人退出圆形队列,剩下的人继续从0开始报数,不断重复。求最后出列者最初在圆形队列中的编号。


下面首先列出0~(N–1)这N个人的原始编号如下:




根据前面曾经推导的过程可知,第一个出列人的编号一定是(M–1)%n。例如,在41个人中,若报到3的人出列,则第一个出列人的编号一定是(3–1)%41=2,注意这里的编号是从0开始的,因此编号2实际对应以1为起点中的编号3。根据前面的描述,m的前一个元素(M–1)已经出列,则出列1人后的列表如下:




根据规则,当有人出列之后,下一个位置的人又从0开始报数,则以上列表可调整为以下形式(即以M位置开始,N–1之后再接上0、1、2……,形成环状):




按上面排列的顺序重新进行编号,可得到下面的对应关系:
 



即,将出列1人后的数据重新组织成了0~(N–2)共N–1个人的列表,继续求n–1个参与人员,按报数到M–1即出列,求解最后一个出列者最初在圆形队列中的编号。


看出什么规律没有?对了,通过一次处理,将问题的规模缩小了。即,对于N个人报数的问题,可以分解为先求解(N–1)个人报数的子问题;而对于(N–1)个人报数的子问题,又可分解为先求[(N–1)–1]人个报数的子问题,……。


问题中的规模最小时是什么情况?就是只有1个人时(N=1),报数到(M–1)的人出列,这时最后出列的是谁?当然只有编号为0这个人。因此,可设有以下函数:
 



那么,当N=2,报数到(M–1)的人出列,最后出列的人是谁?应该是只有一个人报数时得到的最后出列的序号加上M,因为报到M-1的人已出列,只有2个人,则另一个出列的就是最后出列者,可用公式表示为以下形式:
 



通过上面的算式计算时,F(2)的结果可能会超过N值(人数的总数)。例如,设N=2,M=3(即2个人,报数到2时就出列),则按上式计算得到的值是:
 



一共只有2人参与,编号为3的人显然没有。怎么办?由于是环状报数,因此当两个人报完数之后,又从编号为0的人开始接着报数。根据这个原理,即可对求得的值与总人数N进行模运算,即:
 


5.5.4  用数学方法解约瑟夫环(2)

即,N=2,M=3(即有2个人,报数到3–1的人出列)时,循环报数最后一个出列的人的编号为1(编号从0开始)。我们来推算一下,如下所示,当编号为0、1的两个人循环报数时,编号为0的人报的数为0和2,当报到2(M–1)时,编号0出列,最后剩下编号为1的人,所以编号为1的人最后出列。

根据上面的推导过程,可以很容易推导出,当N=3时的公式:

同理,也可以推导出参与人数为N时,最后出列人员编号的公式:

其实,这就是一个递推公式,公式包含以下两个式子:

有了这个递推公式,再来设计程序就很简单了,可以用递归的方法来设计程序,具体代码如下:
 

 
  1. #include <stdio.h> 
  2. int main(void)  
  3. {  
  4.     int n,m,i,s=0;  
  5.     printf ("输入参与人数N和出列位置M的值 = ");  
  6.     scanf("%d%d",&n,&m);  
  7.       
  8.     printf ("最后出列的人最初位置是 %d\n",josephus(n,m));  
  9.     getch();  
  10.     return 0 ;  
  11. }  
  12.  
  13. int josephus(int n,int m)  
  14. {  
  15.     if(n==1)  
  16.         return 0;  
  17.     else  
  18.         return (josephus(n-1,m)+m)%n;  
  19. }  

在以上代码中,定义了一个递归函数josephus(),然后在主函数中调用这个函数进行   运算。

编译执行以上程序,输入N和M的值,可以很快得到最后出列人的编号,输入N=8,M=3,得到的结果如图5-19所示(注意编号是从0开始)。
 

使用递归函数会占用计算机较多的内存,当递归层次太深时可能导致程序不能执行,因此,也可以将程序直接编写为以下的递推形式:
 

 
  1. #include <stdio.h> 
  2. int main(void)  
  3. {  
  4.     int n,m,i,s=0;  
  5.     printf ("输入参与人数N和出列位置M的值 = ");  
  6.     scanf("%d%d",&n,&m);  
  7.     for (i=2; i<=n; i++)  
  8.         s=(s+m)%i;  
  9.     printf ("最后出列的人最初位置是 %d\n",s);  
  10.     getch();  
  11.     return 0 ;  
  12. }  

这段代码执行的结果与递归程序执行结果完全相同。

可以看出,经过一些数学推导,最后总结出规律简化程序,将几十行的代码缩减到几行。更主要的是,程序执行的效率得到大大的提升,省去了很多重复的循环,既使求解的N和M值很大,也不会成为问题

这篇关于用数学方法解约瑟夫环的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1051386

相关文章

约瑟夫问题(Josephus Problem)4:第k个出列的人是谁

版权所有。所有权利保留。 欢迎转载,转载时请注明出处: http://blog.csdn.net/xiaofei_it/article/details/16813419 本文是论述约瑟夫问题的第四部分,约瑟夫问题的描述在第一部分,本文用到了第三部分的算法。请先阅读第一部分和第三部分。 现在要求输出第k个出列的人的编号。 由第三部分的算法分析可知,规模为i-1的队列的任意一人的编号为p,规

约瑟夫问题(Josephus Problem)3:谁最后一个出列

版权所有。所有权利保留。 欢迎转载,转载时请注明出处: http://blog.csdn.net/xiaofei_it/article/details/16813349 本文是论述约瑟夫问题的第三部分,约瑟夫问题的描述在第一部分。请先阅读第一部分。 现在要求输出最后一个出列的人的编号。 第一次见到这个问题是在我高一的时候,那时候搞NOIP,培训的时候碰到了这个题目,当时没想到好的方法,

约瑟夫-链表

已知 n 个人(编号分别为 1 、 2 、 3 , …… 、 n )围坐在一张圆桌周围,从编号为 1 的人开始报数,数到 m 的那个人出列;他的下一个人又从 1 开始报数,数到 m 的那个人又出   输出:出列人的编号( 5 1 7 4 3 6 9 2 8 ) #include iostream #include malloc.h using namespace std; typ

约瑟夫环的C++实现

#include<stdio.h> #include<iostream> #include<malloc.h> #include<string> #include<queue> using namespace std;   int main() {     int n,i;     int w,s;     queue<string> q;     printf("输入个数:\n");

约瑟夫环的深入探索与C++实现

一、引言 约瑟夫环问题,又称“丢手绢”问题,是一个古老而著名的问题。它起源于古罗马时代,由数学家约瑟夫提出而得名。这个问题描述的是:有n个人围成一圈,从编号为1的人开始报数,每次数到m(m为正整数)的人出列,然后由下一个人重新开始报数,直到所有人都出列为止。要求求出出列的顺序。约瑟夫环问题不仅在数学上具有一定的研究价值,而且在计算机科学、算法设计和数据结构中也有广泛的应用。本文将深入探索约瑟夫环

类似于约瑟夫环的杀人游戏

【题目来源】杭电OJ2211  【题目链接】杭电2211杀人游戏 【代码】 #include <iostream>#include <cstdio>using namespace std;long long hash[120][100000];void printHash(){for(int i = 0; i < 8; i++){printf("hash[%d] = ",i);for

约瑟夫问题(没有头节点的循环链表2——删除法)

#include <stdio.h>#include <stdlib.h>struct student{int id;struct student * next;};struct student * tailCreateCircleList(struct student * L,int num){//没有头节点的循环链表struct student * p, * last;L = (struc

[剑指Offer]-圆圈中最后剩下的数字约瑟夫环问题

题目描述 0, 1, … , n-1 这 n 个数字排成一个圈圈,从数字 0 开始每次从圆圏里删除第 m 个数字。求出这个圈圈里剩下的最后一个数字。 解题思路 创建一个总共有 n 个结点的环形链表,然后每次在这个链表中删除第 m 个结点。 算法图解 参考代码: package offer;import java.util.LinkedList;import java.util.L

百练--2746 -- 约瑟夫问题

2746:约瑟夫问题 总时间限制:  1000ms  内存限制:  65536kB 描述 约瑟夫问题:有n只猴子,按顺时针方向围成一圈选大王(编号从1到n),从第1号开始报数,一直数到m,数到m的猴子退出圈外,剩下的猴子再接着从1开始报数。就这样,直到圈内只剩下一只猴子时,这个猴子就是猴王,编程求输入n,m后,输出最后猴王的编号。 输入 每行是用空格分开

C++ 环形链表(解决约瑟夫问题)

约瑟夫问题描述:        编号为 1 到 n 的 n 个人围成一圈。从编号为 1 的人开始报数,报到 m 的人离开。下一个人继续从 1 开始报数。n-1 轮结束以后,只剩下一个人,问最后留下的这个人编号是多少? 约瑟夫问题例子:        开始5个人 1,2,3,4,5 ,从1开始报数,1->1,2->2编号为2的人离开 1,3,4,5,从3开始报数,3->1,4->2编号