约瑟夫环问题
约瑟夫环的知识点总结
约瑟夫环的知识点总结约瑟夫环这个问题不仅在古代受到了广泛的关注,而且在现代数学中也有着重要的地位。
它涉及到了排列、递推、循环和递归等多个数学概念,并且有着一些有趣的数学特性。
因此,学习约瑟夫环不仅能够增加我们对于数学问题的理解,而且也可以提高我们的数学思维能力。
接下来,我们将从几个方面对约瑟夫环进行深入的讨论。
1. 约瑟夫环的历史约瑟夫环最早出现在约瑟夫斯的《犹太古记》中,他描述了犹太人在与罗马军队的战斗中围攻马萨达城的情景。
根据《犹太古记》的记载,当罗马军队攻陷了马萨达城后,大约960名男子决定宁死不从。
于是,他们站成一个圈,每隔两个人就有一个杀掉,直到最后只剩下一个人。
而这个幸存者恰恰就是约瑟夫斯本人。
因此,这个问题就得名为约瑟夫环。
除了这个故事之外,约瑟夫环在古代数学文献中也有着多次的提及。
例如,中国古代数学家秦九韶在其著作《数书九章》中也提到了这个问题。
他利用递推的方法解出了约瑟夫环的一般解,并推广到了更一般的情况。
自古代以来,约瑟夫环一直受到数学家们的关注,他们提出了很多不同的方法来解决这个问题。
而到了现代,约瑟夫环在计算机科学和密码学中也有着广泛的应用。
因此,约瑟夫环问题可以说是一个古老而又具有重要意义的数学问题。
2. 约瑟夫环的一般解在数学中,我们可以用递推的方法对约瑟夫环进行求解。
假设有N个人站成一圈,编号从0到N-1,而每隔M个人就有一个人出列。
那么一个简单直接的方法就是用递归来求解。
具体来说,我们可以定义一个递归函数f(n, m),表示N个人中最后存活下来的那个人的编号。
那么这个函数的递归关系可以如下定义:f(n, m) = (f(n-1, m) + m) % n其中f(1, m) = 0,表示只有一个人时的情况。
通过递归的方法,我们可以得到约瑟夫环的一般解。
而根据这个递归关系,我们还可以得到一些有趣的数学性质。
例如,我们可以求解约瑟夫环在给定N和M的情况下的解,而不需要实际模拟整个过程。
经典问题约瑟夫环讲解
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}该程序只有主程序。
约瑟夫环实验报告
约瑟夫环问题实验报告一、实验内容本实验利用单向循环链表模拟约瑟夫环问题(编号为1,2,…,n的n个人按顺时针方向围坐一圈,没人持有一个密码(正整数)。
开始任选一个正整数作为报数上限值m,从第一个人开始按顺时针方向自1开始报数,报到m是停止报数。
报m的人出列,将他的密码作为新的m值,从他在顺时针方向上的下一个人开始重新从1报数,如此下去,直至所有人全部出列为止),按照出列顺序印出个人的编号。
M的初值为20,n=7,7个人的密码依次为3,1,7,2,4,8,4,首先m值为6。
二、源程序#include"stdafx.h"#include<iostream>using namespace std;#include<conio.h>//getch();/*write: Han.JH*/static int people_number_count; typedef struct People_Node{int Password_Data,people_number;struct People_Node *next;}People_Node,*People_LinkList;void GreatList(People_LinkList &L){People_Node *s,*r; int flag=1;int Password;L=new People_Node;L->next=NULL;r=L;while(flag==1){cout<<"输入每个人的密码,以回车作间隔,'0'表示结束:";cin>>Password;//输入每个人的密码;if(Password!=0){s=new People_Node;s->Password_Data=Password;people_number_count++; //对人的编号s->people_number=people_number_count;r->next=s;r=s;}else{ r->next=L->next;flag=0;}}}void GetList(People_LinkList &L){People_Node *r;int m,k;int count=0;cout<<"输入报数上限值m:";cin>>m;r=L;cout<<"出列排序:";while(count!=people_number_count){ //则所有人以出列,结束循环for(k=1;k<=m-1;k++){r=r->next;}count++;m=r->next->Password_Data;cout<<"["<<r->next->people_number<<"]";r->next=r->next->next;}}void main(){People_LinkList L;void GreatList(People_LinkList &);void GetList(People_LinkList &);cout<<"++++++++++++约瑟夫环问题+++++++++++"<<endl;GreatList(L);GetList(L);cout<<"[--结束--]"<<endl;getch();}三、调试刚开始调试时出现了无法打开<iostream.h>头文件的错误,经过上网查询,#include<iostream.h>是C语言风格,但是在标准 C 里面,是不用#include <iostream.h>的,而要使用#include <iostream>把#include<iostream.h>改为:#include<iostream>using namespace std;后,问题解决。
约瑟夫环问题小结
约瑟夫环问题⼩结⼀问题描述约瑟夫环问题的基本描述如下:已知n个⼈(以编号1,2,3...n分别表⽰)围坐在⼀张圆桌周围。
从编号为1的⼈开始报数,数到m的那个⼈出列;他的下⼀个⼈⼜从1开始报数,数到m的那个⼈⼜出列;依此规律重复下去,要求找到最后⼀个出列的⼈或者模拟这个过程。
⼆问题解法在解决这个问题之前,⾸先我们对⼈物进⾏虚拟编号,即相当于从0开始把⼈物重新进⾏编号,即⽤0,1,2,3,...n-1来表⽰⼈物的编号,最后返回的编号结果加上1,就是原问题的解(为什么这么做呢,下⽂有解释)。
⽽关于该问题的解通常有两种⽅法:1.利⽤循环链表或者数组来模拟整个过程。
具体来讲,整个过程很明显就可以看成是⼀个循环链表删除节点的问题。
当然,我们也可以⽤数组来代替循环链表来模拟整个计数以及出列的过程。
此处只给出利⽤数组来模拟这个过程的解法,最终结果为最后⼀个出列的⼈的编号:#include<iostream>#include<unordered_map>#include<queue>#include<cstring>#include<cstdlib>#include<cmath>#include<algorithm>#include<sstream>#include<set>#include<map>using namespace std;int main(){int n,m;cin>>n>>m;vector<int>rs(n);for(int i = 0 ; i < n; i++)rs[i] = i + 1;//对⼈物重新进⾏编号,从0开始int cur_index = 0;//当前圆桌状态下的出列⼈的编号int out_cnt = 0;//⽤以表⽰出列的⼈数int cnt = n;//表⽰当前圆桌的总⼈数while(out_cnt < n - 1)//当out_cnt等于n-1时,循环结束,此时圆桌师⽣最后⼀个⼈,即我们要的结果{if(cur_index + m > cnt){if((cur_index + m) % cnt == 0)//这种情况需要单独考虑,否则cur_index就变成负值了cur_index = cnt - 1;elsecur_index = (cur_index + m) % cnt - 1;}elsecur_index = cur_index + m - 1;cnt--;out_cnt++;cout<<"当前出列的为:"<<*(rs.begin() + cur_index)<<endl;rs.erase(rs.begin() + cur_index);//从数组中删去需要出队的⼈员}cout<<"最后⼀个出列的⼈物为:"<<rs[0]<<endl;}该⽅法的时间复杂度为O(nm),空间复杂度为O(n),整个算法的基本流程还是⽐较清晰的,相当于每次循环更新cur_cnt、cnt和out_cnt这三个变量,当out_cnt == n-1时,此时出队的⼈数⼀共有n-1⼈,圆桌上只剩下⼀个⼈了,停⽌循环。
模板约瑟夫环(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)
约瑟夫问题
第一个人出列:从 数器 j 从 1 数到 person[i] <- 0 第二个人出列:从 数器 j 从 1 数到 person[i] <- 0 „„ n-1个出列:„„
k = 0 的后继开始扫描数组,报数计 m,该人出列,把他的状态置成 0,
k = i 的后继开始扫描数组,报数计 m,该人出列,把他的状态置成 0,
此题如果意思是定位第 m 个元素,当不需要跳过任何元素 时。 从 k 开始报数第 m 个元素是: ((( k + m - 1 ) – 1 ) mod ) n +1
“遍历”和“数到”的区别: 遍历到的每个元素需要做相同的操作,而“数到m”第m个 元素和前 m-1 个元素有不同的操作。
如果需要跳过某些标记的元素,就无法直接确定第 m 个元 素的位置,则需要按顺序逐个扫描元素,跳过不符合要求的 元素,对符合要求的元素进行计数。 此处注意第一个数也许需要跳过,第一个报数的编号也许不 是 k,不能直接从 1 计数。 i <- k j <- 0 while ( j< m ) do begin i <- (i mod n) + 1 if ( person[i] <> 0 ) then j <- j+1 end
2.1
模拟方法
2.2
2.1.1 数组+标记 2.1.2 循环链表+删除节点
数学模型
定义数组: array person[1..n]代表 n 个人的状态; 第i个元素代表编号为i的人的状态,下标代表人的编号; 状态: 1代表没有出列,0代表已出列。 使用线性数组表示环状圆圈 * 数组元素间相邻关系和环状结构元素间相邻关系有所不 同,person[n]没有后继节点,person[1]没有前驱节点。 数组可以使用下面方法实现环状结构的相邻关系: person[i]后继节点的下标为j: if i = n then i<- 1 else i <- i+1 或者 i <- (i mod n) +1
约瑟夫斯问题
约瑟夫斯问题约瑟夫问题维基百科,⾃由的百科全书跳到导航跳到搜索约瑟夫问题(有时也称为约瑟夫斯置换),是⼀个出现在计算机科学和数学中的问题。
在计算机编程的算法中,类似问题⼜称为约瑟夫环。
⼈们站在⼀个等待被处决的圈⼦⾥。
计数从圆圈中的指定点开始,并沿指定⽅向围绕圆圈进⾏。
在跳过指定数量的⼈之后,处刑下⼀个⼈。
对剩下的⼈重复该过程,从下⼀个⼈开始,朝同⼀⽅向跳过相同数量的⼈,直到只剩下⼀个⼈,并被释放。
问题即,给定⼈数、起点、⽅向和要跳过的数字,选择初始圆圈中的位置以避免被处决。
历史这个问题是以弗拉维奥·约瑟夫命名的,他是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时的问题。
循环队列之约瑟夫环问题
循环队列之约瑟夫环问题约瑟夫问题 约瑟夫环(约瑟夫问题)是⼀个数学的应⽤问题:已知n个⼈(以编号1,2,3...n分别表⽰)围坐在⼀张圆桌周围。
从编号为k的⼈开始报数,数到m的那个⼈出列;他的下⼀个⼈⼜从1开始报数,数到m的那个⼈⼜出列;依此规律重复下去,直到圆桌周围的⼈全部出列。
通常解决这类问题时我们把编号从0~n-1,最后结果+1即为原问题的解。
循环队列求解(链式)#include<stdio.h>#include<stdlib.h>//循环队列//typedef int ElemType;typedef struct QueueNode{int data;struct QueueNode *next;}QueueNode;typedef struct Queue{QueueNode *front;QueueNode *rear;}Queue;void InitQueue(Queue *q){q->front=q->rear=NULL;}void EnQueue(Queue *q , int value){QueueNode *temp=(QueueNode*)malloc(sizeof(QueueNode));temp->data=value;if(q->rear==NULL){temp->next=temp;q->rear=q->front=temp;}else{temp->next=q->rear->next;q->rear->next=temp;q->rear=temp;}}//enter a element from the tailvoid DeQueue(Queue *q, int *value){QueueNode *temp=(QueueNode*)malloc(sizeof(QueueNode)); if(q->rear==NULL){return;}// It's nullelse if(q->rear->next==q->rear){*value=q->front->data;free(q->rear);q->rear=q->front=NULL;}//It just has one nodeelse{*value=q->front->data;temp=q->front;q->front=temp->next;q->rear->next=q->front;}//more one nodefree(temp);}//delete a element from the headint main(){Queue *q=(Queue*)malloc(sizeof(Queue));int i,m,n,count,temp;printf("请输⼊⼈数n和循环要报的数m(两数之间留个空格)\n"); scanf("%d%d",&n,&m);for(i=1;i<=n;i++)EnQueue(q,i);printf("出圈序列:\n");while(q->front){ count=1;while(count<m){q->front=q->front->next;q->rear=q->rear->next;count++;}count=1;DeQueue(q,&temp);printf("%d ",temp);}putchar('\n');}简单解法#include <stdio.h>int josephus(int n, int m) {if(n == 1) {return0;}else {return (josephus(n-1, m) + m) % n;}}int main() {int n, m;while (scanf("%d", &n) == 1) {if (!n) {break;}scanf("%d", &m);int result = josephus(n, m);printf("%d\n", result+1);}return0;}。
约瑟夫环问题的三种解法
约瑟夫环问题的三种解法约瑟夫问题是个著名的问题: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位幸存者。
约瑟夫环问题详解
约瑟夫环问题详解很久以前,有个叫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 )问题是由古罗马的史学家约瑟夫(Flavius Josephus )提出的。
该问题的说法不一,传说他参加并记录了公元66—70年犹太人反抗罗马的起义。
约瑟夫作为一个将军,设法守住了裘达伯特城达47天之久,在城市沦陷之后,他和40名死硬的将士在附近的一个洞穴中避难。
在那里,这些叛乱者表决说“要投降毋宁死”。
于是,约瑟夫建议每隔两个人杀死一人,而这个顺序是由抽签决定的。
约瑟夫有预谋地抓到了最后一签,并且,作为洞穴中的两个幸存者之一,他说服了另一个幸存者一起投降了罗马。
假设现在一个房间内共有n 个人。
同上所述,“杀人狂” 只想留下一个人活命,并且他将按下面的规则去杀人:● 所有的人围成一圈;● 顺时针报数,每次报到q 的人将被杀掉; ● 被杀掉的人将从房间内移走;● 然后从被杀掉的下一个人重新报数,直到剩余一人。
你非常不幸地参加了这场“游戏”,当然,你是想活命的,所以你必须快速决定要站到哪一个位置,才能使得最后留下的人是你。
这就是最初的“约瑟夫环”问题,纯粹的数学计算无法做出解答,但对于参加信息学竞赛的选手来说,可以快速的编写一个程序来解决。
下面就针对这个问题进行分析并解答。
二、特例当q=2时,是约瑟夫环的一个特例,可以利用数学的方法快速求解。
分析:如果只有2个人,显然剩余的为1号。
如果有4个人,在第一轮移除掉2、4以后,只剩下1、3,现在环中又仅剩下2个人了,我们已经知道2个人时,结果为1,所以当n=4时,最后剩余的也为1号。
如此可设想:当()02≥=k n k时,最后剩余的必定为1。
我们定义()n J 为由n 个人的构建成的约瑟夫环最后的结果,则有()12=kJ 。
让我们来证明该设想的正确性:11222--=-=k k k n (第一轮后,移除掉122/2-=k k 个人,剩余12-k ,1号仍在环中) 221222---=-=k k k n (第二轮后,1号仍在环中)……112222=-=n (1号仍在序列当中)()12=J在上面的分析中,每一轮的移除操作,均移除一半出列,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即可。
约 瑟 夫 环 问 题 的 三 种 解 法
约瑟夫问题(数学解法及数组模拟)约瑟夫问题(有时也称为约瑟夫斯置换,是一个出现在计算机科学和数学中的问题。
在计算机编程的算法中,类似问题又称为约瑟夫环。
又称“丢手绢问题”.)据说著名犹太历史学家 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就能得到原来的编号。
Josephus环问题
Josephus环问题约瑟夫环问题问题描述:Josephus问题可以描述为如下的⼀个游戏:N个⼈编号从1到N,围坐成⼀个圆圈,从1号开始传递⼀个热⼟⾖,经过M次传递后拿着⼟⾖的⼈离开圈⼦,由坐在离开的⼈的后⾯的⼈拿起热⼟⾖继续进⾏游戏,直到圈⼦只剩下最后⼀个⼈。
例如:M=0,N=5,则游戏⼈依次被清除,5号最后留下;如果M=1,N=5,那么被清除的⼈的顺序是2,4,1,5,最后剩下的是3号。
如下是两种解题⽅法:建⽴⼀个N⼤⼩的数组,存储N个⼈是否还在圈⼦内(0为在圈⼦内,-1为已经离开圈⼦),依次循环遍历整个数组,直到剩下的⼈数(left)为1。
public static void pass(int m, int n){int[] num = new int[n];int left = n; //剩下的⼈数int index = 0; //当前遍历到的位置while(left != 1){for(int i = 0; i< m; i++){if(num[index++] != 0) //如果当前⼈已经清除i--;if(index >= n)index = 0;}while(num[index] != 0){index++;if(index >= n)index = 0;}System.out.println("out number is " + (index + 1));num[index] = -1; //将清除的数据下标置-1index++;if(index >= n)index = 0;left--;}}第⼆种⽅式,将1~N的数添加到⼀个ArrayList对列中,⾸先计算偏移量(mPrime),当mPrime⼩于对列长度的⼀半时,则从前往后依次遍历找到清除的元素;当mPrime⼤于对列长度的⼀半时,从后往前遍历找到清除的元素。
public static void pass2(int m, int n){ArrayList<Integer> list = new ArrayList<Integer>();for(int i = 1; i <= n; i++)list.add(i);ListIterator<Integer> iter = list.listIterator();int left = n; //剩下的⼈数int item = 0; //the out numberfor(int i= 1; i < n; i++){int mPrime = m % left;if(mPrime < left/2){ //如果当前的偏移量⼩于list长度的⼀半时if(iter.hasNext())item = iter.next();for(int j = 0; j < mPrime; j++){if(!iter.hasNext())iter= list.listIterator();item = iter.next();}}else{ //当偏移量⼤于list长度的⼀半时,从后往前找for(int j = 0; j< left - mPrime; j++){if(!iter.hasPrevious())iter = list.listIterator(list.size());item = iter.previous();}}System.out.println("out number is " + item);iter.remove();if(!iter.hasNext()) //有可能下⼀次循环mPrime就会⼩于left的⼀半iter = list.listIterator();left--;}}总结当M,N较⼤时,则第⼆种⽅法时间效率更⾼,实现表明,当N=100000,M=9000时(省略的两个算法中的syso语句),⽅法⼀个执⾏时间是30713ms,⽽第⼆种⽅法的执⾏时间仅为4891ms,M越⼤时⽅法⼀的时间效率会更差。
约瑟夫斯问题
约瑟夫斯问题
问题描述
约瑟夫斯问题是一个经典的数学问题,也被称为约瑟夫环问题。
问题的描述如下:有n个人围成一圈,从第一个人开始报数,报到m的人出列,然后从出列的下一个人开始重新报数,再次报到m的人出列,如此循环,直到所有人都出列为止。
那么,最后剩下的人在原来的顺序中编号是几?
算法思路
为了解决约瑟夫斯问题,可以使用一种常用的数学技巧来计算最后剩下的人的编号。
假设n个人的编号分别为0, 1, 2, …, n-1,那么可以得到一个递推公式:
f(n, m) = (f(n-1, m) + m) % n
其中f(n, m)表示有n个人时最后剩下的人的编号。
根据这个递推公式,可以进行递归计算。
算法实现
下面是使用Python语言实现约瑟夫斯问题的算法:
```python def josephus(n, m): if n == 1: return 0 else: return (josephus(n-1, m) + m) % n
测试样例
n = 7 # 总人数 m = 3 # 报数到m时出列 survivor = josephus(n, m) print(。
数据结构实验报告一-约瑟夫环问题
实验1约瑟夫环问题1.需求分析(1)输入的形式和输入值的范围:每一次输入的值为两个正整数,中间用逗号隔开。
若分别设为n,m,则输入格式为:“n,m”。
不对非法输入做处理,即假设输入都是合法的。
(2)输出的形式:输出格式1:在字符界面上输出这n个数的输出序列输出格式2:将这n个数的输出序列写入到文件中(3)程序所能达到的功能:对于输入的约瑟夫环长度n和间隔m,输出约瑟夫环的出列顺序。
(4)测试数据:包括正确的输入及其输出结果和含有错误的输入及其输出结果。
正确:输入:10,3输出:3 6 9 2 7 1 8 5 10 4输入:41,3输出:3 6 9 12 15 18 21 24 27 30 33 36 39 1 5 10 14 19 23 28 32 37 41 7 13 20 2634 40 8 17 29 38 11 25 2 22 4 35 16 31错误:输入:10 3输出:6 8 7 1 3 4 2 9 5 102.概要设计(1)抽象数据类型的定义:为实现上述程序的功能,可以用整数存储用户的输入。
并将用户输入的值存储于线性表中。
线性表ADT定义如下:ADT list数据对象:整形数据关系:线性关系,即<ai,ai+1>(0≤a<n)。
基本操作:bool remove(int &elem)//移除一个元素,被移除的元素赋给elem//如果操作成功,返回true,否则返回falsebool isEmpty()//判断数组的元素是否清空,空返回true,否则返回falsebool setPos(int place)//设置当前元素的位置,设置成功返回true,否则返回falseint getLength()//获取数组的实际长度(2)算法的基本思想:约瑟夫环问题中的数据是人所在的位置,而这种数据是存在“第一元素、最后元素”,并且存在“唯一的前驱和后继的”,符合线性表的特点。
约瑟夫环问题的两种解法(循环链表和公式法)
约瑟夫环问题的两种解法(循环链表和公式法)问题描述这⾥是数据结构课堂上的描述: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),显然,这不是⼀个好的算法。
约瑟夫环问题(Josephus)
第一行为一个整数T(<2^15)表示测 试次数,接着第二到T+1行分别为n,m和k 的值。 例:2
10 2 3
输出格式:
T行最后min(n,3)个出列的编号。 结果:6 1 5
问题背景
• 这个问题是以弗拉维奥•约瑟夫斯命名的, 它是1世纪的一名犹太历史学家。他在自己 的日记中写道,他和他的40个战友被罗马 军队包围在洞中。他们讨论是自杀还是被 俘,最终决定自杀,并以抽签的方式决定 谁杀掉谁。约瑟夫斯和另外一个人是最后 两个留下的人。约瑟夫斯说服了那个人, 他们将向罗马军队投降,不再自杀。
(3)us jp=new Josephus(); int a[]=new int[n]; for(int i=0;i<n;i++){ a[i]=i+1; } jp.SortArray(a,n,m,k,g); }
public void show(int[]b,int g){ for(int i=b.length-g;i<b.length;i++){ System.out.print(b[i]+" ");
68 34 25 38 54 4 120 16 23 32 53 97
500 12 30 166 358 266
实验总结:
(1) 经过这次的实践,让我们明白了合作 的重要性。
(2)在程序设计初期,总会或多或少的出现 问题,经过我们的耐心调试,不断地修改, 慢慢地将程序设计较好的符合了要求。
(3)当然其中还是会存在一些漏洞,需要进 一步的改进。在计算机中是容不得丝毫的 错误的,这也让我们学到了面对科学要持 有严谨的态度,否则必定得不到应该有的 结果。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
一、实验题目:
约瑟夫环问题。
二、实验内容:
设编号为1,2,3,……,n的n(n>0)个人按顺时针方向围坐一圈,每个人持有一个正整数密码。
开始时任选一个正整数做为报数上限m,从第一个人开始顺时针方向自1起顺序报数,报到m是停止报数,报m 的人出列,将他的密码作为新的m值,从他的下一个人开始重新从1报数。
如此下去,直到所有人全部出列为止。
令n最大值取30。
要求设计一个程序模拟此过程,求出出列编号序列。
实现:用一个不带头结点的单向循环链表表示上述约瑟夫环,结点结构可定义为:
typedef struct node{ Array int pos;//位置
int code;//密码
struct node *next;
}JosephNode,* JosephList;;
三、程序源代码:
# include <stdio.h>
# include <stdlib.h>
# define ERROR 0
# define OK 1
Typedef struct node{
int pos,code;
struct node *next;
}JosephNode,* JosephList;
int InitList_Joseph(JosephList &h,int n)
//初始Joseph环。
//为什么就&h,而不是h??
{ JosephNode *newp,*p;
h=NULL;
for(int i=1;i<=n;i++)
{ newp=(JosephList)malloc(sizeof(JosephNode));
if(!newp)
return ERROR;
newp->pos=i;
printf("Please input the %dth one's code\n ",i);
scanf("%d",&newp->code);
if(h==NULL)
{h=newp;p=newp;}
else
{p->next=newp;
p=newp;
}
}
p->next=h;return OK;
}
int length(JosephList &h)
//求Joseph环的长度。
{ int len=0;JosephList ph=h;
if(h==NULL) return (len=0);
if(ph->next==ph) return (len=1);
while(ph->next !=h)
{len++;ph=ph->next;}
len++;
return len;
}
//////
void print(JosephList h)
//输出Joseph环。
{ JosephList ph=h;
system("CLS");
printf("The length of Joseph Circle is %d!\n",length(h));
if(ph==NULL){printf("This Joseph circle is empty!\n");return;}
do
{ printf("%dth one's code is %d\n",ph->pos,ph->code);
ph=ph->next;
}while(ph!=h);
}
//////
void Push_Joseph(JosephList h,int m)
//用递归方法求其出圈顺序。
{ JosephList p,first,pf;
static int cnt=0;
if(m==0){printf("\n\nCODE ERROR(0 inluded)\n\n");return;}//若code为0则出错,退出!
if(length(h)<=1)//当长度为1时,最后一个节点出圈。
结束函数。
{printf("\nThe last one is %d\n",h->pos); return;}
else
{ p=h;pf=p;
if(m==1)////若m==1则出圈的是其自己.
{ while(p->next!=h)
p=p->next; //找到其前驱。
printf("\nthe %dth one is %d",++cnt,h->pos);
p->next=h->next;
m=h->code;
free(h);
first=p->next;//将其后继当作头节点,
Push_Joseph(p,m);//递归调用Push_Joseph.
}
else//若m>1则,依次报数。
{for(int i=1;i<m;i++)//找到其后继。
{ pf=p;p=p->next;}
printf("\nThe %dth one is %d",++cnt,p->pos);//让其出圈。
pf->next=p->next;
m=p->code;
first=p->next; //将其后继当作头节点,
free(p);
Push_Joseph(first,m);//递归调用Push_Joseph.
}
}
}
void main()
{ int n,m;
JosephList h=NULL;
printf("Please input the number of people.\n");
scanf("%d",&n);
printf("h=%d\n",h);
if(!InitList_Joseph(h,n))//判断初始环是否成功。
{printf("Creating Joseph circle fails\n");exit(0);}
print(h);//输出Joseph圈。
printf("Please input the first number!\n");
scanf("%d",&m);
Push_Joseph(h,m);//出圈函数。
}
四、测试结果:
五、小结(包括收获、心得体会、存在的问题及解决问题的方法、建议等)
通过这次解决约瑟夫环问题的实验让我对数据结构这门课程有了一个初步的、简单的认识,通过自己的思考和与同学的讨论使我对数据结构中的链表,特别是环链表有了深刻的认识和理解,同时我还体会到了数据结构在编程中的重要性,加深了我对学习这门课程的兴趣;
在这次试验中我还是遇到了不少的问题,比如本次试验中的指针指向性的问题,在报数的时候应该把指针指向表尾,使得每次都能从第一个人开始报数,开始的时候我没有想到这些,结果老是不对,通过仔细的思考和与同学的讨论最终解决了这个问题;通过这次实验使我对数据结构产生了浓厚的兴趣,我发现自己还有许多要学习的自己还要继续努力。