今天重温一下有关约瑟夫环的问题,先上题目
看到约瑟夫环问题,大多数都是用链表解决(因为链表能很好地删除结点),这题同样如此。
在竞赛中,有关链表的题目一般使用的都是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;
}