zoukankan      html  css  js  c++  java
  • 约瑟夫环问题

    问题:

      n个人围成一个圈,每个人分别标注为1、2、...、n,要求从1号从1开始报数,报到k的人出圈,接着下一个人又从1开始报数,如此循环,直到只剩最后一个人时,该人即为胜利者。

      例如当n=10,k=4时,依次出列的人分别为4、8、2、7、3、10,9、1、6、5,则5号位置的人为胜利者。

      给定n个人,请你编程计算出最后胜利者标号数。

    输入:

      第一行为人数n 
      第二行为报数k

    输出:

      最后胜利者标号数

    输入样例:

    10

    4

    输出样例:

    5

    解法:

      解法分为两种,一种是模拟,可以用循环链表,也可以用数组;另一种是通过递推得出公式。

    一、模拟

    链表模拟

     1 #include <bits/stdc++.h>
     2 using namespace std;
     3 typedef struct List{
     4     int data;
     5     struct List *next;
     6 }LinkList;
     7 int main(){
     8     LinkList *L,*r,*s;
     9     L = (LinkList *)malloc(sizeof(LinkList));
    10     r = L;
    11     int n,i,k;
    12     cin>>n>>k;
    13     for(i=1;i<=n;i++){
    14         s = (LinkList *)malloc(sizeof(LinkList));
    15         s->data = i;
    16         r->next = s;
    17         r=s;
    18     }
    19     r->next = L->next;//循环链表
    20     LinkList *p;
    21     p=L->next;
    22     while(p->next!=p){
    23         for(i=1;i<k-1;i++){
    24             p=p->next;//每k个数死一个人 
    25         }
    26         p->next=p->next->next;//将该节点从链表上删除
    27         p=p->next; 
    28     } 
    29     cout<<p->data<<endl;
    30     return 0;
    31 } 

    数组模拟

     1 #include <bits/stdc++.h>
     2 using namespace std;
     3 int main(){
     4     int n,k,a[1001];
     5     cin>>n>>k;
     6     int dead=0,num=0;
     7     for(int i=1;i<=n;i++){
     8         a[i]=i;
     9     } 
    10     for(int i=1;;i++){
    11         if(i>n)i=i%n;//如果大于总人数就从头开始 
    12         if(a[i]>0)num++;//如果当前这个人没有死,就报数 
    13         if(k==num&&dead!=n-1){
    14             num=0;
    15             a[i]=0;
    16             dead++;
    17         }else if(k==num&&dead==n-1){
    18             cout<<a[i]<<endl;
    19             break;
    20         }
    21     }
    22     return 0;
    23 }

    二、递推

    递推过程:

      ①第一个被删除的数为(m-1)%n;  

      ②设第二次的开始数字为k,

      做下映射:(即将数字的排列计算还是从0开始)

      k--->0

      k+1--->1 

      k+2--->2

      ---  ---

      k-2--->n-2 

      此时剩下n-1个人 ,假如我们已经知道了n-1个人时,最后胜利者的编号为x,利用映射关系逆推,就可以得出n个人时,胜利者的编号为(x+k)%n(要注意的是这里是按照映射后的序号进行的)

      其中k=m%n。

      代入 

      (x+k)%n

      <=>(x+(m%n))%n

      <=>(x%n + (m%n)%n)%n

      <=> (x%n+m%n)%n  

      <=> (x+m)%n 

      ③第二个被删除的数为 (m-1)%n-1

      ④假设第三轮的开始数字为o,那这n-2个数构成的约瑟夫环为o,o+1,o+2,...,o-3,o-2。

      映射 

      o--->0 

      o+1--->1  

      o+2--->2

        ---  ---  

      o-2--->n-3  

      这是一个n-2个人的问题。假设最后胜利者为y,那么n-1个人时,胜利者为(y+o)%(n-1),其中o等于m%(n-1)。

      代入可得(y+m)%(n-1)  

      要得到n-1个人问题的解,只需要得到n-2个人问题的解,倒退下去。只有一个人时,胜利者就是编号0.小面给出递推式:

        f(1)=0;

        f(i)=(f[i-1]+m)%i;(i>1) 


    这个公式的思想:

      现在假设n=10

      0 1 2 3  4 5 6 7 8 9 

      k=3 

      第一个人出列后的序列为:

       0 1 3 4 5 6 7 8 9 

      即:  3 4 5 6 7 8 9 0 1(1式) 

      我们把该式转化为: 0 1 2 3 4 5 6 7 8 (2式) 

      则你会发现: ((2式)+3)%10 则转化为(1式)了 。

     

      也就是说,我们求出9个人中第9次出环的编号,最后进行上面的转换就能得到10个人第10次出环的编号了。

      设f(n,k,i)为n个人的环,报数为k,第i个人出环的编号,则f(10,3,10)是我们要的结果。

      当i=1时,  f(n,k,i) = (n+k-1)%n

      当i!=1时,  f(n,k,i)= ( f(n-1,k,i-1)+k )%n

    1 #include<stdio.h>
    2 int main(){
    3     int n, m,i,s=0;
    4     scanf("%d%d",&n,&m);
    5     for(i=2;i<=n;i++)
    6         s=(s+m)%i;
    7     printf("%d", s+1);
    8     return 0;
    9 }

    说一下:

      for(i=2;i<=n;i++)
              s=(s+m)%i;

      这个式子:

      首先从2开始,因为1个人的时候报的数字的人为0号,结果已经确定了。不需要从i=0开始,要注意的是序列从0开始编号的,所以最后的输出结果也要加1。

      s表示的是上一轮的结果,m代表是每多少个人出列一次,i代表当前已经出列了多少个人。

      整个式子就是根据上一个的出列数和已经出列的人数来算的。

  • 相关阅读:
    ES6知识点整理之----解构赋值----对象
    ES6知识点整理之----解构赋值----数组
    ES6知识点整理之----声明变量总结与global
    ES6知识点整理之----const
    ES6知识点整理之----块级作用域
    ES6知识点整理之----let
    ES6知识点整理之----历史及简介
    推荐大家使用的CSS书写规范、顺序
    meta标签补充属性(viewport)
    meta标签属性总结
  • 原文地址:https://www.cnblogs.com/cruelty_angel/p/9755595.html
Copyright © 2011-2022 走看看