约瑟夫问题是一个经典的计算机科学和数学问题,也被称为约瑟夫环或丢手绢问题。
约瑟夫问题起源于一个历史故事,据说古罗马历史学家弗拉维奥·约瑟夫斯和他的40个战友被罗马人围困在一个洞里,他们决定自杀而不是被俘,于是排成一个圆圈,每数到第三个人时,该人就必须自杀,直到所有人都死去。约瑟夫斯和他的朋友并不想遵从这一决定,通过巧妙的安排自己和朋友的位置逃脱了死亡。在数学和计算机编程中,约瑟夫问题的一般形式是:N个人围成一圈,从某个人开始报数,报到M的人出圈,然后从下一个人继续报数,直到全圈的人都出圈为止;
问题:
设编号分别为:1
,
2
,
…
,
n
的
n
个人围坐一圈。约定序号为
k
(
1
≤
k
≤
n
)的人从
1
开始计数,数到
m
的那 个人出列,他的下一位又从1
开始计数,数到
m
的那个人又出列,依次类推,直到所有人出列为止。
假设:设
n=8
,M
=3
,k
=4
时,出列序列为:
(6
,
2
,
7
,
4
,
3
,
5
,
1
,
8)
求解问题:
大家可以用一个不带头结点的循环链表来处理
问题:先构成一个有
n
个结点的单向循环链表,然后从第k
结点起从
1
计数,计到
m
时,对应结点从链表中删除;然后再从被删除结点的 下一个结点起又从1
开始计数
……
,直到所有结点都出列时算法结束。
a.创建单向循环链表,并在数据域中存入相应的编号(1....n)
//创建空链表
linklist_t *creat_Nodehead()
{
linklist_t *Nodehead=(linklist_t *)malloc(sizeof(linklist_t));
if(Nodehead==NULL)
{
printf("头结点开辟失败\n");
return NULL;
}
memset(Nodehead,0,sizeof(linklist_t));
Nodehead->data=1;
Nodehead->next=Nodehead;
return Nodehead;
}
//创建新结点
linklist_t *creat_NewNode()
{
linklist_t *Node=(linklist_t *)malloc(sizeof(linklist_t));
if(Node==NULL)
{
printf("结点开辟失败\n");
return NULL;
}
memset(Node,0,sizeof(linklist_t));
return Node;
}
//在链表尾部添加
void link_endNode(linklist_t *list,int i)
{
linklist_t *NewNode=creat_NewNode(i);
NewNode->data=i;
linklist_t *tmp=list;
while(tmp->next!=list)
{
tmp=tmp->next;
}
tmp->next=NewNode;
NewNode->next=list;
}
b.找到起始位置
使用while进行死循环遍历单向循环链表,若tmp这个结点的数据域所存编号与M相同,则跳出循环,当前结点位置就是起始位置1.
while(1)
{
if(tmp->data==M)
{
break;
}
tmp=tmp->next;
}
c.遍历单向循环链表,并对m计数
设置一个计数器flag,若flag==K-1;则表示下一个结点为出链表的结点,将这个节点删除,然后将flag重新置为1,继续进行上述操作,直到所有结点删除完(所有人出列)
while(N--)
{
while(1)
{
if(K-1==flag)
{
break;
}
flag++;
tmp=tmp->next;
}
flag=1;
linklist_t *freeNode=tmp->next;
number=tmp->next->data;
tmp->next=tmp->next->next;
tmp=tmp->next;
free(freeNode);
freeNode=NULL;
printf("%d-->",number);
}
putchar('\n');
}
结果展示:
在 博客上有我写的代码的压缩包,有兴趣的同学可以下载下来自己运行。注:(我的代码是在Linux ——Ubuntu中编写的)