循环链表解决约瑟夫环问题

一、问题描述

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开始报数”。之后开始下一次遍历。代码如下

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却不行,有没有聪明的小伙伴知道这是为什么吗?可以把你的思路放在评论区里。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

flow_shi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值