此博客为学校一道oj题的思路分析,下面是题目:
题目描述:
约瑟夫环是一个数学的应用问题:已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。
用循环链表实现约瑟夫环。
输入:
输入多组测试数据,每组测试数据格式如下:
人数n(>0)开始位置k(>0)报数m(>0)
输出:
对每组测试数据,输出出圈顺序。
输入样例:
3 1 2
5 3 3
输出样例:
2 1 3
5 3 2 4 1
以下为AC代码:
#include <iostream>
using namespace std;
typedef struct node{
int data;
node*next;
}node;
void create(node*&L,node*&r,int n)
{
L=new node;//创建头节点
L->data=1;//初始化头节点数据
r=L;//创建尾指针
for(int i=1;i<n;i++)
{
node*s=new node;//创建下一节点
s->data=i+1;//初始化下一节点数据
r->next=s;//将上一节点与这一个节点建立联系
r=s;//迭代
}
r->next=L;//最后尾指针指向头指针,实现闭环
}
void out(node*&p,node*&pre,int k,int m)
{
while(--k)
{
p=p->next;
pre=pre->next;
}
int i=1;
while(p->next!=p)
{
if(i==m)//到达删除点的情况
{
i=1;//一次轮完,重新计数,此时为下一轮第一位
cout<<p->data<<" ";
pre->next=p->next;//跨过p建立联系
delete p;//删除p
p=pre->next;//重新定义p,此时p为上面的p->next
}
else//没有到删除点的情况
{
pre=pre->next;
p=p->next;//继续前进,没有改变数值的动作
++i;
}
}
cout<<p->data<<endl;
}
int main()
{
int n;
while(cin>>n)
{
node*r;
node*L;
create(L,r,n);
int k,m;
cin>>k>>m;
out(L,r,k,m);
}
return 0;
}
下面是题目解析:
先创建主函数:
int main()
{
int n;
while(cin>>n)
{
node*L;
node*r;
create(L,r,n);
//在主函数创建好头指针和尾指针,在函数中用引用的形式进行调用,使操作更方便,不用多次创建相同的变量,不用担心变量是否一致
int k,m;
cin>>k>>m;
out(L,r,k,m);
}
return 0;
}
首先,约瑟夫环是一个环状单向链表,则与之前不同,我们不能再放一个空的头节点,而且最后的尾指针要指向头节点
所以下面是创建环状单链表的函数实现:
typedef struct node{
int data;
node*next;
}node;
void create(node*&head,node*&r,int n)
{
head=new node;
head->data=1;//头节点也要进行数据初始化
r=head;//尾指针初始化
for(int i=1;i<n;i++)
{
node*s=new node;
s->data=i+1;
r->next=s;
r=s;
}
r->next=head;//尾指针指向头节点,构成闭环
}
创建好之后,就是执行游戏的具体函数了:
void out(node*&p,node*&pre,int k,int m)
{
while(--k)//与k--不同,--k先进行自减操作再进行判断
{
p=p->next;
pre=pre->next;
}//使两个指针到达指定位置
//因为在头节点时,
//就已经算是第一个经历的节点,所以使用--k,而不是用k--,其作用相当于k先减了个1,再进行k--
int i=1;
while(p->next!=p)//只剩下p时,p->next会指向自己(环状链表)
{
if(m==i)//到达删除位时的操作
{
i=1;//一次轮完,需要重新进行计数,此节点为该一轮的第一个节点,所以重置i为1
cout<<p->data<<" ";//展示这一轮删除的节点
pre->next=p->next;//使上一个节点next直接指向下一个节点,绕过中间节点p
delete p;//删除p节点
p=pre->next;//此时的p重定义为上面原本的p->next
}
else//未到删除节点
{
p=p->next;//直接往前进
pre=pre->next;
i++;
}
}
//只剩最后一个节点,上面未输出,这时需要补充输出
cout<<p->data<<endl;
}
注意点:几个循环的计次需要注意,此处均应从1开始,因为重置点/或到达点就是该轮循环的第一个节点
删除节点的操作图解:
由此,我们可知,要删除一个特定节点,需要两个指针pre和p
其中pre为上一个节点,p为当前节点
所以,最后的代码为:
typedef struct node{
int data;
node*next;
}node;
void create(node*&head,node*&r,int n)
{
head=new node;
head->data=1;//头节点也要进行数据初始化
r=head;//尾指针初始化
for(int i=1;i<n;i++)
{
node*s=new node;
s->data=i+1;
r->next=s;
r=s;
}
r->next=head;//尾指针指向头节点,构成闭环
}
void out(node*&p,node*&pre,int k,int m)
{
while(--k)//与k--不同,--k先进行自减操作再进行判断
{
p=p->next;
pre=pre->next;
}//使两个指针到达指定位置
//因为在头节点时,
//就已经算是第一个经历的节点,所以使用--k,而不是用k--,其作用相当于k先减了个1,再进行k--
int i=1;
while(p->next!=p)//只剩下p时,p->next会指向自己(环状链表)
{
if(m==i)//到达删除位时的操作
{
i=1;//一次轮完,需要重新进行计数,此节点为该一轮的第一个节点,所以重置i为1
cout<<p->data<<" ";//展示这一轮删除的节点
pre->next=p->next;//使上一个节点next直接指向下一个节点,绕过中间节点p
delete p;//删除p节点
p=pre->next;//此时的p重定义为上面原本的p->next
}
else//未到删除节点
{
p=p->next;//直接往前进
pre=pre->next;
i++;
}
}
//只剩最后一个节点,上面未输出,这时需要补充输出
cout<<p->data<<endl;
}
int main()
{
int n;
while(cin>>n)
{
node*L;
node*r;
create(L,r,n);
int k,m;
cin>>k>>m;
out(L,r,k,m);
}
return 0;
}