1、1数据结构课 程 设 计 报 告设计课题: 约瑟夫问题 院 系: 计算机科学与技术学院 专业班级: 计算机网络技术 1102 班 学生姓名: 张 利 学 号: 1 1 0 8 0 4 0 2 1 1 指导教师: 王 昱 哲 2目 录1.需求分析 .31.1 问题描述 .31.2 功能分析 .42.概要设计 .53.详细设计 .64.调试与操作说明 15总 结 163一需求分析1.1 问题描述约瑟夫环问题描述的是:设编号为 1,2,n 的 n(n0)个人按顺时针方向围坐一圈,每个人持有一正整数密码。开始时选择一个正整数作为报数上限 m,从第一个人开始顺时针方向自 1 起顺序报数,报到 m 时停止
2、报数,报 m的人出圈,将他的密码作为新的 m 值,从他在顺时针方向上的下一个人起重新从 1 报数。如此下去,直到所有人都出圈为止。令 n 最大值为 100。要求设计一个程序模拟此过程,求出出圈的编号序列。如下图分析:1 2 34567890这是第一个人,他的密码是“1” ,个他输一个 m 值,如果m=3,则从他开始向下走 3 个这就是第二步的位置,这时他的密码作为新的 m 值,即 m=4,同时得到的第一个密码为4;4 号出去向下走4,到 9 这儿;(这这一步完了剩余的为:1,2,3,5,6, ,7,8,9,0, )这就是第三步的位置,这时他的密码作为新的m 值,即 m=9,同时得到的第二个密码
3、为 9;9号出去向下走 9,到 0这儿;继续走就行了(这儿剩余的就是:1,2,3,5,6,7,8,0)图 1 约瑟夫环问图解43 271 4 8 4约瑟夫环原理演示图1 2 3 4 5 6 7第二部:第一次停下的位置,此时 6 号出列,并将他的值作为新的 m 值,即:新的 m=8;从 7 好开始继续向下走 8 次,到 1 号的位置最后排序后的密码序列:(本图只演示前两步)8第三步:第二次,1 号出列第四步:第三次,4 号出列3第一步:给第一个人赋初始密码为:20 则从它开始向下走 20次,到 6 号位置2 4 1 7 46 1 4 7 2 3 5图 2 约瑟夫环原理演示图1.2 功能分析约瑟夫
4、环问题是一个古老的数学问题,本次课题要求用程序语言的方式解决数学问题。此问题仅使用单循环链表就可以解决此问题。而改进的约瑟夫问题通过运用双向循环链表,同样也能方便地解决。在建立双向循环链表时,因为约瑟夫环的大小由输入决定。为方便操作,我们将每个结点的数据域的值定为生成结点时的顺序号和每个人持有的密码。进行操作时,用一个指针 current 指向当前的结点,指针 front 始终指向头结点。然后建立双向循环链表,因为每个人的密码是通过 rand()函数随机生成的,所以指定第一个人的顺序号,找到结点,不断地从链表中删除链结点,直到链表剩下最后一个结点,通过一系列的循环就可以解决改进约瑟夫环问题。5
5、2、概要设计1、循环链表抽象数据类型定义typedef struct LNode/定义单循环链表中节点的结构 int num;/编号 int pwd;/passwordstruct LNode *next;/指向下一结点的指针LNode;2、本程序包含一下几个模块(1)构造结点模块LNode *createNode(int m_num,int m_pwd)LNode *p;p=(LNode *)malloc(sizeof(LNode);/生成一个结点 p-num=m_num;/把实参赋给相应的数据域p-pwd=m_pwd;p-next=NULL;/指针域为空return p; (2)创建链表模
6、块void createList(LNode *ppHead,int n)(3)出队处理模块void jose(LNode *ppHead,int m_pwd)(4)约瑟夫环说明输出模块void instruction()(5)菜单模块void menu()6(6)主函数模块int main()函数的调用关系图如下:Case 2:建立的约瑟夫环,并输出已建立的约瑟夫环:createList(LNode *ppHead,int n)输出该约瑟夫环的每个人的出列顺序: jose(LNode *ppHead,int m_pwd)图 3 约瑟夫环函数调用关系图菜单函数;void menu()主函数调用
7、函数;main()Case 1:一个简单的输出函数,用于说明约瑟夫环;void instruction()Case 0:default : 输入 0,退出 exit(0);三、详细设计1. 主函数7Main()开始Menu()功能菜单功能 1:约瑟夫环说明功能 2:按要求求解约瑟夫环功能 3:退出系统输入总人数 n输入开始上线数:m输入每个玩家的密码调用:createList(jose(ppHead,m);函数求解所需的密码序列选择要执行的操作程序运行完,自动返回到功能菜单图 4 主函数数据流程图根据流程图,主函数程序如下:int main()int n,m,x;LNode *ppHead=N
8、ULL;menu();for(;)printf(“n 请选择要执行的操作:“);scanf(“%d“,system(“cls“);switch(x)case 1:printf(“*n“); printf(“约瑟夫环:n“); 8printf(“ 编号为 1,2,3,4,n 的 n 个人按顺时针方向围坐一圈,每人持有一个密n“); printf(“码(正整数).一开始任选一个正整数作为报数的上限值 m,从第一个人开始n“); printf(“按顺时针方向自 1 开始顺序报数,报到 m 时停止.报 m 的人出列,将他的密码n“); printf(“m 作为新的 m 值,从他在顺时针方向上的下一人开
9、始重新从 1 报数,如此下去,n“); printf(“直到所有人全部出列为止.编程打印出列顺序.n“); printf(“*n“); main();break;case 2:printf(“n 请输入总人数 n:“);scanf(“%d“,printf(“请输入开始上限数 m:“);scanf(“%d“,createList( printf(“n“);printf(“出队顺序:n“);jose(ppHead,m);printf(“n 约瑟夫环游戏结束!n“);main();break;case 0:exit(0);default:system(“cls“);printf(“n 您选择的操作有
10、误,请重新选择.nnn“);main();return 0;2. 链表的创建9否是createList();从主函数中获取玩家信息 n如果 n0创建循环单链表,储存各个玩家密码退出创建链表完成返回主函数 main()创建储存玩家密码的循环单链表的方法Main()函数图 5 创建链表函数的数据流程图/*创建单向循环链表 ppHead,人数个数为 n,并输入每个人的密码值,若建立失败则生成头结点,让 cur 指向他,若建立成功则插入结点 P,cur 指向的数据元素为 p,后续为“空“的节点,再把 P 插入循环链表 ppHead 中*/根据流程图,创建链表函数程序如下:void createList
11、(LNode *ppHead,int n)int i,m_pwd;LNode *p,*cur;/cur:浮标指针for(i=1;inext=*ppHead;/cur 的指针域指向自身 else/如果不为空,则插入结点 p-next = cur-next;cur-next = p;cur= p;/cur 指向新插入结点 printf(“完成创建!n“); /提示链表创建完成 3. 出队处理Main()函数从循环链表中按初始密码依次扫描,找出对应的玩家序列输出其持有的密码i=ppHead-pwd; j=ppHead-num;移动浮标指针m_pwd=ppHead-pwd;输出密码后,删除相应的结点,
12、并释放所占的储存空间free(ppHead); ppHead=p-next;执行完后返回主函数jose();出队函数出队处理的方法图 6 出队函数的数据流程图/*p 指向要删除节点的前一个节点,ppHead 指向要删除的节点,使p=ppHead,ppHead 再指向要删除节点的下一个节点,使 p 和 ppHead 链接,输11出 p 指向节点的编号和密码值,释放 ppHead,如此循环,直至把所有节点都打印和删除为止!*/根据流程图,出队函数程序如下:void jose(LNode *ppHead,int m_pwd)int i,j;LNode *p,*p_del;/定义指针变量for(i=1
13、;p!=ppHead;i+)for(j=1;jnext;/ppHead 指向下一个元素p-next = ppHead-next;/p 结点与头结点链接i=ppHead-pwd;/i 赋值为 ppHead-pwdj=ppHead-num;/j 赋值为 ppHead-num,j 为要删除的密码值printf(“第%d 个人出列,密码:%dn“,j,i); m_pwd=ppHead-pwd;/m_pwd 赋值为 ppHead-pwdfree(ppHead);/释放头结点ppHead=p-next;/ppHead 重新赋值给 p-next,即释放前的 ppHead-pwd 指针/删除报数结点 i=pp
14、Head-pwd;/i 赋值为 ppHead-pwdj=ppHead-num;/j 赋值为 ppHead-numprintf(“最后一个出列是%d 号,密码是:%dn“,j,i); free(ppHead);/释放头结点4. 约瑟夫环说明模块void instruction()printf(“*n“); printf(“约瑟夫环:n“); printf(“ 编号为 1,2,3,4,n 的 n 个人按顺时针方向围坐一圈,每人持有一个密n“); printf(“码(正整数).一开始任选一个正整数作为报数的上限值 m,从第一个人开始n“); printf(“按顺时针方向自 1 开始顺序报数,报到时停
15、止.报 m 的人出列,将他的密码n“); printf(“m 作为新的 m 值,从他在顺时针方向上的下一人开始重新从 1 报数,如此下去,n“); printf(“直到所有人全部出列为止.编程打印出列顺序.n“); 12printf(“*n“); return 0;5. 菜单模块void menu()printf(“*约瑟夫环 *n“);printf(“ n“);printf(“ 1约瑟夫环问题的阐述 n“);printf(“ 2按要求求解约瑟夫环 n“);printf(“ 0退出 n“);printf(“* 欢迎使用! *n“); 四、程序调试与测试1. 调用模块时,结点结构的调用与其他模块
16、产生冲突,导致每一行都出现两次错误,加入子函数的声明后错误消失。2 . 刚开始时曾忽略了一些变量参数的标识“&“和“*” ,使调试程序时费时不少。今后应重视确定参数的变量和赋值属性的区分和标识。3. 本次课程设计采用数据抽象的程序设计方法,将程序划分为三个层次结构:元素节点、单向循环链表,主控制模块。思路较为清晰,实现调用顺利。 经过本次实验,使我对数据结构这门课程有了进一步的了解,每一个程序经过需求分析、概要设计、详细设计之后,思路即清晰呈现,程序也很快就出来了,最后经过调试、运行又有新的体验。这是一个使用循环链表的经典问题。本程序开始运行界面如下:13选择 1 进入约瑟夫环问题阐述。选择
17、2,输入下列数据测试:请输入总人数 n:7图 7 约瑟夫环开始运行界面图 8 约瑟夫环问题阐述14请输入开始上限数 m:20;请依次输入每个人的密码:3 1 7 2 4 8 4出队顺序:6 1 4 7 2 3 5继续选择 2,输入下列数据测试:请输入总人数 n:5请输入开始上限数 m:30请依次输入每个人的密码:3 4 5 6 7 出队顺序:5 3 1 2 4图 9 约瑟夫环测试 115测试完成,选择 0 退出。设计总结 我的这次数据结构课程设计的题目是:约瑟夫环,通过对该题目的设计,我加深了对数据结构及存储结构的理解,进一步地理解和掌握了课本中所学的各种数据结构,尤其是对单循环链表上基本运算
18、的实现,学会了如何把学到的知识用于解决实际问题,锻炼了自己动手的能力。通过这次数据结构课程设计,我感受最深的就是图 11 约瑟夫环测试 316对于循环链表的使用,可以说对循环链表有了比以前更进一步的认识,以前只是一知半解的,如果只给个题目自己根本不能把程序完整地编写出来,所以这次课程设计最大的收获就在于对循环链表有了一定的理解,包括其中的一系列操作,如建立一个循环链表,删除链表中的一个结点,增加一个结点等。在调试程序的时候我也有所体会,虽然约瑟夫环问题不是很难,但调试的时候还是会出现很多错误,因此我们不能认为容易就不认真对待。在以后的学习中,要能不断发现问题,提出问题,解决问题,从不足之处出发,在不断学习中提高自己。一周的课程设计很短暂,但其间的内容是很充实的,在其中我学习到了很多平时书本中无法学到的东西,积累了经验,锻炼了自己分析问题,解决问题的能力,并学会了如何将所学的各课知识融会,组织起来进行学习,总而言之这两周中我学到很多,受益匪浅。