数据结构与算法——joseph环
经典问题约瑟夫环讲解
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}该程序只有主程序。
约瑟夫环程序设计报告书
4:课程设计报告书附件《数据结构》课程设计报告环Joseph)约瑟夫(第七组别组组长成组员成绩教师指导计算机科学与技术系日11月6年2014.摘要约瑟夫环问题是典型的线性表的应用实例,其开发主要包括后台数据库的建立和维护以及前端应用程序的开发两个方面。
对于前者要求建立起数据一致性和完整性强、数据安全性好的库。
而对于后者则要求应用程序功能完备,易使用等特点。
经过分析,我们使用MICROSOFT公司的Microsoft Visual C++6.0开发工具,利用其提供的各种面向对象的开发工具,尤其是数据窗口这一能方便而简洁操作数据库的智能化对象,首先在短时间内建立系统原型,然后,对初始原型系统进行需求迭代,不断修正和改进,直到形成用户满意的可行系统。
关键词:单循环链表;c 语言;约瑟夫环;序言数据结构是研究数据元素之间的逻辑关系的一门课程,以及数据元素及其关系在计算机中的存储表示和对这些数据所施加的运算。
该课程设计的目的是通过课程设计的综合训练,培养分析和编程等实际动手能力,系统掌握数据结构这门课程的主要内容。
本次课程设计的内容是用单循环链表模拟约瑟夫环问题,循环链表是一种首尾相接链表,其特点是无须增加存储容量,仅对表的链接方式稍作改变,使表处理更加灵活,约瑟夫环问题就是用单循环链表处理的一个实际应用。
通过这个设计实例,了解单链表和单循环链表的相同与不同之处,进一步加深对链表结构类型及链表操作的理解。
通过该课程设计,能运用所学知识,能上机解决一些实际问题,了解并初步掌握设计、实现较大程序的完整过程,包括系统分析、编码设计、系统集成、以及调试分析,熟练掌握数据结构的选择、设计、实现以及操作方法,为进一步的应用开发打好基础。
章节安排1........................... 摘要、序言....一、问题描述1、课程设计目的.................. (4)2、课程设计任务.................. (4)二、设计过程1、设计思想(数据结构).................. . (4)2、设计表示(函数说明).................. . (5)3、详细设计(主要算法).................. . (6)4、用户手册(使用说明).................. . (6)三、测试报告1、测试用例.................. .... . (6)2、测试结果.................. .... . (6)3、分析探讨.................. .... . (7)四、总结................ .... . (10)五、附录(源程序)...... ...... ... ......... ..10六、参考文献......... .... ......... ... . (16)章节安排:一、问题描述1、课程设计目的1.掌握单向循环链表的建立。
C++数据结构之约瑟夫环
2009级数据结构实验报告实验名称:实验线性表实现约瑟夫问题求解学生姓名:桂柯易班级:2009211120班内序号:07学号:09210580日期:2010年10月31日1.实验要求【实验目的】1.熟悉C++语言的基本编程方法,掌握集成编译环境的调试方法;2.学习指针、模板类、异常处理的使用;3.掌握线性表的操作实现方法;4.培养使用线性表解决实际问题的能力。
【实验内容】利用循环链表实现约瑟夫问题的求解。
约瑟夫问题如下:已知n个人(n>=1)围坐一圆桌周围,从1开始顺序编号。
从序号为1的人开始报数,顺时针数到m的那个人出列。
他的下一个人又从1开始报数,数到m 的那个人又出列。
依此规则重复下去,直到所有人全部出列。
请问最后一个出列的人的编号。
2.程序分析2.1 存储结构存储结构:循环链表2.2 关键算法分析【设计思想】首先,设计实现约瑟夫环问题的存储结构。
由于约瑟夫环本身具有循环性质,考虑采用循环链表,为了统一对表中任意节点的操作,循环链表不带头结点。
循环链表的结点定义为如下结构类型:struct Node{int number;Node *next;};其次,建立一个不带头结点的循环链表并由头指针first指示。
最后,设计约瑟夫环问题的算法。
【伪代码】1、工作指针first,r,s,p,q初始化2、输入人数(n)和报数(m)3、循环n次,用尾插法创建链表Node *q;for(int i=1;i<=n;i++){Node *p;p=new Node;p->number=i;p->next=NULL;if(i==1) L=q=p;else{q->next=p;q=q->next;}}q->next=L;if(L!=NULL){return(L);}4、输入报数的起始人号数k;5、Node *q = new Node;计数器初始化i=1;6、循环n次删除结点并报出位置(其中第一个人后移k个)当i<n时移动指针m-2次p=p->next;删除p结点的后一结点qq=p->next;p->next=q->next;*L = p->next;报出位置后Delete q;计数器i++;【复杂度】for(int i=1;i<=n;i++){Node *p;p=new Node;p->number=i;p->next=NULL;if(i==1) L=q=p;else{q->next=p;q=q->next;}时间复杂度:O(n)if(i==1) i+=LengthList(*L);Node *p;p=*L;int j=0;while(j<i-2) {p=p->next;j++;}q = p->next;p->next=p->next->next;*L = p->next;return(q);时间复杂度:O(n2)算法的空间复杂度:O(n2)2.3 其他程序源代码:#include<iostream>using namespace std;struct Node//循环节点的定义{int number;//编号Node *next;};Node *CreateList(Node *L,int &n,int &m);//建立约瑟夫环函数void Joseph(Node *L,int n,int m);//输出每次出列号数函数Node *DeleteList(Node **L,int i,Node *q);//寻找每次出列人的号数int LengthList(Node *L);//计算环上所有人数函数void main()//主函数{Node *L;L=NULL;//初始化尾指针int n, m;cout<<"请输入人数N:";cin>>n;//环的长度if(n<1){cout<<"请输入正整数!";}//人数异常处理else{cout<<"请输入所报数M:";cin>>m;if(m<1){cout<<"请输入正整数!";}//号数异常处理else{L=CreateList(L,n,m);//重新给尾指针赋值Joseph(L,n,m);}}system("pause");}Node *CreateList(Node *L,int &n,int &m)//建立一个约瑟夫环(尾插法){Node *q;for(int i=1;i<=n;i++){Node *p;p=new Node;p->number=i;p->next=NULL;if(i==1) L=q=p;//工作指针的初始化else{q->next=p;q=q->next;}}q->next=L;if(L!=NULL){return(L);}//返回尾指针else cout<<"尾指针异常!"<<endl;//尾指针异常处理}void Joseph(Node *L,int n,int m)//输出每次出列的人{int k;cout<<"请输入第一个报数人:";cin>>k;if(k<1||k>n){cout<<"请输入1-"<<n<<"之间的数"<<endl;} else{cout<<"\n出列顺序:\n";for(int i=1;i<n;i++){Node *q = new Node;if(i==1) q=DeleteList(&L,k+m-1,q);//第一个出列人的号数else q=DeleteList(&L,m,q);cout<<"号数:"<<q->number<<endl;delete q;//释放出列人的存储空间}cout<<"最后一个出列号数是:"<<L->number<<endl;;//输出最后出列人的号数}}Node *DeleteList(Node **L,int i,Node *q) //寻找每次出列的人{if(i==1) i+=LengthList(*L);//顺序依次出列情况的处理方式Node *p;p=*L;int j=0;while(j<i-2) {p=p->next;j++;}q = p->next;p->next=p->next->next;*L = p->next;return(q);}int LengthList(Node *L)//计算环上的人数{if(L){cout<<"尾指针错误!"<<endl;}//异常处理else{int i=1;Node *p=L->next;while(p!=L){i++;p=p->next;}return(i);}}3.程序运行结果1.测试主函数流程:2.测试条件:如上图所示,人数为20人,所报数为6,第一个报数的人是1号。
工作报告之约瑟夫环实验报告总结
约瑟夫环实验报告总结【篇一:约瑟夫环实验报告】实验报告课程名称:数据结构实验名称:顺序表和链表的应用实验编号:实验一指导教师:一、实验目的(1)掌握线性表的基本操作(插入、删除、查找)以及线性表合并等运算在顺序存储结构、链式存储结构上的实现。
重点掌握链式存储结构实现的各种操作。
(2)掌握线性表的链式存储结构的应用。
二、实验内容与实验步骤(1)实验内容:实现约瑟夫环,约瑟夫环(joseph)问题的一种描述是:编号为1、2、3……n的n个人按照顺时针方向围坐一圈,每人持有一个密码(正整数)。
一开始任选一个正整数作为报数的上限值m,从第一个人开始按照顺时针的方向自1开始顺序报数,报到m时停止报数。
报m的人出列,将他的密码作为新的m值,从他的顺时针方向上的下一个人开始重新从1报数,如此下去,直至所有人全部出列为止。
设计一个程序求出出列顺序。
(2)抽象数据类型和设计的函数描述,说明解决设想。
首先定义一个链表,用其中的data项存储每个人的编号,用password项存储每个人所持有的密码,并且声明一个指针。
之后使用creatlist_cl函数来创建一个循环链表,在其中的data和password中存入编号和密码,最后使最后一个节点的next指向l,使其能够形成循环队列。
定义了函数display来显示链表当中的内容,以确定存储的数据没有错误。
定义了函数delete_l来实现约瑟夫环中依次删除的功能,依次比较,如果某个人所持的密码和m值相等,则删除这个结点,并且输出此时该结点的编号和密码,实现出列的功能。
(3)简短明确地写出实验所采用的存储结构,并加以说明。
该实验我主要采用的是线性表的链式存储结构,首先定义了链表的结构,其中包括data项和password项,分别存储每个人的编号和所持密码,还声明了指向下一个结点的指针,该指针可以连接各个结点,并且将最后一个结点的指针指向第一个结点使之成为一个循环链表。
三、实验环境操作系统:windows 7调试软件名称:vc++版本号:6.0上机地点:综合楼311四、实验过程与分析(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)
趣学算法——约瑟夫环问题(java版)
趣学算法——约瑟夫环问题(java版)1 什么是约瑟夫环问题?约瑟夫,是⼀个古犹太⼈,曾经在⼀次罗马叛乱中担任将军,后来战败,他和朋友及另外39个⼈躲在⼀⼝井⾥,但还是被发现了。
罗马⼈表⽰只要投降就不死,约瑟夫想投降,可是其他⼈坚决不同意。
怎么办呢,他想到⼀个主意:让41个⼈围成⼀个圆圈,从第⼀个⼈开始报数,数到3的那个⼈被旁边的⼈杀死。
这样就可以避免⾃杀了,因为犹太⼈的信仰是禁⽌⾃杀的。
结果⼀群⼈杀来杀去最后只剩下两个了,就是约瑟夫和他朋友,于是两⼈愉快地去投降了。
约瑟夫和朋友站在什么位置才保住了性命呢,这就是我们今天要讲的约瑟夫环问题。
2 问题的重要性这是个BAT常⽤⾯试题,⽽且本质上是⼀个游戏,可以⼴泛应⽤于⽣活中,⼯作⽣活好帮⼿就是它了。
3 约瑟夫环抽象问题这个问题实际在讲:N个⼈围成⼀圈,第⼀个⼈从1开始报数,报M的被杀掉,下⼀个⼈接着从1开始报,循环反复,直到剩下最后⼀个,那最后胜利者的初始位置在哪⾥?模拟流程:假如有5个⼈报数,报到3被杀,情况如下A B C1 D E (初始位置,C第⼀个被杀)D E A2 B (C死后的第⼆次排位,A第⼆个被杀)B D E3 (A死后的第三次排位,E第三个被杀)B4 D (E死后的第四次排位,B第四个被杀)D (D留在了最后,初始位置是4)解决⽅法:1 循环遍历法public static int josephus(int n, int m) {//n个⼈, 0 1 2..n-1int[] people = new int[n];//⼈的索引int index = -1;//报数记录, 1 2 3..mint count = 0;//剩余⼈数初始值为nint remain = n;//为了找到最后⼀个幸存者的位置,假设所有⼈都会被杀while (remain > 0) {index++; //找到报数的⼈if (index == n) { //所有⼈遍历⼀圈后从头遍历index = 0;}if (people[index] == -1) { //如果当前的⼈被杀跳过continue;}count++; //报数if (count == m) {people[index] = -1; //报数到m后杀⼈count = 0; //报数重置remain--; //剩余⼈数递减}}return index;}将41传⼊⽅法后,可得结果为30, 因为是从0开始计数,所以等价于现实世界的第31位。
数据结构约瑟夫环问题
数据结构实验报告题目:约瑟夫环问题一.设计内容[问题描述]约瑟夫环问题的一种描述是:编号为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。
《数据结构》上机实验报告—约瑟夫环问题
《数据结构》上机实验报告
专业和班级:信息计算科学与应用数学6班
学号
姓名
成绩
实验名称
线性表结构及其应用
实验内容
约瑟夫环问题
实
验
目
的
和
要
求
【实验目的】
利用单向循环链表解决约瑟夫环问题,提高综合设计能力。
【基本要求】
利用单向循环链表存储结构模拟此过程,按归口炪列的顺序印出各人的编号.
问
题
描
i=1;
while(i<=n)
{
printf(”请输入第%d个人的密码:”,i);
scanf("%d",&pwd);
if(pwd〈= 0)continue;
Insert(L,pwd, i);
i++;
}
i = 1;
p = L-〉next;
while(L->next!= L)
{
q = p;
p = p->next;
【结果截图】
研
究
与
探
讨
解决约瑟夫环问题有三个算法:
一个是在顺序表上实现,另一个是在单向循环链表上实现,第三个则是利用循环队列的方式来实现。
说明:实验名称为教学大纲中各章的实验项目名称,实验内容为具体章节的实验内容名称
述
和
主
要
步
骤
【问题描述】
约瑟夫问题:编号为1,2,。。n的n个人按顺时针方向围坐一圈,每人持有一个密码(正整数)。一开始任选一个正整数作为报数上限值m,从第一个人开始按顺时针方向自1开始顺序报数,报到m时停止报数。报m的人出列,将他的密码作为新的m值,从他在顺时针方向上的下一个人开始重新从1报数,如此下去,直至所有人全部出列为止.试设计一个程序求出出列顺序.
约瑟夫环——递推公式
0123456789 12345678910 4567891012 789101245 10124578 4578101
810145
45810
1045
104
4约瑟夫环——递推公式
递推公式:
f(N,M)=(f(N−1,M)+M)%N
f(N,M)表⽰,N个⼈报数,每报到M时杀掉那个⼈,最终
f(N−1,M)表⽰,N-1个⼈报数,每报到M时杀掉那个⼈,最终胜利者的编号
现在假设有10个⼈,报到3的⼈就会被杀掉,我们⽤数字给这⼗个⼈编号为
1 2 3 4 5 6 7 8 9 10
第·⼀⾏绿⾊那⾏是数组下标,第⼆⾏是每个⼈的编号
现在逆向推导
f(1,3):只剩最后⼀个⼈,胜利者的数组下标为0
f(2,3)=(f(1,3)+3)%2=1,只有两个⼈的时候,胜利者下标为1。
f(10,3)=3,因为我们数组下标是从0开始的,所以⼈的编号是下标+1,也就是4
那么这个公式是怎么推导的呢?
1.假设我们已经知道了10个⼈时,胜利者的下标为3,那下⼀次9个⼈时,胜利者的下标为多少?
其实就是10个⼈时杀掉了编号为3(即数组下标为2)的⼈后,后⾯的⼈都往前移动了3位,所以胜利者的下标由3变成了0
2.那我们倒过来我们知道9个⼈时,胜利者的下标为0,那10个⼈时胜利者的下标为多少?
其实这和上⾯的问题⼀样,这是这是上个问题的逆过程,就是把⼤家都往后移动3位,所以f(10,3)=f(9,3)+3,不过可能会出现数组越界所以要取模变成f(10,3)=(f(9,3)+3)%10
3.那么⼈数改为n报到m时就杀掉数组怎么移动呢
⼀样的,杀⼀⼈则后⾯的⼈的下标都往前移动m则,f(n,m)=(f(n-1,m)+m)%n。
约瑟夫环问题知识点总结
约瑟夫环问题知识点总结约瑟夫环问题的描述如下:有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就能得到原来的编号。
约瑟夫(Josephus)问题与二进制数
约瑟夫(Josephus )问题与二进制数(2013年8月24日整理)一些人乘船远行,忽然遭遇到大风浪,船舱漏了水,很快就可能沉没.船上只有一只救生圈,谁都想利用它逃生.在争执得不可开交之时,约瑟夫提出一个“公平”的办法:大家站成一个圈,从某人开始,1、2、1、2、1、2、… 地报数.凡报到2的人退出圈外——他失去了拥有救生圈逃生的机会,直到剩下一人.这人是“天意”赋予他生机,救生圈归他使用.依照这种“公平”的办法,约瑟夫从劫难中生还.试问他是站在什么位置上的呢?比如说,船上有11人,他该在什么位置?船上20人,他又该在什么位置?推而广之,船上有2n 人或2n +1人,又该如何呢?下面做点儿细致的研究.假设船上有n 个人,从开始报数的人起,依序编号为1、2、3、…、n ,幸存者所小的位置号记为()f n ;显然,(1)1f =、(2)1f =、(3)3f =.当有2n 个人,报数一圈后,淘汰掉2、4、6、…、2n 号位的人,剩下1、3、5、…、21n -号位的人共n 个,又从1号开始报数,这些人的第()f n 个是幸存者,但这个人在原先的位置上是第2()1f n -号,所以(2)()1f n f n =-;(比如:(4)2(2)12111f f =-=⨯-=,(6)2(3)15f f =-=). 当有21n +个人时,报数一圈后,淘汰掉2、4、6、…、2n 、1.号位的人,(这里为何去掉1号?此仍技巧一...),剩下3、5、7、…、21n +号位的人,共n 个;这时,从3开始报数,这些人中的第()f n 个是幸存者,但这个原先的位置是第2()1f n +,所以(21)()1f n f n +=+;(比如: (5)2(2)13f f =+=,(11)2(5)17f f =+=).令()()g n n f n =-,(这里为何这样变换?此仍技巧..二.). 由(*)得:(2)2()1(21)2(), (1)0g n g n g n g n g =+⎧⎨+==⎩(**).考虑二进制...表示,(这里为何考虑二进制数?此仍技巧..三.).对n 与()g n 都用二进制表示,因为二进制数有一些与十进制数类似的运算性质.比如,对任何自然数n ,10n 可以把n 的十进制表示的每位向左移动一位,然后在个位数上添一个0;101n +可以把n 的十进制表示的每一位左移一位,然后在个位数上添一个1.若用二进制数表示,则由(**)式可知(2)g n 是()g n 左移一位后在个位数上添1,而(21)g n +是()g n 左移一位后在个位上添0;从而,由(1)0g =可得()g n 是用二进制数表示n 的数中把0换成1,而把1换成0所得的数.这一结论可用第二.. (1)当1n =时,结论显然成立;(2)假设n k ≤(*)k N ∈时,结论成立,即对任一不大于k 的正整数122()m p a a a = ,其中有{0, 1}i a ∈,1, 2, , i m = ,都有122()()m g p a a a = ,这里1i i a a +=,即当1i a =,则0i a =,当0i a =,则1i a =;当1n k =+时,分两种情形:①若k 是奇数,即21k p =-,则1222(0)m n p a a a == , 1212222()(2)2()1(0)(1)(1)m m g n g p g p a a a a a a ==+=+= ;②若k 是偶数,即2k p =,则122212221(0)(1)(1)m m n p a a a a a a =+=+= , 122()(21)2()(0)m g n g p g p a a a =+== ;这说明,当1n k =+时,结论也成立;根据(1)和(2 下面继续探究()f n 的表达式.根据上述分析可知:若n 是k 位的二进制数,即122k k n -≤<,(***),则()n g n +是每位均为1的k 位二进制数,即1()12221k k n g n -+=+++=- ,故()()221k f n n g n n =-=-+;而由(***)式,可得:21log k n k -≤<,所以21[log ]k n -=,(这就是要求的通项公式)从这个问题可见二进制的作用不可小视之.下面再举一例,注意分析过程. 题目:对怎样的正整数n ,方程[][2][4][8]x x x x n +++=(☆)中的x 有解? 解答:设[]x x r =+,把r 用二进制数表示:312123223(0. )222a a a r a a a ==+++ ,{0, 1}i a ∈, 则有1[2]2[]x x a =+;12212[4]4[]()4[]2x x a a x a a =+=++;1232123[8]8[]()8[]42x x a a a x a a a =+=+++; 所以123[][2][4][8]15[]73x x x x x a a a +++=+++;令12373t a a a =++,因为1a 、2a 、3a {0, 1}∈,所以t 只能取到0、1、3、4、7、8、10、11这8个值,从而当且仅当n 被15除余数为0、1、3、4、7、8、10或11时,方程(☆)才有解.比如,13579n =时有解,因为135********=⨯+,从而905.375905.5x ≤<,所以4t =,即231a a ==,[]905x =,于是有44211905(0.011)9054816ax a =+=++++ ,有无穷多个解.但是,98765t=,不适合.n=时无解,这是因为987651565845=⨯+,即5。
约瑟夫问题(算法与数据结构课设报告)
线性表的操作及其应用一、问题描述线性表、队列是一种常用的数据结构,有顺序和链式两种存储结构,在实际中应用十分广泛,而链表又分为单链表和循环链表,队列又分为链式队列和循环队列。
这些数据结构都可用来解决约瑟夫环问题。
约瑟夫环问题是算法设计中的一个经典问题,是顺序编号的一组n个人围坐一圈,从第1个人按一定方向顺序报数,在报到m时该人出列,然后按相同方法继续报数,直到所有人出列。
设计算法求约瑟夫环中人员的出列顺序。
二、基本要求1、选择合适的存储结构,建立线性表;2、利用顺序存储结构求解约瑟夫环问题;3、利用单链表和循环链表分别求解约瑟夫环问题;4、利用队列求解约瑟夫环问题。
三、测试数据约瑟夫环的测试数据为7,报数为1至3。
四、算法思想由于用到四种不同的存储结构,它们的算法思想依次是:1、首先建立一个顺序表模拟整个约瑟夫环,手动输入顺序表长(即参加约瑟夫循环的人数)和循环的次数和表元素。
用已经输出总人数和顺序表长作比较,作为外层循环条件。
并对每一个输出后的元素重新赋值以为标记。
对于每次循环,首先检查顺序表此次是不是我们设立的标记,如果不是则循环次数加1,当达到要求的循环次数时就将循环次数设置为0,输出该元素到屏幕并将总输出元素加1。
每次外循环我们都会移到表的下一个位置,作为新的判断条件,每次报到表尾的时候,我们都将重新设置到表尾,作为下次循环的表元素。
2、首先采用链式循环链表建立整个约瑟夫环,手动输入第一次的循环次数和每个人所持下一个循环次数。
设立判断指针指向表头,并将该指针是否为空作为外层循环条件。
做一个内层循环,将判断指针移动到循环要输出的数,并设立一个前指针指向该指针的前一个位置,输出该元素后,将循环次数重新赋值成该元素。
接着判断前指针和判断指针比较,如果相等说明整个表已经输出完毕,否则将删除该位置的元素。
3、用链式队列建立循环约瑟夫环,手动输入人数,第一次的循环次数和每个人所持下一个循环次数。
并将每一个元素依次入队列,根据第一次循环次数,建立一个for循环,每一次循环都出队列,如果达到要求的循环次数就输出,否则进队列,这样这个数字就出现在队尾。
数据结构——约瑟夫环
数据结构——约瑟夫环今⽇⼀⾔:谢谢你,成为我前进的理由。
——《⾔叶之庭》数据结构 —— 约瑟夫环这是⽤链表实现的,约瑟夫环的规则是:总数为N的同学围成⼀个圆环,并将这些同学从1开始编号,游戏开始时,约定好⼀个数字K,从1号同学开始轮着叫号,当叫到K号时,该同学淘汰,下⼀位同学从1开始重新叫号,只要叫到K号即淘汰,留下来的最后⼀位同学赢得游戏。
C语⾔实现/*********************************************************************************** 约瑟夫环* create: 2020年5⽉24⽇ 22点22分* author: LOS(⼩鱼)** *******************************************************************************/#include<stdio.h>#include<stdlib.h>/********************************************************************************** 链表需要********************************************************************************/typedef struct Elem{int value;struct Elem *prev,*next;}Elem;struct{Elem elems[200];int size;} Elems; // 全部的链表数据存储在这⾥(不销毁, 但会覆盖)// 定义链表结构typedef struct {struct Elem *first;struct Elem *last;int size;} LinkList;// 初始化链表void initLinkList( LinkList *list ){list->size = 0;}// 获取表长int getSizeL(LinkList *list){return list->size;}void addFirst(LinkList *list,Elem *elem){if( !getSizeL(list) ){list->first = elem;list->last = elem;}else {elem->next = list->first;list->last->next = elem;list->first->prev = elem;list->first = elem;}list->size++;}// 添加元素void addLast(LinkList *list,Elem *elem){if( !getSizeL(list) ){list->first = elem;list->last = elem;} else {elem->prev = list->last;elem->next = list->first;list->last->next = elem;list->first->prev = elem;list->last = elem;}list->size++;}Elem * getElem(LinkList *list, int index){int i ;Elem *elem;// 逐项访问if ( index > list->size/2 ){elem = list->last;for ( i = list->size-1 ; i >= index ; i-- ){if( i == index ){return elem;}elem = elem->prev;}} else {elem = list->first;for ( i = 0 ; i <= index ; i++ ){if( i == index ){return elem;}elem = elem->next;}}}// 移除元素void removeIndexL(LinkList *list, int index){ int i;Elem *elem = list->first;int flag = 0;for ( i = 0 ; i <= index ; i++ ){if( i == index ){elem->prev->next = elem->next;elem->next->prev = elem->prev;if( list->first == elem ){list->first = elem->next;}if( list->last == elem ){list->last = elem->prev;}flag = 1;}elem = elem->next;}if(!flag) printf("没能完成任务\n"); list->size--;}void removeElemL(LinkList *list, Elem *e){ int i;Elem *elem = list->first;int flag = 0;for ( i = 0 ; i < list->size ; i++ ){if( elem == e ){elem->prev->next = elem->next;elem->next->prev = elem->prev;list->first = elem->next;}if( list->last == elem ){list->last = elem->prev;}flag = 1;}elem = elem->next;}if(!flag) printf("没能完成任务\n");list->size--;}/********************************************************************************** 链表需要********************************************************************************/Elem *createElem( int value ){Elem *p = Elems.elems+Elems.size;p->value = value;Elems.size++;if( Elems.size == 200 ) Elems.size = 0; // 注意不能超过200,否则数据错误return p;}void main(){LinkList list;int n,k,i,count;printf("请输⼊参与游戏的总⼈数:");scanf("%d",&n);fflush(stdin);printf("请输⼊淘汰的间隔数:");scanf("%d",&k);fflush(stdin);initLinkList(&list);for ( i = 0 ; i < n ; i++ ){Elem *elem = createElem(i+1);addLast(&list,elem);}printf("游戏开始......\n");count = 0;while(list.size>1){count = (count+k-1)%list.size;printf("%d号同学被淘汰.\n",getElem(&list,count)->value);removeIndexL(&list,count);}printf("最后的赢家是 --> %d号同学!\n",st->value);}运⾏结果请输⼊参与游戏的总⼈数:10请输⼊淘汰的间隔数:3游戏开始......3号同学被淘汰.6号同学被淘汰.9号同学被淘汰.2号同学被淘汰.7号同学被淘汰.1号同学被淘汰.8号同学被淘汰.5号同学被淘汰.10号同学被淘汰.最后的赢家是--> 4号同学!--------------------------------Process exited after 2.205 seconds with return value 27请按任意键继续. . .。
约瑟夫环问题 实验报告完整版
{
int data;//数据域
Node *next;//next指针指向下一个结点
};
3.算法设计
问题要求建立模型,确定存储结构,之后对任意n个人,密码为m,实现约瑟夫环问题,出圈的顺序可以依次输出,也可以用一个数组存储。
设计流程图如图1.1所示。
图1.1设计流程图
(1)创建循环链表
{
p=p->next;
}
q=p->next;
p->next=q->next;
p=p->next;
printf("第%3d个出圈的人是:%3d\n",i,q->value);
free(q);
}
scanf("\n");
p->next=NULL;
}
(3)主程序执行
主程序运行,调用函数,程序接受数据后,输出出圈列数。
}
(2)约瑟夫环报数的算法在运行为循环方式,报数者除非本身已经出去,否则继续顺序报数,其报数循环的代码为
void Joseph(NODE *p,int number,int n)
{
int i,j;
NODE *q=NULL;
for(i=1; i<=number; i++)
{
for(j=1; j<n-1; j++)
由于内容的要求以及问题的方便,用循环链表作为本次实验的抽象数据类型。申请一个结点作为第一个结点,之后调用creat_list函数将后续结点一次插入链接,构造为循环链表。
NODE *link(int number)
{
NODE *head=NULL,*p=NULL,*q=NULL;
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)
目录课题一 joseph环 41.1 问题的提出1.1.问题的提出41.2 概要设计2.1算法思想51.3流程图根据算法思想,画程序流程图如下:661.4 源代码1.3.详细设计 7 输入m 、nm>0且n>0的整数建立含n 个结点的链表且用head 指向第一个元素,结点数据域包含password 、No 、以及指向下一结点的指head=>pn ≥2(m%n)==0?n:m%n=>1=>i i<mp →next=>pi++输出p →Nop →password=>m删除p 所指向结点n--输出p →No结束开始1.5 结果与分析.4 测试及性能分析10课题二拓扑排序 112.1 问题的提出2.1 问题的提出112. 2 概要设计112.3 流程图2.根据算法思想,画流程图如下:1212 开始设辅助数组indegree 记录图的各顶点的入度值,并将indegree 数组各变量赋初值。
输入图的顶点数、边数建立一个栈,存储图的顶点的序号用邻接表法建图,并计算出indegree 数组中各变量值根据indegree 数组将入度为0的顶点入栈count 对输出顶点计数0=>count栈不空删除栈顶元素,赋给i count++将与第i 个顶点链接的各顶点入度减1输出第i 个顶点值 顶点入度为0 顶点序号入栈count<G.vexnum输出“拓扑排序成功” 输出“拓扑排序不成功” 结束2.4 源代码132.5 结果与分析2.4 测试及性能分析17课题三纸牌游戏 193.1 问题的提出.1 问题的提出193. 2 概要设计191.当每个号码每次遇到是某个数的倍数时,都会相应的翻一次,这样,每张牌会翻的次数就各不一样,可能很多次,也可能只有一两次,结果就只是要输出在经过各个不同次数的翻牌后,正面向上的牌都有哪几个。
举例说明一下,比如24,第一次它是2的倍数时要从正面翻到背面,当进行到3时,就又要从背面翻回来,而到4时还要在翻,同理呢,到6.8.12…它都要来回的翻。
约瑟夫环问题的两种解法(循环链表和公式法)
约瑟夫环问题的两种解法(循环链表和公式法)问题描述这⾥是数据结构课堂上的描述: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)。
数据结构与算法课程设计joseph环152208100066张小莲目录需求分析-------------------------------------------------------03算法分析-------------------------------------------------------04 该单循环链表的逻辑结构------------------------------------------04 删除出列的结点-------------------------------------------------04 判断是否所有人全部出列------------------------------------------04算法设计-------------------------------------------------------05 PersonList结构体-----------------------------------------------05 CreateList函数-------------------------------------------------05 Exports函数---------------------------------------------------05完整代码-------------------------------------------------------07 结果说明-------------------------------------------------------09 总结------------------------------------------------------------10编号是1,2,……,n的n个人按照顺时针方向围坐一圈,每个人只有一个密码(正整数)。
一开始任选一个正整数作为报数上限值m,从第一个人开始顺时针方向自1开始顺序报数,报到m时停止报数。
报m的人出列,将他的密码作为新的m值,从他在顺时针方向的下一个人开始重新从1报数,如此下去,直到所有人全部出列为止。
设计一个程序来求出出列顺序。
1. 该单循环链表的逻辑结构由于题目要求“从第一个人开始顺时针方向自1开始顺序报数,报到m时停止报数。
报m的人出列,将他的密码作为新的m值,从他在顺时针方向的下一个人开始重新从1报数,如此下去”,队伍中编号最大的人报数完毕后编号最小的人应紧接着报数,所以创建链表时链表中最后一个结点的next指针直接指向首结点而不是头结点比较合适。
该单循环链表的逻辑结构如下:2. 删除出列的结点某结点出列后,应将该结点从单循环链表里删除,则需要找到该结点的前一个结点,并由p指向它,通过修改指针域使结点p的直接后继为s的直接后继,即p->next=s->next。
当m=1时,即将出列的结点的前一个结点就是还未进行移动的当前的s指针指向的结点。
当要删除的结点恰好为head指向的结点时,删除前应修改head指向的结点为s->next。
3.判断是否所有人全部出列由于结点出列后的删除操作,整个单循环链表的表长是逐渐缩短的,直至单链表只剩下头结点和首结点:此时已经无法在进行删除结点操作,因为该结点的直接后继就是自己。
但实际上,当只剩下一个人时无论m为何值此人直接出列就可以了。
也就是说,当链表里只剩下最后一个结点时已经不必进行删除操作,直接输出该结点的编号即可。
于是问题就转化成如何判断链表里只剩最后一个结点,判断head->next是否与head相等可以解决问题。
1.PersonList结构体每个结点包含编号,密码以及指向下一个结点的指针域三个数据。
typedef struct node{int password; //密码int number; //编号struct node *next; //指针域} PersonList;2.CreateList函数该函数的功能是建立单循环链表,并从键盘读入每个编号的密码,入口参数n为单循环链表所包含的结点的个数,函数返回一个PersonList类型的指针。
PersonList *CreateList(int n){PersonList *head,*s,*r;head=(PersonList *) malloc(sizeof(PersonList));head->next=NULL;head->number=1;printf("请输入编号为1的人的密码:");scanf("%d",&head->password); //从键盘中读取编号为1的人的密码getchar(); //接收回车r=head;for(int i=2;i<=n;i++){s=(PersonList *) malloc(sizeof(PersonList));s->next=NULL;s->number=i;printf("请输入编号为%d的人的密码:",i);scanf("%d",&s->password); //从键盘读入每个编号的密码getchar(); //接收回车r->next=s; //插入新结点r=s; //将尾指针指向新结点}r->next=head; //将最后一个结点的指针域指向首结点return head;}3.Exports函数该函数的功能是模拟出列的过程,并按照出列的顺序输出每个人的编号,入口参数head 为单循环链表,入口参数m为初始报数上限值。
void Exports(PersonList *head,int m){PersonList *s,*p;s=head;m=m-1;printf("出列顺序为:");while(head!=head->next) //当链表里有一个以上的结点时{if(m!=1) //若m=1可跳过for循环for(int i=1;i<=m-1;i++) //报数直到第m-1个结点s=s->next;p=s; //p指向第m-1个结点s=s->next; //s指向第m个结点printf("%4d",s->number); //输出第m个结点的编号m=s->password; //将第m个结点的密码作为新的m值if(s==head) //若要删除的结点恰好是首结点,移动head指针head=s->next;p->next=s->next; //删除第m个结点s=p; //s指向报数1结点的前一个结点,为下一次报数做准备}printf("%4d",s->number); //输出链表里剩下的唯一一个结点}完整代码#include <stdio.h>#include <stdlib.h>typedef struct node{int password;int number;struct node *next;} PersonList;PersonList *CreateList(int n){PersonList *head,*s,*r;head=(PersonList *) malloc(sizeof(PersonList));head->next=NULL;head->number=1;printf("请输入编号为1的人的密码:");scanf("%d",&head->password);getchar();r=head;for(int i=2;i<=n;i++){s=(PersonList *) malloc(sizeof(PersonList));s->next=NULL;s->number=i;printf("请输入编号为%d的人的密码:",i);scanf("%d",&s->password);getchar();r->next=s;r=s;}r->next=head;return head;}void Exports(PersonList *head,int m){PersonList *s,*p;s=head;m--;printf("出列顺序为:");while(head->next!=head){if(m!=1)for(int i=1;i<=m-1;i++)s=s->next;p=s;s=s->next;printf("%4d",s->number);m=s->password;if(s==head)head=s->next;p->next=s->next;s=p;}printf("%4d",s->number);}void main(){int m,n;printf("请输入报数上限值:");scanf("%d",&m);printf("请输入参加报数的人数:");scanf("%d",&n);PersonList *q=CreateList(n);Exports(q,m);printf("\n");}结果说明第一轮报数m=7 编号为7的人出列第二轮报数m=4 编号为1的人出列第三轮报数m=5 编号为6的人出列第四轮报数m=4 编号为2的人出列第五轮报数m=2 编号为4的人出列第六轮报数m=1 编号为5的人出列第七轮报数m=9 编号为8的人出列第八轮报数m=7 编号为9的人出列第九轮报数m=8 编号为3的人出列第十轮报数m=3 编号为10的人出列总结课本里提供了单循环链表的置空,建立,求表长,取结点,定位,插入,删除等操作代码,理解每个方法的原理便于面对实际问题时化繁为简灵活运用。