约瑟夫问题三种求解方法

    约瑟夫问题是个很经典的问题,目前网上求解方法较多,主要有数组法、循环队列法、指针链表法三种求解思路,下面给出三种求解方法的原创的标准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);
  }   


}


  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值