1.关于约瑟夫问题
约瑟夫斯领导犹太人反抗罗马帝国的统治,在与罗马军队的激烈战斗中,与士兵们一同被困在一个山洞里。总共有41人,约瑟夫斯希望向罗马军队投降,但他的士兵们却坚决拒绝,宁愿死也不愿被敌人俘虏。面对这种困境,约瑟夫斯需要找到一种既能实现投降目的,又能确保自己安全的解决方案。
于是,他提出了一个方案:让大家围成一个圈,按照顺时针的顺序,每数到第m个人就杀掉,这个过程不断重复,直到最后只剩下一个人。这样,通过数学计算和策略安排,约瑟夫斯能够确保自己不被处决,同时实现投降的目的。士兵们接受了这个方案,并按照约瑟夫斯的要求行动起来。
例如,有7个人围成一圈,按顺序编号,约定数到3的人自杀,那么顺序如下:
第一次报数: 3号被淘汰。
第二次报数: 6号被淘汰。
第三次报数: 2号被淘汰。
第四次报数: 7号被淘汰。
第五次报数: 5号被淘汰。
第六次报数: 1号被淘汰。
最后存活的十是4号。
2.题目描述
对于给定的n和m,分别表示总人数和要报的数字,求最后剩余的人是几号?
输入:7 3
输出:4
3.算法1 (队列实现)
思考报数过程,其报数规律如下:
1 2 3 4 5 6 7 (3应该被淘汰,接下来的报数顺序应该是如下)4 5 6 7 1 2 (数到7后继续从1开始,模拟环,此时6应该被淘汰,变成)7 1 2 4 5 (同上,接下来2该被删除,变成)4 5 7 1 (7被删除)1 4 5 (5被删除)1 4 (1被删除)4 //即存活的人
可以发现,数字顺序的变化规律,就是将报到m之前的人(m-1)移到最后面来模拟环,之后再删除要删除的数即可。在数列的前面进行删除操作,在数列后面进行删除操作,故可以采用队列这种数据结构。
实现函数如下:
/* 队列求解 */
int getAnsByQueue(int n,int m){queue<int> q;for(int i=1; i<=n; i++){ //按序号添加人q.push(i);}while(q.size() != 1){for(int i=0;i<m-1;i++){ //把报到m之前的人移到队列最后q.push(q.front()); q.pop();}q.pop(); //删掉淘汰的人}return q.front(); //返回幸存的那个人
}
4.算法2 (链表实现)
该算法的思想是通过迭代器的移动来遍历链表每个节点,当报数到m时,就删除该节点,同时需要判断迭代器是否指向了最后面,如果是,需要重新将迭代器指向开头,模拟环的操作。
完整程序如下:
int getAnsByList(int n,int m){list<int> l;for(int i = 1; i <= n; i++){ //按序号添加人l.push_back(i);}list<int>::iterator it = l.begin(); //创建迭代器while (l.size() != 1) {for(int i=0;i<m-1;i++){ //迭代器移动m-1次it ++;if(it == l.end()){ //如果指向了最后一个的下一个位置it = l.begin(); //重新指回开头}}it = l.erase(it); //返回迭代器的下一个位置if(it == l.end()){ //如果指向了最后一个的下一个位置it = l.begin(); //重新指回开头}}return l.front(); //返回剩余的人
}
4.算法对比
经过在同一台计算机上的运行对比,在数据规模很大的时候,两种算法执行的效率(空间,时间)相差较大。
数据量 | 队列实现 | 链表实现 |
---|---|---|
n:1000000 m:100 | 时间:11s 空间:3.9M | 时间:3.5s 空间:34M |
n:1000000 m:1000 | 时间:113s 空间:3.9M | 时间:41s 空间:34M |
可以发现,在时间效率方面链表结构要优于队列结构,因为链表删除效率较高,只需要在需要删除的节点上操作即可,不需要这个整个元素的移动。但是在空间方面,队列的结构要明显优于链表结构,因为链表在节点结构,内存连续性,空间利用率上面与队列差异很大。
因此,对于需要频繁地进行插入和删除操作,链表可能是一个更好的选择,因为它的动态特性使其能够更高效地处理这些操作。而如果需要快速访问队列中的元素,基于数组的队列可能更合适。