经典例题|约瑟夫环多方法解决

本文章将用循环链表、数组、递归以及循环方法对约瑟夫环问题进行讲解。其中链表法和数组法会对过程进行模拟,递归和循环将对约瑟夫环问题进行数学剖析。

问题描述

n个人围成圈,依次编号为1、2、3、...、n,从1号开始依次报数,当报到m时,报m的人退出,下一个人重新从1报起,当报到m时,报m的人退出,如此循环下去,问最后剩下的那个人的编号是多少?

链表法

建立一个循环链表,节点的数值部分存储整数1至n,将尾部节点链接到第一个节点,每次遍历m-2步,把第m-1个节的指针域指向的节点数据打印出来,然后将m--1这个节点的指针域指向m的指针域指向的节点,再用free将m这个节点内存释放.当只剩一个节点时,(只剩一个节点的判断方法是:节点的指向节点自己,也就是p->next=p),节点的数值部分就是最后那个人的编号。

代码附上

#include<stdio.h>
#include<stdlib.h>
typedef struct list1{
    int data;
    struct list1 *next;
}list;                          //声明一个链表节点
void func(int ,int ,list *);
void stamp(list *);
int main()
{
    int n,m;
    printf("请输入总人数、出列序号\n");
    scanf("%d%d",&n,&m);
    list *l=NULL;
    list *k;
    for(int i=n;i>=1;i--)                           //创建链表
    {
        list *p=(list *)malloc(sizeof(list));
        if(i==n)        k=p;
        p->data=i;
        p->next=l;
        l=p;
    }
        k->next=l;                          //将尾节点链接到第一个节点
        func(n,m,l);
        return 0;
}
void func(int n,int m,list *l)
{
    int num=n;
    stamp(l);
    while(l->next!=l)
    {
        for(int i=1;i<m-1;i++)
        {
        l=l->next;
        }
        printf("*********%d号出列*********\n",l->next->data);
        l->next=l->next->next;
        l=l->next;
        num--;
        if(num>1) stamp(l);
        else printf("%d号选手胜出",l->data);
    }
}
void stamp(list *l)                             //每次排除一个人就输出剩余人数(方便理解过程)
{
    list *temp=l;
    printf("剩下的选手为\n");
    do
    {
        printf("%d ",temp->data);
        temp=temp->next;
    }while(temp!=l);
    putchar(‘\n‘);
} 

数组法

和链表法相似,数组法也是模拟过程.用i来模拟当前人物对应相应数组下标,当i超出剩下人数时,i重归于0,即回到第一个人,从而达到循环的效果,s模拟当前已经历过的人数,当s==m时即淘汰当前人物,将数组从i这个位置往前移动,实现删除这个人的效果

#include<stdio.h>
void func(int n  ,int m );
int main()
{
    int m,n;
    printf("请输入共有多少人,出列编号\n");
    scanf("%d%d",&n,&m);            //n人数,m报到出列的号码
        func(n,m);
}
void func(int n,int m)
{
    int a[n],num=n,i,k;                     //num记录当前剩余人数
    for(i=0;i<n;i++)
        a[i]=i+1;
    i=0;
    while(num>1)
    {
        int s=0;                                //记录已经越过几个人
        for(;;i++)
        {
            if(i==num)
                i=0;
            s++;
            if(s==m)    break;                  //执行删除
        }
        printf("***********%d号出局***********\n",a[i]);
        for(int j=i;j<num;j++)                  //删除当前人
            a[j]=a[j+1];
        num--;
    }
    printf("%d号胜出\n",a[0]);             结束
}

数学法(递归.循环)

这个需要数学分析:

假如有6个人,编号为0,1,2,3,4,5,每次报到3的人出列(n=6,m=3);来模拟一下这个过程

第一次淘汰后 0,1,3,4,5 (1)

由于下一次是从3号开始我们可以改写为 3,4,0,1,2 (2)

第二次淘汰后 3,4,0,1 (1)

同理:我们可以改写为 0,1,2,3 (2)

第三次淘汰后 0,1,3 (1)

同理:我们可以改写为 1,2,0 (2)

第四次淘汰后 0,1 (1)

同理:我们可以改写为 0,1 (2)

第五次 1 (1)

改写为 0 (2)

通过观察,发现(1)式可由二式推导出来 例如第五次 ((2)+3)%2

第四次 ((2)+3)%3

......

第一次 ((2)+3)%6

可以发现规律就是(1)式可以由((2)+m)%x x为本轮剩余人数

这样的话我们可以利用递推来解决这个问题.无论你怎么淘汰最后一个剩下的人在(2)式的情况下一定是0,所以可以利用这个规律不断向前得到他原来的序号-1,因为我们是从0开始排序的,但是题目是从1开始排序的.验证一下

(0+3)%2=1,(1+3)%3=1,(1+3)%4=0,(0+3)%5=3,(3+3)%6=0;0+1=1,所以剩下的人应该为1,大家可以用笔验算是不是1,答案肯定是的.这样就可以写程序了.因为取余可能会得到0这个序号,所以我们排序从0开始排,就不用进行其他转化了

递归法

#include<stdio.h>
int func(int ,int );
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    printf("%d",func(n,m)+1);               //得到的数加1就是需要的序号
    return 0;
}
int func(int n,int m)
{
    if(n==1)                                //还剩一个人的时候返回0
        return 0;
    else return (func(n-1,m)+m)%n;      //还剩n个人时就返回((2)+m)%n这个数
}

运行结果,正确

循环法

我们利用递归由于不能及时清理现场会占用大量的资源,所以数据较大时会给计算机带来较大负荷,所以得换一个法子,就用循环将递归改造一下就行,因为循环一次清理一次现场,就不会出现上述现象

#include <stdio.h>
int main()
{
    int n,m,i,s=0;
    while( scanf("%d%d",&n,&m)==2)
    {
        s=0;
        for (i=2; i<n; i++)             //递归往里面进去,则循环从里面出来,用i代表剩余人数
            s=(s+m)%i;
        printf ("%d",s+1);              //输出结果
    }
    return 0 ;
}  

运行结果,大家可以验算一下,是没有错误的.经过一番推理长代码就剩下了几行,可见算法是十分重要的,但是当题目要求输出每一层被淘汰的人的话,这个程序就不适用的,可见代码各有各的好处,但是要根据题目要求来.

看了这么几种方法你学会了什么呢,欢迎加入piu小屋c语言学习群 进行交流.

piu小屋|全文结

原文地址:https://www.cnblogs.com/piuxw/p/10173818.html

时间: 2024-09-30 14:38:57

经典例题|约瑟夫环多方法解决的相关文章

约瑟夫环的java解决

总共3中解决方法,1.数学推导,2.使用ArrayList递归解决,3.使用首位相连的LinkedList解决 import java.util.ArrayList; /** * 约瑟夫环问题 * 需求:n个人围成一圈,从第一个人开始报数,数到K的人出局,然后从下一个人接着报数,直到最后一个人,求最后一个人的编号 * @author Miao * */public class Josephus { public static void main(String[] args) { int n =

POJ 3517 And Then There Was One (约瑟夫环问题)

经典的约瑟夫环问题嘛.有点小小的变形而已.给你N个人围成一个环(编号1~N),从第M个人开始,每隔K个人报一次数,报数的人离开该环. 求最后剩下的人的编号. 约瑟夫问题的数学递推解法: (1)第一个被删除的数为 (m - 1) % n. (2)假设第二轮的开始数字为k,那么这n - 1个数构成的约瑟夫环为k, k + 1, k + 2, k +3, .....,k - 3, k - 2.做一个简单的映射. k         ----->  0 k+1    ------> 1 k+2    

Roman Roulette(约瑟夫环模拟)

Roman Roulette Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 286    Accepted Submission(s): 105 Problem Description The historian Flavius Josephus relates how, in the Romano-Jewish conflict of

约瑟夫环问题-Java数组解决

约瑟夫环问题说的是,n个人围成一圈,从第k个人开始沿着一个方向报数,报到第m个人时,第m个人出列,从紧挨着的下一个人(未出列)开始,求整个环中人的出列顺序.下面是我用java实现的解决方法. 1 class JosephLoop 2 { //n为环中人数,m为每次报数的人数,k为报数的起始位置0-n 3 int n,m,k; 4 int[] persons; 5 int[] seq; 6 7 JosephLoop(int n, int k, int m){ 8 this.n = n; 9 thi

C++循环链表解决约瑟夫环问题

约瑟夫环问题可以简单的使用数组的方式实现,但是现在我使用循环链表的方法来实现,因为上午看到一道面试题规定使用循环链表解决约瑟夫环问题. 什么是约瑟夫环? “约瑟夫环是一个数学的应用问题:已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围.从编号为k的人开始报数,数到m的那个人出列:他的下一个人又从1开始报数,数到m的那个人又出列:依此规律重复下去,直到圆桌周围的人全部出列.”(百度百科中的解决办法列出了很多,可以看到循环链表并不是最简单的方法) 这道面试题考察了循环链表的“创建”,

三种方法求解约瑟夫环问题

约瑟夫环是一个数学的应用问题:已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围.从编号为k的人开始报数,数到m的那个人出列:他的下一个人又从1开始报数,数到m的那个人又出列:依此规律重复下去,直到圆桌周围的人全部出列. 方法1:使用stl::list模拟环形链表,参考剑指offer 代码: #include <iostream> #include <list> using namespace std; int lastNumber(unsigned int n,un

C语言用数组解决约瑟夫环问题

       在罗马人占领乔塔帕特后,39 个犹太人与约瑟夫及他的朋友躲到一个洞中,大家决定宁愿自杀也不要被敌人抓到,于是确定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止.然而约瑟夫和他的朋友并不想死去,那么他应该怎样安排他和他的朋友的位置,才能逃脱这场死亡游戏呢?         用C语言解决约瑟夫环问题的最佳方式是采用循环链表,但使用数组同样也可以解决瑟夫环的问题.采用循环链表的方法,以后详述.本节主

php解决约瑟夫环的问题

php里面解决约瑟夫环还是比较方面的,但是下面的方法太费空间 <?php class SelectKing{ private $m;//幅度 private $n;//总数 public function __construct($m,$n){ $this->m = $m; $this->n = $n; } public function getKing(){ $mokeys = range(1, $this->n); $tmp = 0; while(count($mokeys)&

C++ 用循环链表解决约瑟夫环问题

约瑟夫环问题 已知 n 个人(n>=1)围坐一圆桌周围,从 1 开始顺序编号,从序号为 1 的人开始报数,顺时针数到 m 的那个人出列.下一个人又从 1 开始报数,数到m 的那个人又出列.依此规则重复下去,直到所有人全部出列.请问最后一个出列的人的初始编号. 要求 输入人数 n,所报数 m,输出最后一个人的初始编号. 解决思路 首先因为是圆桌问题,使用链表解决的话需要构建循环链表. 接着是出列问题,这里我的设计思路是将指向链表的指针移动到需要出列的人的位置,然后根据正常的链表删除进行操作即可.