综合实验约瑟夫环实习报告
一、实习题
约瑟夫(Joeph)问题的一种描述是:编号为1,2,…,n的n个人按顺时针方向围坐一圈,每人持有一个密码(正整数)。
一开始任选一个正整数作为报数上限值m,从第一个人开始按顺时针方向自1开始顺序报数,报到m时停止报数。
报m的人出列,将他的密码作为新的m值,从他在顺时针方向上的下一个人开始重新从1报数,如此下去,直至所有人全部出列为止。
试设计一个程序求出出列顺序。
1.需求分析和说明
分析约瑟夫问题:n个人围成圈,每人各持一个密码,任选一个正整数作为报数上限值m,从第一个人开始,数到第m个人,删除并以出列者密码作为新的m值,从下一个人开始进行第二轮操作,直到所有人都出列。
例如n=6, m=3, 处理过程下图。
2.设计
n个人围圈,形成线性关系;处理为逐个删除,故用链式结构合适;又人员围成圆圈,所以此链式结构采用循环方式较好;排号按照一个方向进行,故数据结构采用带头结点的单向循环链表。
假设人员以首次的编号命名,对每个人员采用编号和密码加以描述。
利用单向循环链表存储结构模拟此过程,按照出列的顺序印出各人的编号。
存储结构:
typedef struct Node
{
ElemType date; //编号
int mima; //密码
struct Node *next;
}Node,*LinkList;
单向循环链表:
LinkList H; //表头指针
Node *Q,*Pre;//*Q指新建结点,*pre指向当前工作结点
Q=(Node*)malloc(sizeof(Node));
构造函数:
void InitList(LinkList *H); //初始化循环链表
void InPut(LinkList H,int *A);//插入结点
void DelList(LinkList H,int *x, int*a); //删除结点
算法思想:
声明一个Node类型的单向循环链表。
从键盘顺序输入n个人的密码, 建立约瑟夫环。
,定报数上限,计数并逐个读取并删除第m个结点,更新m的数值,重复上述步骤,直到链表为空。
调用关系:
程序任务简单,故设计在一个main()函数内,有主函数调研相关函数操作。
算法实现框架:
声明数据类型的单向循环链表->输入人数N->依次输入N个人的密码用尾插法建立循环链表->输入M的初值->按规则计数并调用删除函数并显示结点的序号释放该结点—>结点的
密码最为新的m值重复上一步操作直至链表元素全部出列
3.用户手册:
运行程序,按照屏幕提示分别输入圈内人数n,正整数m和n个人的密码,之后屏幕将显示按照输入次序排好的人员被逐个删除的次序。
4.调试报告:
时间复杂度分析:
该算法在建立时的时间复杂度为O(n), 删除时时间耗费在逐个数元素上而计数的个数由m确定,m由删除成员的密码确定,m的初值人为确定,最后出列的元素的密码将不起作用;故总删除总的时间为:所有密码(出最后出列的成员的密码)的乘机*m的初值。
复杂度为:O(n*m);时间复杂度也为:O(n*m);
综合建立和删除,算法时间复杂度为:O(n*m)
算法改进思路:
在对元素数数删除过程中,总是要去判断是否是头结点并绕过它,可以改进一下,去掉头结点,由此看来,并非链式结构带有头结点都有益处。
改进后性能可以提高,但时间复杂度依然为:O(n*m)
5.附录
源程序清单
#include<stdio.h>
#include <stdlib.h>
typedef int ElemType;
typedef struct Node
{
ElemType date; //编号
int mima; //密码
struct Node *next;
}Node,*LinkList;
void InitList(LinkList *H); //初始化循环链表
void InPut(LinkList H,int *A);//插入结点
void DelList(LinkList H,int *x, int*a); //删除结点
void main()
{
int m,i=1,x=0,a=0,j,n;
LinkList H; //表头指针
InitList(&H); //初始化
printf("输入几个人:");
scanf("%d",&n); //输入人的个数以控制插入元素次数
InPut(H,&n);
printf("输入M的值:"); //M存储删除结点的代号
scanf("%d",&m);
printf("约瑟夫环的值:");
for(i=1;i<=n;i++) //执行n次删除结点操作把所有元素都删除
H=H->next; //排除头结点干扰
do{
for(i=1;i!=m;i++) //搜索代号为m的点
H=H->next;
DelList(H,&x,&a); //删除结点,用X删除结点的前一结点的编号,用a存储结点的密码,x,a是指针变量
printf("%d\t",x);
m=a;
}while(H->next!=H); //H指向要删除结点的前一个结点
printf("%d",H->date);
free(H);
printf("\n");
}
void DelList(LinkList H,int *x, int*a)
{
Node *k;
k=H->next;
H->next=H->next->next;
*a=k->mima;
*x=k->date;
free(k);
}
void InitList(LinkList *H)
{
(*H)=(LinkList)malloc(sizeof(Node));
(*H)->next=(*H); //初始化环链表
}
void InPut(LinkList H,int *A) //尾插法建循环链表
{
int i,a;
Node *Q,*Pre;
Pre=H;
for(i=1;i<=*A;i++) // A指结点个数
{
printf("第%d个人的密码:",i);
scanf("%d",&a);
Q=(Node*)malloc(sizeof(Node));
Q->mima=a;
Q->date=i; //编号
Pre->next=Q;
Pre=Q;
}
Pre->next=H->next;//pre是最后一个结点指向第一个结点,形成循环
}
测试数据:
m的初值为20;密码:3,1,7,2,4,8,4(正确的结果应为6,1,4,7,2,3,5)。
测试及运行结果:
输入几个人:7
第1个人的密码:3
第2个人的密码:1
第3个人的密码:7
第4个人的密码:2
第5个人的密码:4
第6个人的密码:8
第7个人的密码:4
输入M的值:20
约瑟夫环的值为:6 1 4 7 2 3 5。