西安交通大学数据结构上机报告之约瑟夫环问题仿真
1、约瑟夫环问题仿真
设编号为1,2,…,n(n>0)个人按顺时针方向围坐一圈,每人持有一个正整数密码。开始时任意给出一个报数上限m,从第一个人开始顺时针方向自1起顺序报数,报到m时停止报数,报m的人出列,将他的密码作为新的m值,从他在顺时针方向上的下一个人起重新自1报数;如此下去直到所有人全部出列为止。
(1)问题分析
可利用单循环链表解决此问题,建立一个单循环链表,依次录入密码,然后从第一个节点出发,连续略过N-1个结点,将第N个节点从链表中删除,并将第N个结点的密码作为新的m值,接着从下一个结点开始,循环此过程,直至链表为空。
(2)数据结构设计
此程序实现的方法是建立一个单循环链表,然后对循环链表进行相关操作,模拟整个报数及出列过程。
主要为以下三个步骤:
1.建立一个具有n个链结点,无头结点的循环链表
2.确定第1个报数人的位置
3.不断地从链表中删除链结点,直到链表为空
首先,建立建立一个单循环链表,将每个人的信息用一个结点存储,包括三个信息,一是他的编号num,二是他持有的密码key,三是
指向下一结点的指针。其中编号是一开始按照相邻顺序编号的,密码可以设置,需要输入数值。
第一次报数默认为从1 号开始报数,需设置报数上限值m ,报m 的人出列,然后从出列的人后面一个人开始下一轮报数,且报数上限值m变为出列的人持有密码。
即第一次由头指针head 开始向后数到第 N-1 个结点,删掉第N-1 个结点的后继结点,即第 N 个结点,这时第 N-1 个结点直接指向第 N+1 个结点,同时让头指针head也指向第 N+1 个结点。因为下一轮报数时将从第 N+1 个结点开始,即还是head指向的结点开始。
第二轮报数首先将m值修改为刚刚被删除结点的持有密码,即m=q->key 再进行和第一轮报数相同的操作。
以后每轮报数如次反复执行,直到所有结点被删除结束。
且在此过程中,根据m值的不同,将分别对m=1与m=其他值的情况进行讨论处理。
程序主要过程可由以下三个函数实现:
1)ERROR()2)struct L *creat(int N)3)struct L
*LisDelete(struct L *head,int m,int N)
(3)算法设计与实现
#include <>
#include <>
#define NULL 0
#define LEN sizeof(struct L)
struct L
{
int num;
int key;
struct L *next;
};
int n;
int i=0;
ERROR()
{
printf("ERROR!\n");
printf("所用的密码必须还正整数而且m>0 N>0!!!\n"); }
struct L *creat(int N)
{
struct L *head;
struct L *p1,*p2;
n=0;
p1=p2=(struct L *)malloc(LEN);
printf(" 请输入第1个人手里的密码:"); scanf("%d",&p1->key);
p1->num=1;
head=NULL;
while(p1->num<=N)
{
n=n+1;
if(n==1) head=p1;
else p2->next=p1;
p2=p1;
p1=(struct L *)malloc(LEN);
if (n==N)
{
printf(" 最后请输入0来结束密码录入:");
}
else
{
printf(" 请输入第%d个人手里的密码:",n+1); }
scanf("%d",&p1->key);
p1->num=n+1;
}
p2->next=head;
return(head);
}
struct L *LisDelete(struct L *head,int m,int N) {
struct L *p,*q;
int j=0,k=0;
q=p=head;
i=i+1;
if(p->next==head) return(p);
if(m==1)
{
while(k
printf("%i 第%d个人退出.\n",i,q->num);
m=q->key;
p->next=q->next;
head=q->next;
free(q);
}
else
{
while(j { p=p->next; j=j+1; } q=p->next;p->next=q->next; printf("%d 第%d个人退出.\n",i,q->num); m=q->key; head=p->next; free(q); } N=N-1; LisDelete(head,m,N); } main() { struct L *head; struct L *p; int m,N; printf("*************************************************** ***\n"); printf("约瑟夫环:\n"); printf(" 编号为1,2,3,4…,n的n个人按顺时针方向围坐一圈,每人\n"); printf("持有一个密码(正整数).一开始任选一个正整数作为报数的上\n"); printf("限值m,从第一个人开始按顺时针方向自1开始顺序报数,报到m\n"); printf("时停止.报m的人出列,将他的密码作为新的m值,从他在顺时针\n"); printf("方向上的下一人开始重新从1报数,如此下去,直到所有人全部\n"); printf("出列为止.编程打印出列顺序.\n"); printf("*************************************************** ***\n"); printf(" 输入人数:"); scanf("%d",&N); printf(" 输入处值m:"); scanf("%d",&m); printf("***********************************\n"); if (m<=0||N<=0) { ERROR(); } else { printf(" 过程:\n"); head=creat(N); printf("*******************************\n"); printf(" 结果:\n"); p=LisDelete(head,m,N); printf("%d 第%d个人退出.\n",N,p->num); } printf("***********************************\n"); } (4)运行结果及分析 运行结果如图所示: 分析: 该算法的主要部分是单循环链表,在建立时的时间复杂度为O(N),删除时时间耗费在逐个数元素上,而计数的个数由m确定,m由删除成员的密码确定,m的初值人为确定,最后出列的成员的的密码将不起作用,故时间复杂度为O(m*N)。 综合建立和删除,算法的时间复杂度为O(m*N)。 本算法的不足之处是:当m、N取较大数值时,将无法在短时间内得出结果,影响工作的效率。 因此必要时需进行改进,减小时间复杂度,提高算法的效率。