鱼C论坛

 找回密码
 立即注册
查看: 2294|回复: 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).
#include<stdio.h>


想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

 楼主| 发表于 2014-4-19 03:18:47 | 显示全部楼层
#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[n+1]的数组.
        for(i=1;i<=n;i++)
        {
                a[i]=i;                      //为每个数组元素编号.a[0]不使用
        }

        i=1;                             //用于判断是否数到了数组的末尾
        k=0;                             //记录当前的数到的数
        t=0;                             //记录退出的人数
        while(t<n-1)                     //当人数剩下一人是,退出循环
        {
                if(a[i]!=0)                  //如果a[i]的编号不为0,表明该人没有退出,计数值加1.
                {
                        k++;
                }
                if(k==m)                     //如果计数值达到了预定的计数值,则报到该数的人退出,即将a[i]置0.并且再次的初始化计数值为0;
                {
                        printf("序号为 %d 的人出圈\n",a[i]);
                        a[i]=0;
                        k=0;
                        t++;                     //退出的人数加1
                }
                i++;
                if(i==n+1)                   //表明到达了数组的末尾,改变i的值,重新指向数组的头部
                {
                        i=1;
                }
        }
        for(i=1;i<=n;i++)
        {
                if(a[i]!=0)                  //寻找不为0的a[i],即为最后留下的人
                {
                        printf("最后留下的是 %d 号\n",a[i]);
                }
        }
        return 0;
}
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2014-4-19 03:19:22 | 显示全部楼层
#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;
}
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2014-4-19 03:20:00 | 显示全部楼层
#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;
}
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2014-4-19 03:21:31 | 显示全部楼层
代码有点挫   大家凑合着看吧   {:2_35:}
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2014-4-19 14:50:49 | 显示全部楼层
再增加一种队列实现的代码
#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);
}
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2014-4-19 17:21:59 | 显示全部楼层
感谢楼主分享,收藏了先
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

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

本版积分规则

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

GMT+8, 2024-12-26 02:07

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

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