约瑟夫问题
约瑟夫问题具体描述
设有编号为1,2,3,···,n的n(n>0)个人按顺时针方向围坐一圈,每个人手持一个随机产生的密码(正整数)。现从第k个人开始按顺时针的方向从1开始报数,报数上限是第一个人持有的密码m,报到m的人出列。然后将出列人持有的密码作为新的m的值,从下一个人开始重新从1开始报数,如此下去,直到所有人全部出列为止。
约瑟夫问题分析
很显然这是一个线性结构,可以用线性表来表示。进行的主要操作是报数、出列,这相当于对线性表进行删除操作,因此宜选用链表存储结构,每个结点代表一个人。n个人围坐成一圈循环报数,则利用单循环链表解决本问题更容易。因此需创建一个含有n个结点的单循环链表,每个结点的数据域可以用来存储结点的编号和密码,密码可由随机函数产生。结点的指针域指向下一结点的位置。第一次报数前首先应找到第k个结点,然后从它开始报数。为了找到报数为m的结点,需要从第k个结点开始计数,当找到报数为m的结点后,删除该结点,并且需要把该结点的密码作为新的m值,从该结点开始重新报数,如此重复,直到链表为空为止。
例题
编号是1,2,……,n的n个人按照顺时针方向围坐一圈,每个人持有一个密码(正整数)。一开始任选一个正整数作为报数上限值m,从第一个仍开始顺时针方向自1开始顺序报数,报到m时停止报数。报m的人出列,将他的密码作为新的m值,从他的顺时针方向的下一个人开始重新从1报数,如此下去,直到所有人全部出列为止。请设计一个程序输出出列顺序。
提示:存储结构采用不带头结点的循环单链表,结点结构如下:
typedef struct Node
{ int ID;
int password;
struct Node *next;
}LNode,*LinkList;
要求:
(1)编写建立循环单链表的函数,依次输入每个人的ID和password建立不带头结点的循环单链表。
(2)编写函数,按照规则依次删除相应的元素。
(3)main()函数调用(1)和(2)中的函数,输出约瑟夫序列。
Input
输入n
输入m
第1行为n
第2行为m
接下来每行表示每个人的ID和password
Output
依次输出出列者的ID
每输出一个人的ID换行。
Sample Input
8
4
1 3
2 1
3 9
4 2
5 4
6 7
7 4
8 6
Sample Output
4
6
7
3
5
8
2
1
HINT
注意:处理m=1的特殊情况。
代码如下
#include <stdio.h>
#include <stdlib.h>
typedef struct Node
{ int ID;
int password;
struct Node *next;
}LNode,*LinkList;
LinkList CreateList(LinkList L,int num);
void OutNum(LinkList L,int theFirstNum);
int main()
{
LinkList L = NULL;
int num;
int theFirstNum;
scanf("%d",&num);
scanf("%d",&theFirstNum);
L = CreateList(L,num);
OutNum(L,theFirstNum);
return 0;
}
LinkList CreateList(LinkList L,int num)
{
LinkList s,p;
int i = 1;
L = (LinkList)malloc(sizeof(LNode));
scanf("%d %d",&L->ID,&L->password);
L->next = NULL;
p = L;
while(i<num)
{
s = (LinkList)malloc(sizeof(LNode));
scanf("%d",&s->ID);
scanf("%d",&s->password);
s->next = NULL;
p->next = s;
p = s;
i++;
}
p->next = L;
return L;
}
void OutNum(LinkList L,int theFirstNum)
{
LinkList p,q,m;
int password,i;
password = theFirstNum;
p = L;
while(p->next != p)
{
for(i=1;i<password;i++)
{
q = p;
p = p->next;
}
password = p->password;
q->next = p->next;
printf("%d\n",p->ID);
free(p);
p = q->next;
}
printf("%d\n",p->ID);
}