约瑟夫环问题

合集下载

实验一约瑟夫环问题实验报告

实验一约瑟夫环问题实验报告

题目二约瑟夫环问题设编号为1,2,3,……,n 的n(n>0)个人按顺时针方向围坐一圈,每个人持有一个正整数密码。

开始时任选一个正整数做为报数上限m ,从第一个人开始顺时针方向自1起顺序报数,起顺序报数,报到报到m 时停止报数,时停止报数,报报m 的人出列,的人出列,将他的密码作为将他的密码作为新的m 值,从他的下一个人开始重新从1报数。

报数。

如此下去,如此下去,如此下去,直到所有人全部出列直到所有人全部出列为止。

令n 最大值取30。

要求设计一个程序模拟此过程,求出出列编号序列。

struct node //结点结构{ int number; /* 人的序号人的序号*/ int cipher; /* 密码密码*/ struct node *next; /* 指向下一个节点的指针*/ }; 一、循环链表的结点类型定义/* 单链表的结点类型 */typedefstruct node{int number;int cipher;struct node *next;}list, *linklist;二、循环链表的初始化/* 函数功能:初始化n 个元素的循环链表参数;链表(linklist L),元素个数(int n )通过后插法对无头结点的链表初始化。

*/voidinit(linklist&L,int n){int key, i;cout<<"输入第1个人的密码为:";//输入第一个节点的密码。

cin>>key;L= new list;L->number = 1;L->cipher = key;L->next = L;for(i = 2; i<= n; i ++)//输入2—n 的节点密码。

{linklist p = new list;cout<<"输入第"<<i<<"个人的密码为:";cin>>key;p->cipher = key;p->number = i;p->next = L->next; //使用后插法插入。

约瑟夫环的知识点总结

约瑟夫环的知识点总结

约瑟夫环的知识点总结约瑟夫环这个问题不仅在古代受到了广泛的关注,而且在现代数学中也有着重要的地位。

它涉及到了排列、递推、循环和递归等多个数学概念,并且有着一些有趣的数学特性。

因此,学习约瑟夫环不仅能够增加我们对于数学问题的理解,而且也可以提高我们的数学思维能力。

接下来,我们将从几个方面对约瑟夫环进行深入的讨论。

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}该程序只有主程序。

约瑟夫环问题小结

约瑟夫环问题小结

约瑟夫环问题⼩结⼀问题描述约瑟夫环问题的基本描述如下:已知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⼈,圆桌上只剩下⼀个⼈了,停⽌循环。

数据结构约瑟夫环问题

数据结构约瑟夫环问题

数据结构实验报告题目:约瑟夫环问题一.设计内容[问题描述]约瑟夫环问题的一种描述是:编号为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。

约瑟夫问题

约瑟夫问题




第一个人出列:从 数器 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位幸存者。

约瑟夫环问题知识点总结

约瑟夫环问题知识点总结

约瑟夫环问题知识点总结约瑟夫环问题的描述如下:有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)

约瑟夫环问题(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就能得到原来的编号。

约瑟夫环问题

约瑟夫环问题

约瑟夫环问题一、前言约瑟夫环(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号总在剩余的环中。

Josephu约瑟夫问题java实现(环形链表)

Josephu约瑟夫问题java实现(环形链表)

Josephu约瑟夫问题java实现(环形链表)5.4.1约瑟夫问题Josephu(约瑟夫、约瑟夫环) 问题为:设编号为 1,2,… n 的 n 个⼈围坐⼀圈,约定编号为 k(1<=k<=n)的⼈从 1 开始报数,数 到 m 的那个⼈出列,它的下⼀位⼜从 1 开始报数,数到 m 的那个⼈⼜出列,依次类推,直到所有⼈出列为⽌,由 此产⽣⼀个出队编号的序列。

5.4.2解决思路⽤⼀个不带头结点的循环链表来处理 Josephu 问题:先构成⼀个有 n 个结点的单循环链表,然后由 k 结点起从 1 开 始计数,计到 m 时,对应结点从链表中删除,然后再从被删除结点的下⼀个结点⼜从 1 开始计数,直到最后⼀个 尚硅⾕ Java 数据结构和算法 更多 Java –⼤数据 –前端 –python ⼈⼯智能 -区块链资料下载,可访问百度:尚硅⾕官⽹ 第 55页 结点从链表中删除算法结束。

代码实现//约瑟夫问题-环形链表public class Josepfu {public static void main(String[] args) {CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();circleSingleLinkedList.addBoy(5);// 加⼊ 5 个⼩孩节点circleSingleLinkedList.showBoy();circleSingleLinkedList.countBoy(1, 2, 5);}}//环形链表class CircleSingleLinkedList{//指向链表的第⼀个节点private Boy first = null;//添加num个⼩孩节点public void addBoy(int num){if (num<1)throw new RuntimeException("输⼊值错误");Boy curBoy = null;for(int i=1;i<=num;i++){Boy boy = new Boy(i);if (i==1){first = boy;first.setNext(first);//形成环curBoy = first;}else{boy.setNext(first);curBoy.setNext(boy);curBoy = boy;}}}// 根据⽤户的输⼊,计算出⼩孩出圈的顺序/**** @param startNo* 表⽰从第⼏个⼩孩开始数数* @param countNum* 表⽰数⼏下* @param nums* 表⽰最初有多少⼩孩在圈中*/public void countBoy(int startNo, int countNum, int nums) {if (nums<1||countNum<1||first==null||startNo<1||startNo>nums)throw new RuntimeException("参数有误,从新输⼊!!");//创建辅助指针,指向环形链表的最后⼀个节点Boy helper = first;while (helper.getNext()!=first){helper = helper.getNext();}//移动helper和first,使从第startNo个⼩孩开始数for (int i=0;i<(startNo-1);i++){helper = helper.getNext();first = first.getNext();}//开始数数,出圈while (helper!=first){//报数for (int i=0;i<(countNum-1);i++){helper = helper.getNext();first = first.getNext();}System.out.println("⼩孩"+ first.getNo() +"出队列:" );first = first.getNext();helper.setNext(first);}System.out.println("最后的⼩孩:"+ first.getNo());}//遍历环形链表public void showBoy(){if (first==null)throw new RuntimeException("链表为空");System.out.println("⼩孩的编号: "+first.getNo());//first⽆法移动,创建中介节点遍历链表Boy curBoy = first.getNext();//当中介节点再⼀次回到first时,表⽰链表遍历完成while (curBoy!=first){System.out.println("⼩孩的编号: "+curBoy.getNo()); curBoy = curBoy.getNext();}}}//创建boy类表⽰⼀个节点class Boy{private int no;private Boy next;public Boy(int no) {this.no = no;}public int getNo() {return no;}public void setNo(int no) {this.no = no;}public Boy getNext() {return next;}public void setNext(Boy next) {this.next = next;}}。

Josephus环问题

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越⼤时⽅法⼀的时间效率会更差。

数据结构实验报告一-约瑟夫环问题

数据结构实验报告一-约瑟夫环问题

实验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)

约瑟夫环问题(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. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

实验报告
1.问题描述
设有编号为1,2,…,n的n(n>0)个人围成一个圈,每个人持有一个密码m。

从第一个人开始报数,报到m时停止报数,报m的人出圈,再从他的下一个人起重新报数,报到m时停止报数,报m的出圈,……,如此下去,直到所有人全部出圈为止。

当任意给定n和m后,设计算法求n个人出圈的次序。

2.设计思想
首先,设计实现约瑟夫环问题的存储结构。

由于约瑟夫环问题本身具有循环性质,考虑采用循环链表,为了统一对表中任意结点的操作,循环链表不带头结点。

将循环链表的结点定义为如下结构类型:
struct Node
{
int data;
Node *next;
};
其次,建立一个不带头结点的循环链表并由头指针first指示。

最后,设计约瑟夫环问题的算法。

3. 基本要求
●建立模型,确定存储结构。

●对任意n个人,密码为m,实现约瑟夫环问题。

●出圈的顺序可以依次输出,也可以用一个数组存储。

4.算法设计
1.系统里面自己定义了一个fun1函数,用来执行创建循环列表的功能。

YUE * fun1(int n)
{ YUE *head,*p1,*p2;
int i;
for(i=1;i<=n;i++)
{ p1=(YUE *)malloc(S());
if(i==1) head=p1;
else p2->next=p1;
p1->n=i;
p2=p1;
}
p1->next=head;
return head;
}
2.系统里面另外有定义了一个fun2的函数,用来执行满足条件出队列的功能。

int fun2(YUE *p)
{ YUE *p2;
int n;
p2=p->next;
n=p2->n;
p->next=p2->next;
free(p2);
return n;
}
3.在程序的开端,首先定义了一个链表结构。

typedef struct yuesefu
{ int n;
struct yuesefu *next;
} YUE;
4.在程序的中间部分,是程序的主函数,用来判断条件的满足与否。

YUE * fun1(int n);
int fun2(YUE *p);
int main()
{ int m,i,n,j,s;
YUE *p,*p2;
printf("n=");
scanf("%d",&n);
printf("m=");
scanf("%d",&m);
printf("i=");
scanf("%d",&i);
p=fun1(n);
for(j=1;j<i;p=p->next,j++);
j=1;p2=p;
while((p->next)!=p)
{ p=p->next;
if(++j==m)
{ j=1;p=p->next;
s=fun2(p2);
printf("%d ",s);
}
p2=p;
}
printf("%d\n",p->n);
system("pause");
}
5. 运行、测试与分析
先输入在队列中总人数n
再输入密钥m值
最后输入从第i个人开始报数
4.思考题
(1)采用顺序存储结构如何实现约瑟夫环问题?
创建一个具有n个元素的顺序表对象list。

从第s个元素开始,依次计数,每数到d,就将对应元素删除。

重复计数并删除元素,直到剩下一个元素。

如果每个人持有的密码不同,应如何实现约瑟夫环问题?
把每个人的密码定义出来,如果报数与其密码相等,出列。

附录:(代码)
#include <stdlib.h>
#include <stdio.h>
#define S() sizeof(struct yuesefu)
typedef struct yuesefu
{ int n;
struct yuesefu *next;
} YUE; /*定义链表*/ YUE * fun1(int n);
int fun2(YUE *p);
int main()
{ int m,i,n,j,s;
YUE *p,*p2;
printf("n=");
scanf("%d",&n);
printf("m=");
scanf("%d",&m);
printf("i=");
scanf("%d",&i);
p=fun1(n);
for(j=1;j<i;p=p->next,j++);
j=1;p2=p;
while((p->next)!=p)
{ p=p->next;
if(++j==m)
{ j=1;p=p->next;
s=fun2(p2);
printf("%d ",s);
}
p2=p;
}
printf("%d\n",p->n);
system("pause");
}
YUE * fun1(int n)
{ YUE *head,*p1,*p2;
int i;
for(i=1;i<=n;i++)
{ p1=(YUE *)malloc(S());
if(i==1) head=p1;
else p2->next=p1;
p1->n=i;
p2=p1;
}
p1->next=head;
return head;
} /*创建循环链表*/ int fun2(YUE *p)
{ YUE *p2;
int n;
p2=p->next;
n=p2->n;
p->next=p2->next;
free(p2);
return n;
} /*出队列函数*/。

相关文档
最新文档