Josephus(约瑟夫斯)问题

Josephus问题是下面的这个游戏:有N个人坐成一圈,编号为1至N。从编号为1的人开始传递热马铃薯。M次传递之后,持有热马铃薯的人退出游戏,圈缩小,然后游戏从退出人下面的人开始,继续进行。最终留下来的人获胜。比如:M=0,N=5, 5号获胜,退出顺序为1、2、3、4,5获胜;M=1,N=5, 3获胜,退出顺序为2、4、1、5。

   思路一: 直接链表法。 将N个人排成环状队列,依次传递,通过程序说明(亲测无误):

 /*********************************************************************************

       头文件与命名空间

**********************************************************************************

 #include <iostream>     

 #include <list>    

 using std::cin;    

 using std::cout;    

 using std::endl;    

 using std::list;    

 using std::list<int>;

 int main()  

 {

 /*********************************************************************************

  准备工作

 *********************************************************************************/   

    int person;  // 人数   

    int transmit;  // 传递基数(即题中的M)    

    cout << "GAME: JOSEPHUS" << endl << "Nums of persons: " ;    

    cin >> person;    

    cout << "transmit: ";    

    cin >> transmit;    

 /********************************************************************************      

  将N个人装入循环链表

 ********************************************************************************/      

    int num = 1; // 人员序号    

    list<int> list_person;    

    list<int> :: iterator iter1 = list_person.begin();    

    while (num <= person)   

    {      

     list_person.push_back(num++);    

    } 

 /*********************************************************************************

   有N个人,会传递N-1次

 ********************************************************************************/    

    int N = person-1; // 传递次数    

    int m_transmit = 0; // 追随传递基数    

    while (N > 0)   // 游戏开始    

    {

      while (m_transmit<transmit && iter1!=list_person.end())    // 马铃薯还没有传递到末尾

      {

          m_transmit++;

          iter1++;

      }

     if (m_transmit < transmit || iter1 == list_person.end())  // 当传递到末尾一人但是传递基数还没到M,要将马铃薯从头开始继续传 

     {

         iter1 = list_person.begin();

        continue;

     }

     cout << "erase: " << *iter1 << endl;

     iter1 = list_person.erase(iter1);  // 持有马铃薯的人退出游戏

     m_transmit = 0;

     N--;    

  }    

  if (iter1 == list_person.end())  // 有可能最后一次淘汰完人,链表指针指向this.end()处,所以要让指针回指到开始处    

  {

       iter1 = list_person.begin();   

  }    

 cout << "The winner is: " << *iter1 << endl;    

 return 0;  

 }

运行结果图: M=1, N=5


 思路二:逆推法

       将游戏人数排成环状,如图a,为实验方便可将编号变为从0-N,如图b, 最后得出获胜编号为n,只要n+1就是实际编号

                

                                  图a                                                                                       图b

        假设N=7,m=3。走过3步之后,3号出队,然后再从4号开始继续查找,实际上从这个时候开始,正是从N-1个元素里开始取元素了。我们可以

将第二轮的元素从4号开始重新以元素0为起始顺序进行查找。

         一般的第一个人出队后,第一个出队的人编号必为m%N,剩下的N-1个人组成新的Josephus环,这时候以(m+1)%N开始。假定k=(m+1)%N,

他们组成新的序列,映射关系如下:

        k-->0

        k+1-->1

        k+2-->2

        ......

        k-3-->N-3

        k-2-->N-2

       k-1是前面一次遍历移除的。

                  

        说明对于N-1的环中,任何一个元素index对应到N的环中之间差了k,即(m+1)%N。这里的差不是直接相减差k,而是循环进位的结果。经分析可得出元素在N-1环和N环中对应的映射关系为index(N)=(index(N-1)+m+1)%N,接着移除元素,会发现映射关系index(N-1)=(index(N-2)+m+1)%(N-1);如此往复,会移除到只有一个元素的环,且index(1)=0,再逆着推,得index(2)=(index(1)+m+1)%2。通过归纳,得出数学规律: f(1)=0, f(N)=(f(N-1)+m)%N。 实际结果为f(N)+1。

        代码(亲测无误):

      #include <iostream>

     using std::cin;
     using std::cout;
     using std::endl;

     int person;  // N
     int transmit; // m

     inline void init()
    {
        cout << "Josephus Game:" << endl << "The Number of Person: ";
        cin >> person;
        cout << "transmit: ";
        cin >> transmit;
    }

     int main()
    {
        init();
        int index = 0;
        int person_trace = 1;
        for (; person_trace != person; ++person_trace)
       {
          index = (index+transmit+1) % (person_trace+1);
        }
          cout << index+1;
          return 0;
      }

   运行结果: N=7   m=1

    

    

      

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值