约瑟夫斯环 (减治法)
题目:
约瑟夫斯是一位著名的犹太历史学家,参加并记录了公园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; }