约瑟夫环的三种解法
约瑟夫环问题的具体描述是:设有编号为1,2,……,n的n(n>0)个人围成一个圈,从第1个人从1开始报数,报到m时停止报数,报m的人出圈,再从他的下一个人起重新报数,报到m时停止报数,报m的出圈,……,如此下去,直到所有人全部出圈为止。(求最后出圈的那个人的序号)
解法一:数组
解法二:数学方法(即更加高效的算法)
每次去除一个元素后,将剩下的元素重新排序,用倒推的方法求出下一个出圈的元素在上一轮中的序号。从倒数第一轮一直推到第一轮,
就能够直到胜利的那个元素的原先的序号。详情如下:
为了讨论方便,先把问题稍微改变一下,并不影响原意:
问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出 ,剩下的人继续从0开始报数。求胜利者的编号。
我们知道第一个人(编号一定是m%n-1) 出列之后,剩下的n-1个人组成了一个新的约瑟夫环(以编号为k=m%n的人开始):
k k+1 k+2 ... n-2, n-1, 0, 1, 2, ... k-2
并且从k开始报0。
现在我们把他们的编号做一下转换:
k --> 0
k+1 --> 1
k+2(x+k)%n--> 2 (x)
...
...
k-2 --> n-2
变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解:例如x是最终的胜利者,那么根据上面这个表把这个x
变回去不刚好就是n个人情况的解吗?!!变回去的公式很简单,相信大家都可以推出来:x‘=(x+k)%n
如何知道(n-1)个人报数的问题的解?对,只要知道(n-2)个人的解就行了。(n-2)个人的解呢?当然是先求(n-3)的情况 ---- 这显然就是
一个倒推问题!好了,思路出来了,下面写递推公式:令f表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是f
递推公式
f=0;
f=(f+m)%i; (i>1)
有了这个公式,我们要做的就是从1-n顺序算出f的数值,最后结果是f。因为实际生活中编号总是从1开始,我们输出f+1
解法三:链表
构造一个循环链表,每经过一个数据元素,则将计数器数加1.当计数器加到m时,移除该链表元素,将计数器置为0,继续往下数。
直到链表长度为1.移除元素的顺序即为n个人出圈的次序。算法复杂度为O(nm).
#include<stdio.h>
#include<stdio.h> //约瑟夫环问题
#include<stdlib.h>
int main()
{
int n,i,*a,m;
int k,t;
printf("输入总共的人数:");
scanf("%d",&n);
printf("输入报数值:");
fflush(stdin);
scanf("%d",&m);
a=(int *)malloc(sizeof(int)*(n+1));//动态分配内存,建立一个int a的数组.
for(i=1;i<=n;i++)
{
a=i; //为每个数组元素编号.a不使用
}
i=1; //用于判断是否数到了数组的末尾
k=0; //记录当前的数到的数
t=0; //记录退出的人数
while(t<n-1) //当人数剩下一人是,退出循环
{
if(a!=0) //如果a的编号不为0,表明该人没有退出,计数值加1.
{
k++;
}
if(k==m) //如果计数值达到了预定的计数值,则报到该数的人退出,即将a置0.并且再次的初始化计数值为0;
{
printf("序号为 %d 的人出圈\n",a);
a=0;
k=0;
t++; //退出的人数加1
}
i++;
if(i==n+1) //表明到达了数组的末尾,改变i的值,重新指向数组的头部
{
i=1;
}
}
for(i=1;i<=n;i++)
{
if(a!=0) //寻找不为0的a,即为最后留下的人
{
printf("最后留下的是 %d 号\n",a);
}
}
return 0;
} #include<stdio.h>
int main()
{
int m,n,i,S=0;
printf("输入总共的人数:");
scanf("%d",&n);
printf("输入报数值:");
fflush(stdin);
scanf("%d",&m);
for(i=2;i<=n;i++)
{
S=(S+m)%i;
}
printf("最后出圈的人得序号为:%d\n",S);
return 0;
} #include<stdio.h>
#include<stdlib.h>
typedef struct Node //定义节点结构
{
int data;
struct Node *next;
}Node,*Linklist;
int CreatLinklist(Linklist *T,int n); //生成一个循环链表*T
int DeletNode(Linklist *q); //删除节点(*q)的下一个节点(*q)->next
int main()
{
int n,m,k=0; //k用于记录计数值
Linklist T=NULL,p=NULL,q=NULL;
printf("输入人数n=");
scanf("%d",&n);
printf("输入报数值m=");
fflush(stdin);
scanf("%d",&m);
T=(Linklist)malloc(sizeof(Node));
if(CreatLinklist(&T,n))
{
p=T;
while(p!=p->next) //当只剩一个节点时,退出循环.
{
k++;
if(k==m) //当计数值为k==m时,删除计数值为m的节点
{
printf("序号为 %d 的人出圈\n",p->data);
k=DeletNode(&q); //q为计数值为m的上一个节点(直接前驱)
p=q; //删除结束后,将当前的指针q的指向赋给p
}
q=p; //保存p的上一个节点的指针(即直接前驱)
p=p->next;
}
printf("最后留下的人的序号为 %d\n",p->data);
free(p);
}
else
{
printf("创建循环链表失败,退出程序\n");
system("pause");
exit(0);
}
system("pause");
return 0;
}
int CreatLinklist(Linklist *T,int n)
{
int i;
Linklist p=NULL,q=NULL;
p=(*T);
p->data=1;
p->next=NULL;
for(i=1;i<n;i++)
{
q=(Linklist)malloc(sizeof(Node));
q->data=i+1; //为每个人编排一个序号.
p->next=q; //挂链
p=q;
}
q->next=(*T); //指向第一个节点(即构成循环链表)
return 1;
}
int DeletNode(Linklist *q) //删除节点p的下一个节点.
{
Linklist p=NULL;
p=(*q)->next;
(*q)->next=p->next;
free(p); //注意内存的释放.
return 0;
} 代码有点挫 大家凑合着看吧 {:2_35:} 再增加一种队列实现的代码#include<stdio.h>
#include<stdlib.h>
typedef struct QNode //节点结构
{
int data;
struct QNode *next;
}QNode,*QueuePtr;
typedef struct //队列的链表结构
{
QueuePtr front,rear; //队头队尾指针
}ListQueue;
void CreatQueue(ListQueue *Q,int n); //创建一个队列
void DealQueue(ListQueue *Q,int m); //按条件出队和入队操作
int main()
{
int m,n;
ListQueue Q;
Q.front=NULL;
Q.rear=NULL; //初始化队头队尾指针
printf("输入总人数n=");
scanf("%d",&n);
fflush(stdin);
printf("输入报数值m=");
scanf("%d",&m);
CreatQueue(&Q,n);
DealQueue(&Q,m);
return 0;
}
void CreatQueue(ListQueue *Q,int n)
{
int i;
QueuePtr q=NULL,p=NULL; //定义完指针后,记得初始化.否则把指针"栓"在NULL处
q=(QueuePtr)malloc(sizeof(QNode));
q->data=1;
q->next=NULL;
Q->front=q; //队头指针指向第一个节点
for(i=1;i<n;i++)
{
p=(QueuePtr)malloc(sizeof(QNode));
p->data=(i+1); //为每个人编号
q->next=p; //挂链
q=p;
}
p->next=NULL;
Q->rear=p; //队尾指针指向最后一个节点
}
void DealQueue(ListQueue *Q,int m)
{
QueuePtr q=NULL,p=NULL;
int k=0; //计数值
while(Q->front!=Q->rear) //当队头指针和队尾指针不同时指向同一个节点时,循环继续
{
q=Q->front; //指针q用于记录当前访问的节点
k++; //计数值加1
if(k==m)
{
printf("序号为 %d 的人出圈\n",Q->front->data);//如果当前节点的报数为m,则该节点出队
Q->front=q->next; //头指针指向当前节点的直接后继
free(q); //释放当前节点
k=0; //计数值重新开始
}
else
{
Q->rear->next=q; //当前访问的节点入队尾 (只不过是把不满足报数值的节点从队头变到了队尾)
Q->rear=q; //当前访问的节点作为队尾
Q->front=q->next; //队头指针指向当前节点的直接后继(即当前节点出队)
}
}
printf("序号为 %d 的人最后留下\n",Q->front->data);
} 感谢楼主分享,收藏了先
页:
[1]