问题描述
约瑟夫问题的一种描述是:编号为1,2,...,n的n个人按顺时针方向围坐一圈,每人持有一个密码(正整数)。一开始任选一个正整数作为报数上限值m,从第一个人开始。按顺时针方向自1开始顺序报数,报到m时停止报数。报m的人出列,将他的密码作为新的m值,从他在顺时针方向上的下一个人开始重新从1报数,如此下去,直至所有人全部出列为止。试设计程序求出出列顺序。
基本要求
利用单向循环链表存储结构模拟此过程,按照出列的顺序印出个人的编号。
测试数据
m的初值为20;n=7,7个人的密码: 3,1,7,2,4,8,4。(正确的结果应为6,1,4,7,2,3,5)
(报告上要求写出多批数据测试结果
实现提示
程序运行后首先要求用户指定初始报数上限值与人数,然后读取各人的密码。
代码实现:
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#define LENGTH sizeof(struct member) //宏定义结构体的长度
typedef struct member
{
int number; //成员的初始位置
int secret; //成员手里的密码
struct member *next; //指向下一个成员
}Member;
Member *Old_LinkList_creat(Member *old_head, int n); //创建旧链表
Member *LinkList_insert(Member *new_head, Member *p3, int n); //将指定结点链接到新链表
Member *New_LinkList_creat(Member *old_head, Member *new_head, int m, int n);//将所有满足要求的结点链接成新的链表
Member *LinkList_print(Member *new_head, int n); //打印链表
void DestroyList(Member *head, int n); //释放链表
int main()
{
char ch = 'y';
while(ch == 'y')
{
int m; //第一次报数上限值
int n; //成员人数
printf("输入第一次报数上限值m:");
scanf("%d",&m);
printf("输入成员的人数n:");
scanf("%d",&n);
Member *old_head = (Member *)malloc(LENGTH); //创建旧链表头结点
Member *new_head = (Member *)malloc(LENGTH); //创建新链表头结点
old_head->number = n; //用来判断约瑟夫环是否结束的标志
new_head->next = NULL; //用来判断插入新链表的结点是不是首元结点
old_head = Old_LinkList_creat(old_head, n);
new_head = New_LinkList_creat(old_head, new_head, m, n);
new_head = LinkList_print(new_head, n);
DestroyList(new_head, n);
printf("按y继续,按其它任意键结束:");
getchar();
scanf("%c",&ch);
printf("\n");
printf("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
printf("\n");
}
return 0;
}
//创建旧链表
Member *Old_LinkList_creat(Member *old_head, int n)
{
Member *p = old_head; //LENGTH为宏定义的结构体长度
printf("依次输入%d位成员手里的密码(如:2 3):\n",n);
int i = 0;
while(i < n)
{
p->next = (Member *)malloc(LENGTH);
scanf("%d",&p->next->secret);
p->next->number = i + 1;
p = p->next;
i++;
}
p->next = old_head->next; //尾结点指向首元结点,形成循环链表
return old_head;
}
//将指定结点链接到新链表尾结点
Member *LinkList_insert(Member *new_head, Member *p3, int n)
{
Member *p1 = new_head->next;
if(new_head->next == NULL)
{
new_head->next = p3;
p3->next = new_head->next;
}
else
{
while(p1->next != new_head->next) //遍历找到尾结点
{
p1 = p1->next;
}
p1->next = p3;
p3->next = new_head->next;
}
return new_head;
}
//将所有满足要求的结点链接成新的链表
Member *New_LinkList_creat(Member *old_head, Member *new_head, int m, int n)
{
Member *p2, *p3;
p2 = old_head; //p2指向转移结点的直接前驱
p3 = old_head->next; //p3指向要转移的结点
int cnt = 0; //计数器,记录遍历每一个结点
while(old_head->number)
{
cnt++; //先加1,判断p3是否为要转移的结点
if(cnt == m)
{
m = p3->secret; //将目标结点手里的密码作为新的m值
p2->next = p3->next;
new_head = LinkList_insert(new_head, p3,n);
cnt = 0; //上一个目标结点结束,从新开始计数
old_head->number--; //指示约瑟夫环(循环)结束的标志
p3 = p2->next;
}
else //不是目标结点,p2、p3同步右移指向下一个结点
{
p2 = p2->next;
p3 = p3->next;
}
}
old_head->next = NULL;
free(old_head);
return new_head;
}
//打印链表
Member *LinkList_print(Member *new_head, int n)
{
printf("\n约瑟夫密码为:\n");
Member *p4;
p4 = new_head;
for(int j = 1, i = 1; j <= n; j++, i++)
{
printf("%d\t",p4->next->number);
p4 = p4->next;
if(i%10 == 0) //每10个数换行
{
printf("\n");
}
}
printf("\n");
return new_head;
}
//释放链表
void DestroyList(Member *head, int n)
{
Member *p, *q;
p = head;
for(int i = 0; i <= n; i++)
{
q = p->next;
free(p); //从头开始释放
p = q;
}
}
运行截图: