约瑟夫问题(Josephus problem)

来源:互联网 发布:淘宝拍照技巧小摄影棚 编辑:程序博客网 时间:2024/06/06 19:56

问题概述: 

非原装正版问题,但是道理还是那个道理

N个人编号1~N,围坐成一个圆圈。从1号人开始传递一个热土豆,经过M次传递后那这热土豆的人被清除离座,由坐在后面的人拿起热土豆继续进行游戏。最后剩下的人获胜。
ex: M=0,N=5 清除顺序:1->2->3->4->5   ;   M=1,N=5 清除顺序:2->4->1->5->3;
(注意的是:这个问题描述和网上的部分问题描述不一致,在于 每次 开始都会从被排除的下一个人开始,M是 间隔gap,而不是报数的个数number。)

ps:书上给出的代码我已经改成我的理解,以及最后的数学方法也是按自己的理解得到输出结果。

代码实现:

list和iterator:

(书上给出的答案加以修改)

按照作者的观点,每次得到 M' =M mod N,这样来避免由于M>N造成的转圈重复计数,然后分出两种情况来优化每次循环的次数即M‘,情况如下 
  • M’>N/2 , 从当前位置反方向走 即 itr--
  • M'<=N/2  ,  从当前位置正方向走 即 itr++
用了 list 给出的 删除erase 和 iterator 给出的位置,故每次删除是真的从这一圈人中去除了,人数也会相应的减少。
优点:可以精神集中在解决 主要问题上,且 易于理解和实现。

时间复杂度:

T(N)=O(MN) 由于有N个数,然后每次最多移动M次

#include#includeint main(){int n, m, i, j, num, mp;cout << "Please input the number of people N and the gap M:" << endl;cin >> n >> m;num = n;           //num存储每次的剩余人数list L;for (i = 1; i <= n; ++i) L.push_back(i);list::iterator ite = L.begin();for (i = 0; i < n; ++i) {mp = m%num;    //防止重复计数浪费资源if (mp <= num / 2)for (j = 0; j < mp; ++j) {++ite;if (ite == L.end())ite = L.begin();}else {       mp = num - mp;  //反方向走是距离差for (j = 0; j < mp; ++j)if (ite == L.begin())ite = --L.end();elseite--;}num--;cout << *ite;ite = L.erase(ite);if (ite == L.end())ite = L.begin();}system("pause");return 0;}

array:

把上述问题用数组实现,由于数组是连续分配的一段空间,不能真的删除,那对应该删除的人,只是标记上该元素被删除(使用一个附加的位域(bin field),我就是用一个bool数组Tag 来标记:true 未删除 |  false 删除)

循环的时候,如果Tag[index]==true那么在M的循环中计数,当计数完成后,还要确定下一个是否也满足未删除状态,直到找到一个未删除的,然后将其删除

ps:应该也可以把标记数组,记为0和1这样可以用数字的累加代替判断

优点:可以体验全过程自己实现,以及能正确实现的快感...

时间复杂度:

T(N)=O(N^2) 由于有N个数,然后  M<=每次移动<N,因为要依次判断每个元素是否被删除...

void Josephus(int N, int M){int index = 0;int temp = 0;int num = N; //剩余人数int rem = M; //每次的间距int * a = new int[N];for (int i = 0; i < N; ++i) a[i] = i + 1;bool * Tag = new bool[N];memset(Tag, true, N);for (int i = 0; i < N; ++i) {rem = M % num;//计数循环while (temp < M) {if (Tag[index] == true )++temp;index = (index + 1) % N;}//未被淘汰的第一个while (Tag[index] == false)index = (index + 1) % N;cout << a[index] << " ";Tag[index] = false;--num;temp = 0;//恢复到初始条件}delete[]Tag;}int main(){int N, M;cout << "Please input the number of people N and the gap M: " << endl;cin >> N >> M;Josephus(N, M);system("pause");return 0;}

math:

约瑟夫环问题的简单解法(数学公式法)  点击打开链接

算法的关键就是唯一的那个循环,那个循环是从2个元素开始,因为结合规律已经知道了函数:
f[1]=0;
f[i]=(f[i-1]+m+1)%i;  (i>1)

当然我承认我没看懂文章最后的吉大的那个算法,然后自己改了相关的部分,写出了符合本题的代码( 从每次的循环m变为m+1,最后输出的是s+1,自己从头写一次就会找到规律)

//只能推算最终结果/*原版 从0开始编号,报m-1的被排除f(1)=0  f(n)=[f(n-1)+m]%nint main(){int n, m, i, s = 0;printf("N M = ");cin >> n >> m;for (i = 2; i <= n; i++){s = (s + m) % i;}cout << s ;  //编号从0开始system("pause");}*///此时需要从1开始编号了,所以结果+1,报m的排除int main(){int n, m, i, s = 0;printf("N M = ");cin >> n >> m;for (i = 2; i <= n; i++)s = (s + m + 1) % i;cout << s + 1 ;  //编号从0开始,但是要求从1system("pause");return 0;}

优点:跑的快,而且线性增长,代码量小
缺点:想不出来,别人想出来,自己需要消化

时间复杂度:

T(N)=O(N^2) 无话可说。。。