zoukankan      html  css  js  c++  java
  • [College] 《C++程序设计-实践教程》约瑟夫问题

    约瑟夫问题

    题目描述

    n个人围成一圈,从编号为1的开始从1~m报数,报到m的人离开,接着再从下一个人重新报数。这样反反复复,直到只剩下一个人,求编号为多少的人留下?

    输入格式

    两个整数n、m。n表示总人数,m表示报数的截止上限。

    输出格式

    一个整数,表示剩下的人的编号。

    AC代码

    链表版(模拟) O(nm)

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    
    using namespace std;
    
    struct  node
    {
        int next;//记录当前参与者的后一位的位置
        int post;//记录当前参与者的前一位的位置
    };
    node circle[1000];
    
    int main()
    {
        int num,exit_num,present=1;//定义:剩余人数num,报数上限exit_num,当前位置present
        cout<<"Please input the number of people: "<<endl;
        cin>>num;
        cout<<"Please input the exit number: "<<endl;
        cin>>exit_num;
    
        //初始化链表为一个圈
        for(int i=1;i<=num;i++)
        {
            circle[i].next=i+1;
            circle[i].post=i-1;
        }
        circle[num].next=1;//最后一位的下一个为第一位
        circle[1].post=num;//第一位的上一位为最后一位
    
        while(num>1)
        {
            for(int i=1;i<exit_num;i++)//模拟报数
                present=circle[present].next;
            circle[circle[present].post].next=circle[present].next;//对于退出者进行删除操作:上一位的下一位为当前的下一位;下一位的上一位为当前的上一位
            circle[circle[present].next].post=circle[present].post;
            present=circle[present].next;//从当前的下一位再一次开始报数
            num--;//更新剩余人数
        }
        cout<<present<<endl;
        system("pause");
        return 0;
    }

    采用链表的优势就是:可以非常出色地维护一个环形的结构,并且避免了处理跳过不参与报数的位置,而且在效率上——由于直接跳过了退出的位置——相较于一般的数组更为优秀~ 

    不过这个是下学期才学的知识......

    数组版(模拟) O(nm)

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    
    using namespace std;
    
    bool circle[1000];
    
    int main()
    {
        int num,exit_num,present=1,cnt;//定义num为初始参与人数,exit_num为报数的上限,present为当前位置,cnt为剩余人数
        cout<<"Please input the number of participants: "<<endl;
        cin>>num;
        cout<<"Please input the exit number"<<endl;
        cin>>exit_num;
    
        cnt=num;//初始化cnt为总人数
        while(cnt>1)
        {
            for(int i=1;i<=exit_num;i++)//模拟报数
            {
                if(circle[present]==true)//若当前位置的参与者已经退出,则不计入报数,还原i值
                    i--;
                else if(i==exit_num)//若当前参与者未退出,且报数已经达到上限,则令其退出
                    circle[present]=true;
                present=(present==num)?(1):(present+1);//更新当前位置为下一位
            }
            cnt--;//更新剩余人数
        }
        for(int i=1;i<=num;i++)//遍历整个数组,寻找剩下的唯一参与者
            if(circle[i]==false)
                cout<<i<<endl;
        system("pause");
        return 0;
    }

    数组版的代码要相对复杂一点,原因在于我们不能对数组结构中的元素进行“删除”操作,这使得我们在遍历时(也就是模拟报数过程时)要考虑已经不参与报数的位置,在更新当前位置present时也要注意模拟环状的结构。另外,由于我们无法通过present的值直接确定答案,我们在最后需要单独从1~num遍历一次数组,找到尚未被标记的位置并输出。

    递推版(数学技巧) O(n)

    #include<iostream>
    #include<cstdlib>
    #include<cstdio>
    
    using namespace std;
    
    int main()
    {
        int num,exit_num,f=0;//定义总人数为num,报数的截止上限为exit_num
        cout<<"Please input the number of people: "<<endl;
        cin>>num;
        cout<<"Please input the exit number: "<<endl;
        cin>>exit_num;
    
        for(int i=1;i<=num;i++)
            f=(f+exit_num)%i;
    
        cout<<f+1<<endl;
    
        system("pause");
        return 0;
    }

    啧啧啧,是不是感觉清爽了不少?这个代码是如此的简洁,让我们来着重了解一下~

    为方便叙述,我们先将num个人从0~(num-1)编号。

    接着,我们不妨假设有num个人,我们很容易得出,第一个退出的人的编号肯定是k=(exit_num−1)%num。当编号为k的人退出后,接下来我们可以将问题看作:从原编号为k+1的人开始重新编号,然后就是一个人数为num−1个人的子问题了。这里其实包含了递归的思想,本题可用递归法求解~ (无论是递归还是递推,其实两者思路基本一致)

    当然,这num−1个人的答案并不是最终答案,因为它是重新编号后的答案,所以我们要将其恢复原编号。

    怎么恢复呢???我们发现,新编号为0的人前面有exit_num−1个人,也就是说,新的编号相对于原来的编号左移了exit_num,所以最终的答案应该要加上exit_num。

    那么我们就可以得到递推公式:设f[n]为总人数为n时最后剩下的人的编号,则有f[n]=(f[n−1]+exit_num)%n(模n是为了防止计算所得值溢出),并且易得f(1)=0,可借此进行循环递推。

    当n=num时,f[num]就是最后剩余人的编号。由于我们是按0~(num-1)编号,而题目要求1~num编号,因此答案应为f[num]+1。

    递归实现 O(n)

    #include<iostream>
    #include<cstdlib>
    #include<cstdio>
    
    using namespace std;
    
    int work(int n,int m)
    {
        if(n==1)
            return 0;
        return (work(n-1,m)+m)%n;
    }
    
    int main()
    {
        int num,exit_num;//定义总人数为num,报数的截止上限为exit_num
        cout<<"Please input the number of people: "<<endl;
        cin>>num;
        cout<<"Please input the exit number: "<<endl;
        cin>>exit_num;
    
        int ans=work(num,exit_num)+1;
        cout<<ans<<endl;
    
        system("pause");
        return 0;
    }
  • 相关阅读:
    定位图片的特殊例子+上传图片
    mysql 视图 安全性( mysql 表能读,但是视图不能读问题 )
    关于mysql 的 autoCommit 参数
    @Transactional 可以写在 Controller 方法上面了
    微信 支付宝 同时支付一个订单的解决方案
    Illegalmixofcollations (utf8_unicode_ci,IMPLICIT) and (utf8_general_ci,IMPLICIT)foroperation '= 连表查询排序规则问题
    Transaction rolled back because it has been marked as rollback-only 原因 和解决方案
    RabbitMQ 死信队列 延时
    好久没考虑过的 sql 注入
    基于redis的 分布式锁 Java实现
  • 原文地址:https://www.cnblogs.com/SinGuLaRiTy2001/p/11734342.html
Copyright © 2011-2022 走看看