约瑟夫问题是个很经典的问题,目前网上求解方法较多,主要有数组法、循环队列法、指针链表法三种求解思路,下面给出三种求解方法的原创的标准C语言代码,含详细注释,供C语言和数据结构的初学者学习,本程序在VC 6.0下编译通过。
约瑟夫问题的来历:
据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。17世纪的法国数学家加斯帕在《数目的游戏问题》中讲了这样一个故事:15个教徒和15 个非教徒在深海上遇险,必须将一半的人投入海中,其余的人才能幸免于难,于是想了一个办法:30个人围成一圆圈,从第一个人开始依次报数,每数到第九个人就将他扔入大海,如此循环进行直到仅余15个人为止。问怎样排法,才能使每次投入大海的都是非教徒。
约瑟夫问题的一般形式:
约瑟夫问题是个有名的问题:N个人围成一圈,从第一个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉。例如N=6,M=5,被杀掉的人的序号为5,4,6,2,3。最后剩下1号。
一、采用一维数组法(直接思维法)求解:
#include<stdio.h>
//功能:采用一维数组解决约瑟夫问题
//输入参数:TotalNum总人数,Num要出列的序号
static void SolveJoseph(int TotalNum,int Num)
{
#define OUTPUT(num) printf("%d ",num); //定义出列方式
int i,j=0,Cnt=TotalNum;//报数数数变量,数组偏移变量,报数轮数计数
int array[50]; //暂存人号码的数组
for(i=0;i<TotalNum;i++) //数组赋值
array[i]=i+1;
while(Cnt--!=0)//判断所有成员是否已出列
{
for(i=1;i<=Num;j=(j+1)%TotalNum)
{
if(array[j]!=0)i++; //若该位置元素未出列,数数变量+1
if(i==Num+1&&array[j]!=0)
{
OUTPUT(array[j]);//元素出列
array[j]=0;//已出列元素标记为0
}
}
}
}
int main(void)
{
int TotalNum,Num;
printf("约瑟夫问题");
while(1)
{
printf("\n输入人数总数和出列序号,中间空格隔开:");
scanf("%d%d",&TotalNum,&Num);
SolveJoseph(TotalNum,Num);
}
}
二、循环队列法
#include <stdio.h>
#include <malloc.h>
#define MaxSize 100
typedef char ElemType ;
typedef struct
{
ElemType data[MaxSize];
int front,rear; //队首和队尾指针
} SqQueue;
/*void InitQueue(SqQueue *&q)//此处必须是引用类型
{
q=(SqQueue *)malloc (sizeof(SqQueue));
q->front=q->rear=0;
}*/
//原程序采用C++的引用类型,改为标准C编写
#define InitQueue(q) do{q=(SqQueue *)malloc (sizeof(SqQueue)); q->front=q->rear=0;}while(0);
//清空队列
void ClearQueue(SqQueue *q)
{
free(q);
}
//判断队列是否为空
int QueueEmpty(SqQueue *q)
{
return(q->front==q->rear);
}
//元素入列
int enQueue(SqQueue *q,ElemType e)
{
if ((q->rear+1)%MaxSize==q->front) //队满
return 0;
q->rear=(q->rear+1)%MaxSize;
q->data[q->rear]=e;
return 1;
}
//原程序采用C++的引用类型,改为标准C编写
/*int deQueue(SqQueue *&q,ElemType &e)
{
if (q->front==q->rear) //队空
return 0;
q->front=(q->front+1)%MaxSize;
e=q->data[q->front];
return 1;
}*/
//元素出列
int deQueue(SqQueue *q,ElemType *e)
{
if (q->front==q->rear) //队空
return 0;
q->front=(q->front+1)%MaxSize;
*e=q->data[q->front];
return 1;
}
//采用循环队列法解决约瑟夫问题
//输入参数:TotalNum总人数,Num要出列的序号
void SolveJoseph(int TotalNum,int Num)
{
#define OUTPUT(num) printf("%d ",(int)num); //定义出列方式
int Cnt=0,i;//定义数数变量
ElemType elem;//定义一个临时变量
SqQueue *q;//定义队列
InitQueue(q);
for( i=0;i<TotalNum;i++) //队列初始化
enQueue(q,(ElemType)(i+1));
while(!QueueEmpty(q))//判断是否所有元素已出列
{
deQueue(q,&elem); //出列
if(Cnt!=Num-1) enQueue(q,elem);//如果数数变量不等于设定的编号则入列
else OUTPUT(elem); // 否则输出元素
Cnt=(Cnt+1)%Num; //数数变量加1
}
ClearQueue(q);//释放队列的内存空间
}
int main(void)
{
int TotalNum,Num;
printf("约瑟夫问题");
while(1)
{
printf("\n输入人数总数和出列序号,中间空格隔开:");
scanf("%d%d",&TotalNum,&Num);
SolveJoseph(TotalNum,Num);
}
}
三、采用单链表法
#include<stdio.h>
//功能:采用单链表解决约瑟夫问题
//输入参数:TotalNum总人数,Num要出列的序号
#include <malloc.h>
typedef char ElemType;
typedef struct LNode //定义单链表结点类型
{
ElemType data;
struct LNode *next;//指向后继结点
} LinkList;
void CreateListF(LinkList *L,ElemType a[],int n)
//头插法建立单链表
{
LinkList *s;int i;
L=(LinkList *)malloc(sizeof(LinkList)); //创建头结点
L->next=NULL;
for (i=0;i<n;i++)
{
s=(LinkList *)malloc(sizeof(LinkList));//创建新结点
s->data=a[i];
s->next=L->next;//将*s插在原开始结点之前,头结点之后
L->next=s;
}
}
void CreateListR(LinkList *L,ElemType a[],int n)
//尾插法建立单链表
{
LinkList *s,*r;int i;
L=(LinkList *)malloc(sizeof(LinkList)); //创建头结点
L->next=NULL;
r=L; //r始终指向终端结点,开始时指向头结点
for (i=0;i<n;i++)
{
s=(LinkList *)malloc(sizeof(LinkList));//创建新结点
s->data=a[i];
r->next=s; //将*s插入*r之后
r=s;
}
r->next=NULL; //终端结点next域置为NULL
}
/*void InitList(LinkList *&L)
{
L=(LinkList *)malloc(sizeof(LinkList)); //创建头结点
L->next=NULL;
}*/
//原程序采用C++的引用类型,改为标准C编写
#define InitList(L) do{ L=(LinkList *)malloc(sizeof(LinkList)); L->next=NULL; }while(0) //创建头结点
void DestroyList(LinkList *L)
{
LinkList *p=L,*q=p->next;
while (q!=NULL)
{ free(p);
p=q;
q=p->next;
}
free(p); //此时q为NULL,p指向尾结点,释放它
}
int ListEmpty(LinkList *L)
{
return(L->next==NULL);
}
int ListLength(LinkList *L)
{
LinkList *p=L;int i=0;
while (p->next!=NULL)
{ i++;
p=p->next;
}
return(i);
}
void DispList(LinkList *L)
{
LinkList *p=L->next;
while (p!=NULL)
{ printf("%d ",p->data);
p=p->next;
}
printf("\n");
}
int GetElem(LinkList *L,int i,ElemType *e)
{
int j=0;
LinkList *p=L;
while (j<i && p!=NULL)
{ j++;
p=p->next;
}
if (p==NULL) //不存在第i个数据结点
return 0;
else //存在第i个数据结点
{ *e=p->data;
return 1;
}
}
int LocateElem(LinkList *L,ElemType e)
{
LinkList *p=L->next;
int n=1;
while (p!=NULL && p->data!=e)
{ p=p->next;
n++;
}
if (p==NULL)
return(0);
else
return(n);
}
int ListInsert(LinkList *L,int i,ElemType e)//链表插入操作,下标从1开始
{
int j=0;
LinkList *p=L,*s;
while (j<i-1 && p!=NULL) //查找第i-1个结点
{ j++;
p=p->next;
}
if (p==NULL) //未找到位序为i-1的结点
return 0;
else //找到位序为i-1的结点*p
{ s=(LinkList *)malloc(sizeof(LinkList));//创建新结点*s
s->data=e;
s->next=p->next;//将*s插入到*p之后
p->next=s;
return 1;
}
}
/*int ListDelete(LinkList *L,int i,ElemType &e)//链表删除操作,下表从1开始
{
int j=0;
LinkList *p=L,*q;
while (j<i-1 && p!=NULL)//查找第i-1个结点
{ j++;
p=p->next;
}
if (p==NULL) //未找到位序为i-1的结点
return 0;
else //找到位序为i-1的结点*p
{ q=p->next;//q指向要删除的结点
if (q==NULL) return 0;//若不存在第i个结点,返回0
e=q->data;
p->next=q->next;//从单链表中删除*q结点
free(q); //释放*q结点
return 1;
}
}
*/
//原始代码采用了C++中的引用类型,为增加移植性,改为标准C编写
int ListDelete(LinkList *L,int i,ElemType *e)//链表删除操作,下表从1开始
{
int j=0;
LinkList *p=L,*q;
while (j<i-1 && p!=NULL)//查找第i-1个结点
{ j++;
p=p->next;
}
if (p==NULL) //未找到位序为i-1的结点
return 0;
else //找到位序为i-1的结点*p
{ q=p->next;//q指向要删除的结点
if (q==NULL) return 0;//若不存在第i个结点,返回0
*e=q->data;
p->next=q->next;//从单链表中删除*q结点
free(q); //释放*q结点
return 1;
}
}
static void SolveJoseph(int TotalNum,int Num)
{
#define OUTPUT(num) printf("%d ",num); //定义出列方式
int i,j=0;//报数数数变量,数组偏移变量
LinkList *L; //存放人号码的链表
ElemType elem;
InitList(L);//链表初始化
for(i=0;i<TotalNum;i++) //队列初始化
ListInsert(L,i+1,(ElemType)(i+1)); //链表插入下表从1开始
//程序主干部分
while(!ListEmpty(L))//判断所有成员是否已出列
{
j=(j+Num-1)%ListLength(L);//根据报数的大小,增加偏移量
ListDelete(L,j+1,&elem); //删除需要出列的元素。链表下标从1开始
OUTPUT(elem);//输出元素
}//end while
DestroyList(L);//销毁链表
}//end SolveJoseph
void main(void)
{
int TotalNum,Num;
printf("约瑟夫问题");
while(1)
{
printf("\n输入人数总数和出列序号,中间空格隔开:");
scanf("%d%d",&TotalNum,&Num);
SolveJoseph(TotalNum,Num);
}
}