约瑟夫斯环 (减治法)

题目:

约瑟夫斯是一位著名的犹太历史学家,参加并记录了公园66~70年犹太人反抗罗马的起义。约瑟夫斯作为一个将军,设法守住裘达波特的堡垒47天,但在城市陷落之后,他和40名顽强的将士在一个洞穴中避难。在哪里,叛乱者表示“要投降毋宁死”。于是约瑟夫斯建议每个人应该轮流杀死他旁边的人,而这个顺序抽签决定。约瑟有预谋的拿到了最后一签,成为了幸存者。

我们让n个人围成一个圈,并编号1~n,从编号1的人开始计数,每次消去第二个人,知道留下最后幸存者。这个问题要求出幸存者的号码J(n)。如果n=6,第一轮,2、4、6位置上的人被杀,然后3,5补刀2,3的位置上,第二轮1、3被杀,5幸存下来。J(6)=5;如果n=7,第一轮,2,4,6,1被杀,第二轮3,5被杀,J(7)=7;

算法分析:

将人数分为基数n,偶数n考虑。如果n为偶数,n=2k,一轮之后,规模减半。3到了2个位置,5到了3的位置,7到了4的位置。为了得到一个人的初始位置,需要将他的新位置x2-1,对于幸运者这个关系会一直持续下去:

J(2K)=2J(K)-1

对于基数n,n=2k+1,第一轮会消去所有偶数位的人,同时把位置1的人消去,留下了一个规模为k的例子,新位置编号和旧位置之间的关系为:

J(2K+1)=2J(K)+1

为了得到这2个递推式的闭合式,一般反向替换法,还可以用前向替换法,比如求出J(N)的前15个值,用数学归纳法证明合理性。而在这个案例中,规模n刚好可以用2进制表示:我们可以对n本身做一次向左的循环唯一来得到J(n)。如J(6)=J(110)(2进制)=101(2进制向左移位)=5,J(7)=J(111)(2进制)=111(2进制移位后)=7。

 

在网上看了下还有,对于移动位数不确定的情况,可以用循环链表实现。附上代码

#include<iostream>
#include<list>
#include<cstdlib>
using namespace std;
 
int main(int argc, char* argv[])
{  
    int total=0;  
    cout<<"Please input the total num:";  
    cin>>total;    
    int keyNum=0;  
    cout<<"Please input the key num:";  
    cin>>keyNum;    
    if(keyNum<keyNum || keyNum<1 || total<1)  
    {   
        cout<<"input error!"<<endl;   
        system("paused");  
    }    
      
    list<int>* table=new list<int>();    
    for(int i=1; i<=total; ++i)  
    {   
        table->push_back(i);  
    }    
      
     int shout=1;    
     for(list<int>::iterator it=table->begin(); table->size()!=1;)  
     {   
         if(shout++==keyNum)   
         {    
             //cout<<*it<<endl;    
             it=table->erase(it);    
             shout=1;    
         }   
         else   
         {    
             ++it;   
         }      
          
         if(it==table->end())   
         {    
             it=table->begin();   
         }  
     }    
     cout<<"The last one is:";   
     cout<<*table->begin()<<endl;    
     system("pause");  
     return 0; 
 }

 

posted @ 2015-10-26 20:19  SeeKHit  阅读(1382)  评论(0编辑  收藏  举报