一、问题描述
n 个人围成一圈,从第一个人开始报数,数到 m 的人出列,再由下一个人重新从 1开始报数,数到 m 的人再出圈,依次类推,直到所有的人都出圈,请输出依次出圈人的编号。
二、算法思路
n个人围成一圈,很容易可以想到用循环链表解决问题,用结点代表每个人,节点的数据域存储人的编号。对链表的定义如下
typedef struct node
{
int data; //数据域
struct node* next; //指针域
}node;
首先我们需要将链表初始化,对约瑟夫环问题,已知链表结点总数,也就是循环次数,且结点存储的数据就是初始化时结点在链表的顺序(比如第一个结点存储的数据就是1)。于是我们采取for循环来进行链表初始化,假设循环n次,则前n-1次创建新结点,最后一次让尾结点指向头结点完成循环链表的创建。相关代码如下:
//初始化循环链表
node* CreatList(int n) //参数为链表结点数,即围成环的人数
{
node* headNode = (node*)malloc(sizeof(node)); //为头结点申请内存
node* p = headNode; //用p来遍历链表
for (int i = 1; i <= n; i++) //有n个数据==人,则循环n次
{
p->data = i; //令第i个结点的存储的数据==i
if (i < n) //前n-1次申请下一个结点
{
p->next = (node*)malloc(sizeof(node)); //给下一个结点申请内存
p = p->next; //让p向后挪一位用于下一次遍历
}
else p->next = headNode; //第n次让尾结点指向头节点
}
return headNode; //返回头结点
}
之后,就要进入我们今天的正题了,约瑟夫环问题可以抽象成通过不断地删除链表中的元素直至链表中只剩最后一个元素,那根据循环链表地定义,当链表中只剩一个元素的时候,链表头节点的下一个结点就是它自己,如图
那么headNode==headNode->next
就可以成为我们判断循环结束的条件。在遍历时,先用node* p
指向报数为“1”的人(注意报数为“1”不是编号为“1”)第一次报数为“1”的人就是编号为“1”的人,也就是我们的 headNode
,再通过循环让p
指向被删除的数的上一个结点,若要删除的结点为报数为k,则需要循环k-2次。之后就是打印此结点、删除结点……不再做过多赘述。在删除之后p->next
就是原来被删除的结点的下一个结点了,于是让headNode
指向这个结点,也就是“再由下一个人重新从 1开始报数”。之后开始下一次遍历。代码如下文章来源:https://www.toymoban.com/news/detail-726132.html
int Joseph(node* headNode, int n, int k)
{
while (headNode->next != headNode) //当headNode==headNode->next的时候说明只剩一个人未出圈
{
node* p = headNode; //用p遍历链表
for (int i = 1; i <= k - 2; i++)//让p->next指向要出圈的人
p = p->next;
node* q = p->next; //用q暂存要出圈的人,防止丢失
printf("%d ", q->data); //打印本次要出圈的人的编号
p->next = p->next->next; //将此人出圈
free(q); //释放出圈人所占的空间
headNode = p->next; //让头结点指向出圈人的下一个结点,即“再由下一个人重新从 1开始报数”
}
return headNode->data; //返回最后出圈人的编号
}
三、整体代码
#include<stdio.h>
#include<stdlib.h>
typedef struct node
{
int data;
struct node* next;
}node;
//初始化循环链表
node* CreatList(int n) //参数为链表结点数,即围成环的人数
{
node* headNode = (node*)malloc(sizeof(node)); //为头结点申请内存
node* p = headNode; //用p来遍历链表
for (int i = 1; i <= n; i++) //有n个数据==人,则循环n次
{
p->data = i; //令第i个结点的存储的数据==i
if (i < n) //前n-1次申请下一个结点
{
p->next = (node*)malloc(sizeof(node)); //给下一个结点申请内存
p = p->next; //让p向后挪一位用于下一次遍历
}
else p->next = headNode; //第n次让尾结点指向头节点
}
return headNode; //返回头结点
}
int Joseph(node* headNode, int n, int k)
{
while (headNode->next != headNode) //当headNode==headNode->next的时候说明只剩一个人未出圈
{
node* p = headNode; //用p遍历链表
for (int i = 1; i <= k - 2; i++)//让p->next指向要出圈的人
p = p->next;
node* q = p->next; //用q暂存要出圈的人,防止丢失
printf("%d ", q->data); //打印本次要出圈的人的编号
p->next = p->next->next; //将此人出圈
free(q); //释放出圈人所占的空间
headNode = p->next; //让头结点指向出圈人的下一个结点,即“再由下一个人重新从 1开始报数”
}
return headNode->data; //返回最后出圈人的编号
}
int main()
{
int n, k;
scanf("%d %d", &n, &k); //输入总人数和出圈序号
node* headNode = CreatList(n); //链表初始化
printf("%d", Joseph(headNode,n,k)); //输出最后一个出圈人的编号
return 0;
}
四、课后思考
最后,以上内容在洛谷P1996可以AC,但是在P8671却不行,有没有聪明的小伙伴知道这是为什么吗?可以把你的思路放在评论区里。文章来源地址https://www.toymoban.com/news/detail-726132.html
到了这里,关于循环链表解决约瑟夫环问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!