【约瑟夫环问题】
已知 n 个人(n>=1)围坐一圆桌周围,从 1 开始顺序编号,从序号为 1 的人开始报数,顺时针数到 m 的那个人出列。下一个人又从 1 开始报数,数到m 的那个人又出列。依此规则重复下去,直到所有人全部出列。请问最后一个出列的人的初始编号。
【要求】
输入人数 n,所报数 m,输出最后一个人的初始编号。
【约瑟夫环问题解决思路】
首先因为是圆桌问题,使用链表解决的话需要构建循环链表。
接着是出列问题,这里我的设计思路是将指向链表的指针移动到需要出列的人的位置,然后根据正常的链表删除进行操作即可。
我最近喜欢上了链表,所以就想水几发试试,试着写上自己的题解,希望许多像我一样努力的人能少走弯路。
给出两种解决的代码,一种链表,一种数组。
详细代码如下:
(1)链表:
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
typedef struct LNode{
int data; //数据域
struct LNode *next; //指针域
}LNode,*LinkList; //*LinkList为LNode类型
int n,m;
LinkList head,p,r;
int main()
{
//ios::sync_with_stdio(false); //可要可不要,主要是关闭缓冲区域
cin>>n>>m;
head = new LNode; //创建一个头结点
head->data = 1; //头结点的数据域置为1
head->next = NULL; //头结点的指针域为空
r = head; //让指针r指向head指向的结点,头结点head不移动
for(int i=2;i<=n;i++) //这个for循环是为了利用尾插法建好链表
{
p = new LNode; //生成一个新的结点
p->data = i; //数据域为i
p->next = NULL; //指针域为空
r->next = p; //这两句是为了移动尾指针使之始终指向新生成的指针
r = p;
}
r -> next = head; //首尾相连,循环链表建好
r = head; //因为head是头指针,没有移动,我们在输出的时候让r重新从头(指向头结点)开始
for(int i=1;i<=n;i++) //输出操作
{
for(int j=1;j<=m-2;j++) //要找第m个位置,先找第m-2个位置
r = r -> next; //这一步一旦成功,这时r指针就指向了第m-1个位置
cout<<r->next->data<<' '; //因为r指针指向第m-1个位置,所以r->next就连接了第m个结点,这是输出第m个结点的数据域
r->next = r->next->next; //删除第m个结点
r = r->next; //r指向删除的结点所连接的后续结点,因为每出队一个,就要从下一个位置重新开始
}
}
运行结果:
(2) 数组实现:
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
const int maxn = 1e5+10;
int a[maxn],b[maxn];
void solve(int n,int m)
{
int j=0,countee=0;
for(int i=0;i<n;i++) //习惯数组下标从0开始
a[i] = i+1;
for(int i=0;j!=n;i++)
{
if(a[i] == -1) //将首尾相连,类似一个循环数组
i = 0;
if(a[i]!=0 && a[i]!=-1) //中间的情况,继续进行数的自加
countee++;
if(countee==m) //将第m位的数存起来
{
b[j++] = a[i];
a[i] = 0;
countee = 0;
}
}
for(int i=0;i<n;i++) //依次输出
cout<<b[i]<<' ';
cout<<endl;
}
int main()
{
int n,m;
while(cin>>n>>m)
{
memset(a,0,sizeof a);
memset(b,0,sizeof b);
a[n] = -1; //这一步赋一下值
solve(n,m);
}
return 0;
}