约瑟夫环问题小结

匿名 (未验证) 提交于 2019-12-03 00:40:02

一 问题描述

约瑟夫环问题的基本描述如下:已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为1的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,要求找到最后一个出列的人或者模拟这个过程。

二 问题解法

在解决这个问题之前,首先我们对人物进行虚拟编号,即相当于从0开始把人物重新进行编号,即用0,1,2,3,...n-1来表示人物的编号,最后返回的编号结果加上1,就是原问题的解(为什么这么做呢,下文有解释)。而关于该问题的解通常有两种方法:

1.利用循环链表或者数组来模拟整个过程。

具体来讲,整个过程很明显就可以看成是一个循环链表删除节点的问题。当然,我们也可以用数组来代替循环链表来模拟整个计数以及出列的过程。此处只给出利用数组来模拟这个过程的解法,最终结果为最后一个出列的人的编号:

 

#include<iostream> #include<unordered_map> #include<queue> #include<cstring> #include<cstdlib> #include<cmath> #include<algorithm> #include<sstream> #include<set> #include<map> using namespace std;   int main() {   int n,m;   cin>>n>>m;   vector<int>rs(n);   for(int i = 0 ; i < n; i++)     rs[i] = i + 1;//对人物重新进行编号,从0开始   int cur_index = 0;//当前圆桌状态下的出列人的编号   int out_cnt = 0;//用以表示出列的人数   int cnt = n;//表示当前圆桌的总人数   while(out_cnt < n - 1)//当out_cnt等于n-1时,循环结束,此时圆桌师生最后一个人,即我们要的结果   {         if(cur_index + m > cnt)         {             if((cur_index + m) % cnt == 0)//这种情况需要单独考虑,否则cur_index就变成负值了                 cur_index = cnt - 1;             else                 cur_index = (cur_index + m) % cnt - 1;         }         else             cur_index = cur_index + m - 1;         cnt--;         out_cnt++;         cout<<"当前出列的为:"<<*(rs.begin() + cur_index)<<endl;         rs.erase(rs.begin() + cur_index);//从数组中删去需要出队的人员   }   cout<<"最后一个出列的人物为 :"<<rs[0]<<endl; } 

 

该方法的时间复杂度为O(n),空间复杂度为O(n),整个算法的基本流程还是比较清晰的,相当于每次循环更新cur_cnt、cnt和out_cnt这三个变量,当out_cnt == n-1时,此时出队的人数一共有n-1人,圆桌上只剩下一个人了,停止循环。此外,该算法有几点需要注意:

(1)首先,我们为什么要对用户进行重新编号(从0开始到n-1),在我看来,这是因为在整个循环过程中我们用到了对当前圆桌人数总数cnt进行了取余的操作,而取余的结果包括0到cnt -1,即包括0;如果编号是从1开始的话,在余数为0的时候需要特殊处理,而从0开始编号的话,一方面符合编程习惯(下标从0开始计数);另一方面面对取余操作不需要特殊处理。

(2)代码中的cur_index指的当前圆桌状态下需要出队的人的编号,即数到m的人的编号(此处的编号指的是重新编号的编号);由于cur_index的值表示的是重新编号后的编号,但它的初值表示的是最开始数数的那个人的编号(初始值的时候就不表示需要出队的人的编号了),由于题目要求的是从编号为1的人开始数数,并且其对应的新编号为0,故cur_index的初值为0。此外,在循环计算cur_index时,我们发现不论是哪种情况,cur_index更新值都有一个减1的操作;这是因为cur_index每次加m得到的值或者加m再取余得到的值,实际上是需要出队人员的原始编号(即从1开始到n结束的那个编号),而cur_index应该表示的是重新编号后的编号,而新编号比旧编号小1,所以需要减去1,这其实可以看成是一个规律。此处可以举个例子,例如:对于n=5,m=3;cur_index的初值为0,cur_index + 3 <5,所以cur_index + m = 3(不用进行取余操作了),如果不减1的话,3表示的是新编号,对应的旧编号就是4,而实际上应该出队的人员的编号是3,对应的新编号是2。

(3)数组rs的下标相当于新编号,而数组存储的内容相当于旧编号,rs每次删除的元素对应每次出队的人员的编号,在这里我们需要了解erase的原理,rs每删除一个元素时,被删除之后的元素就会前移,相当于新旧编号的对应关系也发生了变化,即下一个开始数数的人占据了之前出队人员的位置,它的新编号发生了变化;而对向量进行erase操作后元素移动的原理实质和圆桌人员移动的情况是一致的啊,然后结合cur_index进行操作(把vector进行erase操作后移动元素的原理看成是圆桌人员移动),所以说能利用vector代替循环链表模拟整个过程。

 

2.利用数学推导得出的公式直接求解。

方法1中利用了数组直接进行过程模拟,但空间复杂度比较高,下面给出一种更为常见的方法,即直接对整个过程归纳出一个数学公式来。公式的具体推导本文不详细描述,可参考:https://blog.csdn.net/wusuopubupt/article/details/18214999

上文中给出了公式$f(i)=[f(i-1) + m] \% i$ 其中$f(i)$表示的是当圆桌人数为$i$时,应该出队人员的编号

可用递归和迭代实现

三 问题变种

原文:https://www.cnblogs.com/wangkundentisy/p/9278693.html

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!