鱼C论坛

 找回密码
 立即注册
查看: 2608|回复: 6

[技术交流] 约瑟夫环的三种解法

[复制链接]
发表于 2014-4-19 03:15:18 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
约瑟夫环问题的具体描述是:设有编号为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]表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是f[n]
递推公式
f[1]=0;
f[i]=(f[i-1]+m)%i;   (i>1)
有了这个公式,我们要做的就是从1-n顺序算出f的数值,最后结果是f[n]。因为实际生活中编号总是从1开始,我们输出f[n]+1

解法三:链表
构造一个循环链表,每经过一个数据元素,则将计数器数加1.当计数器加到m时,移除该链表元素,将计数器置为0,继续往下数。
直到链表长度为1.移除元素的顺序即为n个人出圈的次序。算法复杂度为O(nm).
  1. #include<stdio.h>
复制代码



小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2014-4-19 03:18:47 | 显示全部楼层
  1. #include<stdio.h>                  //约瑟夫环问题
  2. #include<stdlib.h>
  3. int main()
  4. {
  5.         int n,i,*a,m;
  6.         int k,t;
  7.         printf("输入总共的人数:");
  8.         scanf("%d",&n);
  9.         printf("输入报数值:");
  10.         fflush(stdin);
  11.         scanf("%d",&m);
  12.         a=(int *)malloc(sizeof(int)*(n+1));  //动态分配内存,建立一个int a[n+1]的数组.
  13.         for(i=1;i<=n;i++)
  14.         {
  15.                 a[i]=i;                      //为每个数组元素编号.a[0]不使用
  16.         }

  17.         i=1;                             //用于判断是否数到了数组的末尾
  18.         k=0;                             //记录当前的数到的数
  19.         t=0;                             //记录退出的人数
  20.         while(t<n-1)                     //当人数剩下一人是,退出循环
  21.         {
  22.                 if(a[i]!=0)                  //如果a[i]的编号不为0,表明该人没有退出,计数值加1.
  23.                 {
  24.                         k++;
  25.                 }
  26.                 if(k==m)                     //如果计数值达到了预定的计数值,则报到该数的人退出,即将a[i]置0.并且再次的初始化计数值为0;
  27.                 {
  28.                         printf("序号为 %d 的人出圈\n",a[i]);
  29.                         a[i]=0;
  30.                         k=0;
  31.                         t++;                     //退出的人数加1
  32.                 }
  33.                 i++;
  34.                 if(i==n+1)                   //表明到达了数组的末尾,改变i的值,重新指向数组的头部
  35.                 {
  36.                         i=1;
  37.                 }
  38.         }
  39.         for(i=1;i<=n;i++)
  40.         {
  41.                 if(a[i]!=0)                  //寻找不为0的a[i],即为最后留下的人
  42.                 {
  43.                         printf("最后留下的是 %d 号\n",a[i]);
  44.                 }
  45.         }
  46.         return 0;
  47. }
复制代码
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2014-4-19 03:19:22 | 显示全部楼层
  1. #include<stdio.h>

  2. int main()
  3. {
  4.         int m,n,i,S=0;
  5.         printf("输入总共的人数:");
  6.         scanf("%d",&n);
  7.         printf("输入报数值:");
  8.         fflush(stdin);
  9.         scanf("%d",&m);
  10.        
  11.         for(i=2;i<=n;i++)
  12.         {
  13.                 S=(S+m)%i;
  14.         }
  15.         printf("最后出圈的人得序号为:%d\n",S);
  16.        
  17.         return 0;
  18. }
复制代码
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2014-4-19 03:20:00 | 显示全部楼层
  1. #include<stdio.h>
  2. #include<stdlib.h>

  3. typedef struct Node     //定义节点结构
  4. {
  5.         int data;
  6.         struct Node *next;
  7. }Node,*Linklist;

  8. int CreatLinklist(Linklist *T,int n);   //生成一个循环链表*T
  9. int DeletNode(Linklist *q);             //删除节点(*q)的下一个节点(*q)->next
  10. int main()
  11. {
  12.         int n,m,k=0;                        //k用于记录计数值
  13.         Linklist T=NULL,p=NULL,q=NULL;
  14.         printf("输入人数n=");
  15.         scanf("%d",&n);
  16.         printf("输入报数值m=");
  17.         fflush(stdin);
  18.         scanf("%d",&m);

  19.         T=(Linklist)malloc(sizeof(Node));

  20.         if(CreatLinklist(&T,n))
  21.         {
  22.                 p=T;
  23.                 while(p!=p->next)              //当只剩一个节点时,退出循环.
  24.                 {
  25.                         k++;
  26.                         if(k==m)                   //当计数值为k==m时,删除计数值为m的节点
  27.                         {
  28.                                 printf("序号为 %d 的人出圈\n",p->data);
  29.                                 k=DeletNode(&q);       //q为计数值为m的上一个节点(直接前驱)
  30.                                 p=q;                   //删除结束后,将当前的指针q的指向赋给p
  31.                         }
  32.                         q=p;                       //保存p的上一个节点的指针(即直接前驱)
  33.                         p=p->next;
  34.                 }
  35.                 printf("最后留下的人的序号为 %d\n",p->data);
  36.                 free(p);
  37.         }
  38.         else
  39.         {
  40.                 printf("创建循环链表失败,退出程序\n");
  41.                 system("pause");
  42.                 exit(0);
  43.         }
  44.         system("pause");
  45.         return 0;
  46. }

  47. int CreatLinklist(Linklist *T,int n)
  48. {
  49.         int i;
  50.         Linklist p=NULL,q=NULL;
  51.         p=(*T);
  52.         p->data=1;
  53.         p->next=NULL;
  54.         for(i=1;i<n;i++)
  55.         {
  56.                 q=(Linklist)malloc(sizeof(Node));
  57.                 q->data=i+1;                        //为每个人编排一个序号.
  58.                 p->next=q;                          //挂链
  59.                 p=q;       
  60.         }
  61.         q->next=(*T);                       //指向第一个节点(即构成循环链表)
  62.         return 1;
  63. }
  64. int DeletNode(Linklist *q)           //删除节点p的下一个节点.
  65. {
  66.         Linklist p=NULL;
  67.         p=(*q)->next;
  68.         (*q)->next=p->next;
  69.         free(p);                         //注意内存的释放.
  70.         return 0;
  71. }
复制代码
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2014-4-19 03:21:31 | 显示全部楼层
代码有点挫   大家凑合着看吧   {:2_35:}
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2014-4-19 14:50:49 | 显示全部楼层
再增加一种队列实现的代码
  1. #include<stdio.h>
  2. #include<stdlib.h>

  3. typedef struct QNode        //节点结构
  4. {
  5.         int data;
  6.         struct QNode *next;
  7. }QNode,*QueuePtr;

  8. typedef struct              //队列的链表结构
  9. {
  10.         QueuePtr front,rear;     //队头队尾指针
  11. }ListQueue;

  12. void CreatQueue(ListQueue *Q,int n);             //创建一个队列
  13. void DealQueue(ListQueue *Q,int m);              //按条件出队和入队操作

  14. int main()
  15. {
  16.         int m,n;
  17.         ListQueue Q;
  18.         Q.front=NULL;
  19.         Q.rear=NULL;             //初始化队头队尾指针
  20.         printf("输入总人数n=");
  21.         scanf("%d",&n);
  22.         fflush(stdin);
  23.         printf("输入报数值m=");
  24.         scanf("%d",&m);

  25.         CreatQueue(&Q,n);
  26.         DealQueue(&Q,m);

  27.         return 0;

  28. }

  29. void CreatQueue(ListQueue *Q,int n)              
  30. {
  31.         int i;
  32.         QueuePtr q=NULL,p=NULL;                     //定义完指针后,记得初始化.否则把指针"栓"在NULL处
  33.         q=(QueuePtr)malloc(sizeof(QNode));
  34.         q->data=1;
  35.         q->next=NULL;
  36.         Q->front=q;                                 //队头指针指向第一个节点
  37.         for(i=1;i<n;i++)
  38.         {
  39.                 p=(QueuePtr)malloc(sizeof(QNode));
  40.                 p->data=(i+1);                           //为每个人编号
  41.                 q->next=p;                               //挂链
  42.                 q=p;
  43.         }
  44.         p->next=NULL;
  45.         Q->rear=p;                                   //队尾指针指向最后一个节点
  46. }

  47. void DealQueue(ListQueue *Q,int m)
  48. {
  49.         QueuePtr q=NULL,p=NULL;
  50.         int k=0;                                     //计数值
  51.         while(Q->front!=Q->rear)                     //当队头指针和队尾指针不同时指向同一个节点时,循环继续
  52.         {
  53.                 q=Q->front;                               //指针q用于记录当前访问的节点
  54.                 k++;                                      //计数值加1
  55.                 if(k==m)
  56.                 {
  57.                         printf("序号为 %d 的人出圈\n",Q->front->data);  //如果当前节点的报数为m,则该节点出队
  58.                         Q->front=q->next;                      //头指针指向当前节点的直接后继
  59.                         free(q);                               //释放当前节点
  60.                         k=0;                                   //计数值重新开始
  61.                 }
  62.                 else
  63.                 {
  64.                         Q->rear->next=q;                       //当前访问的节点入队尾    (只不过是把不满足报数值的节点从队头变到了队尾)
  65.                         Q->rear=q;                             //当前访问的节点作为队尾
  66.                         Q->front=q->next;                      //队头指针指向当前节点的直接后继(即当前节点出队)
  67.                 }
  68.         }
  69.         printf("序号为 %d 的人最后留下\n",Q->front->data);
  70. }
复制代码
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2014-4-19 17:21:59 | 显示全部楼层
感谢楼主分享,收藏了先
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-4-20 01:46

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表