约瑟夫环问题(洛谷P1996)链表入门题

        今天重温一下有关约瑟夫环的问题,先上题目



        看到约瑟夫环问题,大多数都是用链表解决(因为链表能很好地删除结点),这题同样如此。

        在竞赛中,有关链表的题目一般使用的都是STL或者手写静态链表,那么这题就手写静态链表看看。手写静态链表,一般有两个方式,一个是用node(节点)的结构体数组来存储,一个是用一维数组来写,相对来说前者逻辑清楚一些且不容易犯错,代码如下:

struct Node{
	int data;
	int next;
};
Node node[105];

        因为结构体数组不是“教科书式的链表”,它是对链表概念的一种模拟,所以可操作空间很大,如这里Node中的next为int类型,那么我们想访问下一节点只需要用node[next]就可以了。        

        确定使用结构体数组解题之后,就是data跟next应该是多少的问题。因为题目的输入只有一个总人数和一个出圈的人数,那么很明显data就应该是1、2、3、4、5......这样下去直到等于n结束,因为我们是认为node[next]为下一节点的(结构体数组模拟链表),所以next应该为下一节点node的下标,即“当前下标+1”,为i+1,那么创建链表的过程便如下:

	int n,m; cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		node[i].data = i;
		node[i].next = i+1;
	}
	node[n].next = 1;

        注意,这里的i是从1开始的,也就是说默认下标为0的node[0]为头节点,在创建完链表之后,将最后一个节点的next指向第一个节点将链表变为循环链表,那么就完成了约瑟夫环的模拟,最后变成这样的一个链表:


        那么接下来就是用循环来控制now来遍历这个单向循环链表(单向就够了,双向可以少用一个pre变量),代码如下:

	int pre = 0;//一开始将pre设为0,node[pre]即node[0]为头节点
	int now = 1;//一开始node[now]为第一个节点
	int count = 0;//计数器 
	int flag = 0;//控制打印的空格 
	while(node[now].data!=node[now].next)//此处循环条件想明白是最关键的 
	{
		count++;
		if(count==m)
		{
			count = 0;
			if(flag) cout<<" ";
			cout<<node[now].data;//打印
			flag = 1;
			node[pre].next = node[now].next;//删除节点 
		}
        //pre为当前节点now的上一节点
		pre = now;//pre后移
		now = node[now].next;//now 后移
	}

        这行代码其他地方都不是很难理解,但是那个循环条件有点抽象,这里解释一下:因为我们是用结构体数组来模拟链表的,随着我们一个接着一个地删除节点,最后整个链表只剩下了一个节点,这个节点的的next指向它自己(这就说明只剩下这一个没有被删除了),那么这个时候就已经可以退出了。其中flag用来打印格式,这就是全部的思路了。

        最后,这道题目的易错点为链表只有一个人并让这个人出圈的情况(即n=1,m=1时),因为这个时候循环体是直接退出的,那么就要注意打印的格式了。

        完整代码如下:

#include <bits/stdc++.h> 
using namespace std;
struct Node{
	int data;
	int next;
};
Node node[105];

int main()
{
	int n,m; cin>>n>>m;
    if(n==1&&m==1) {cout<<1; return 0;}
	for(int i=1;i<=n;i++)
	{
		node[i].data = i;
		node[i].next = i+1;
	}
	node[n].next = 1;
	int pre = 0;//将pre设为0,node[pre]即node[0]为头节点
	int now = 1;//node[1]为第一个节点
	int count = 0;//计数器 
	int flag = 0;//控制打印的空格 
	while(node[now].data!=node[now].next)//此处循环条件想明白是最关键的 
	{
		count++;
		if(count==m)
		{
			count = 0;
			if(flag) cout<<" ";
			cout<<node[now].data;
			flag = 1;
			node[pre].next = node[now].next;//删除节点 
		}
		pre = now;//后移pre和now 
		now = node[now].next;
	}
	cout<<" "<<node[now].data;
	return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值