用循环队列求解约瑟夫环问题

用循环队列求解约瑟夫环问题
大家好,这次我们来求解求解约瑟夫环问题,并对比用循环队列和用普通形式求解之间的不同之处。也欢迎各位大佬指正。

问题描述

设有n个人站成一圈,其编号为1~n。从编号为1的人开始按顺时针方向1、2……循环报数,数到m的人出列,然后从出列者的下一个人重新开始报数,数到m的人又出列,如此重复进行,直到n个人都出列为止。要求输出这n个人的出列顺序。
例子展示:当n=5,m=2时的约瑟夫环问题情况如下
在这里插入图片描述

循环队列求解

算法描述

秉承队列的先进先出原则,我们在此基础上思考求解方式。

1. n个人围成圈:先将所有人都放入循环队列中,无论这个循环队列的大小是否超出了n,让每个出队的人再进队,不断重复达到围成圈的实际操作;

2. 依次报数:从第一个人开始依次出队,出队一次则用计数变量 ‘i’ 自增;

3. 数到m的人出列:当i%m==0时,则我们就不用将这个人再进队了,直接将其放入一个数组中,用该数组来记录出队顺序;

4. 直到n个人都出列为止:当队列为空时,也就是队列中n个人都出列了,此时结束循环,输出数组内的元素;

以上操作即可完成约瑟夫环问题。

程序清单

以下为顺序队列的建队过程,大体一致就不多做解释了

#include <iostream>
using namespace std;
#define Maxze 30
typedef int ElemType;
typedef struct
{
	ElemType data[30];
	int front;
	int rear; 
}SqQueue;

//1.初始化队列
void InitQueue(SqQueue *&q)
{
	q=new SqQueue;
	q->front=-1;
	q->rear=-1;
} 

//2.删除队列
void DestroyQueue(SqQueue *&q)
{
	delete q;
} 

//3.判断是否为空表
bool QueueEmpty(SqQueue *q)
{
	if(q->front==q->rear)
		return true;
	else
		return false;
 }
  
//4.入队
bool enQueue(SqQueue *&q,ElemType e)
{
	q->rear=(q->rear+1)%Maxze;
	if(q->rear==q->front)		//队满 
		return false;
	q->data[q->rear]=e;
	return true;
 } 
 
 //5.出队
bool deQueue(SqQueue *&q,ElemType &e)
{
	if(q->front==q->rear)		//队空 
		return false;
	q->front=(q->front+1)%Maxze;
	e=q->data[q->front];
	return true;
}

以下是约瑟夫环问题的关键求解部分
(设立如此多的函数是为了让主函数看起来更简便)

//6.建立所有人员的编号(主要是为了主函数简洁)
void Number(ElemType Fir_Last[],int n)
{
	int i;
	for(i=0;i<n;++i)
		Fir_Last[i]=i+1;
} 

//7.所有人排队
void QueueUp(SqQueue *&q,ElemType Fir[],int n)
{
	int i;
	for(i=0;i<n;++i)
	{
		enQueue(q,Fir[i]);	//将每个人都入队 
	}

} 

//8.排查报数为m的人
void SeekQueue(SqQueue *&q,ElemType Last[],int m)
{
	int i=0,j=0;
	ElemType e;
	while(!QueueEmpty(q))
	{
		i++;
		deQueue(q,e);
		if(i==m)
		{
			Last[j]=e;
			i=0,j++;
		}
		else
		{
			enQueue(q,e);
		}
	}
} 

//9.输出出队的顺序
void DispQueue(ElemType Last[],int n)
{
	int i,k=0;
	for(i=0;i<n;++i)
	{
		cout<<Last[i]<<"	";
		k++;
		if(k%5==0) cout<<endl;	//每输出5个就换行 
	}
} 

以下是主函数的调用部分

int main()
{
	int n,m;
	SqQueue *q;
	InitQueue(q);
	cout<< "请输入有多少个人:";
	cin>>n;
	cout<< "\n请输入m的数值:";
	cin>>m;
	cout<<"则其出队的顺序为:\n" ;
	ElemType Fir_Last[n];
	Number(Fir_Last,n); 			//给所有人编号 
	QueueUp(q,Fir_Last,n);		//所有人先排队 
	SeekQueue(q,Fir_Last,m);	//寻找出队序列 
	DispQueue(Fir_Last,n);		//输出顺序 
	cout<<"\n作者:Nothing_Wzy"<<endl; 
}

运行结果

在这里插入图片描述
以上即用循环队列求解约瑟夫环问题的全部内容了,接下来我们使用普通的求解方式来对比两者之间的差异:

普通形式求解

算法描述

建立一个数组存放所有人的编号,开始用i++不断循环(当i=n时重置i),用num来记数,当num%m==0时,输出a[i],(也可以)令a[i]=0;在循环中跳过数值为0的变量,用sign来记录总输出个数,当sign=n时就结束循环。

程序清单

输入部分从简了

#include<iostream>
using namespace std;
#include<stdio.h>
void fun(int a[],int n,int m)
{
	int i=0,num=1,sign=0;//sign为结束标志 
	while(sign!=n)
	{
		if(i==n) i=0;		//重置i
		
		if(a[i]==0) i++; 
		else if(num%m==0)	//报数到m即出队 
		{	
			cout<<a[i]<<"	";
			a[i]=0;			//出队的操作就将它赋值为0 
			i++;num++;
			sign++;
			if(sign%5==0) cout<<endl;//每输出5个数就换行 
		}
		else i++,num++;	
	}
}
int main()
{
	int a[]={1,2,3,4,5,6,7,8,9,10,11,12};
	int	n=12;		//输入部分从简了
	int m=3;
	fun(a,n,m);
}

运行结果

在这里插入图片描述

对比分析

很清晰的看得到,我们使用第二种方式求解时,针对问题直接设置想要的变量,建立想要的循环,暴力求解得出答案,但缺点很明显,代码部分难以解释清楚用以,且为“一次性代码”,不可循环利用。
而我们使用第一中方式求解时,我们确定好要使用的数据结构类型等,再根据给定的功能快速得出求解方向,以此清晰明了的求解出问题的答案。而且代码是可以重复利用的。

以上就是我个人的求解思路与感想,如有不妥之处或者更好的方式的话,欢迎在此评论或私信指出喔。

发布了3 篇原创文章 · 获赞 1 · 访问量 61

猜你喜欢

转载自blog.csdn.net/Nothing_Wzy/article/details/105626497