Josephus(约瑟夫斯)问题
来源:互联网 发布:江民杀毒 知乎 编辑:程序博客网 时间:2024/06/07 01:04
Josephus问题是下面的这个游戏:有N个人坐成一圈,编号为1至N。从编号为1的人开始传递热马铃薯。M次传递之后,持有热马铃薯的人退出游戏,圈缩小,然后游戏从退出人下面的人开始,继续进行。最终留下来的人获胜。比如:M=0,N=5, 5号获胜,退出顺序为1、2、3、4,5获胜;M=1,N=5, 3获胜,退出顺序为2、4、1、5。
思路一: 直接链表法。 将N个人排成环状队列,依次传递,通过程序说明(亲测无误):
/*********************************************************************************
头文件与命名空间
**********************************************************************************
#include <iostream>
#include <list>
using std::cin;
using std::cout;
using std::endl;
using std::list;
using std::list<int>;
int main()
{
/*********************************************************************************
准备工作
*********************************************************************************/
int person; // 人数
int transmit; // 传递基数(即题中的M)
cout << "GAME: JOSEPHUS" << endl << "Nums of persons: " ;
cin >> person;
cout << "transmit: ";
cin >> transmit;
/********************************************************************************
将N个人装入循环链表
********************************************************************************/
int num = 1; // 人员序号
list<int> list_person;
list<int> :: iterator iter1 = list_person.begin();
while (num <= person)
{
list_person.push_back(num++);
}
/*********************************************************************************
有N个人,会传递N-1次
********************************************************************************/
int N = person-1; // 传递次数
int m_transmit = 0; // 追随传递基数
while (N > 0) // 游戏开始
{
while (m_transmit<transmit && iter1!=list_person.end()) // 马铃薯还没有传递到末尾
{
m_transmit++;
iter1++;
}
if (m_transmit < transmit || iter1 == list_person.end()) // 当传递到末尾一人但是传递基数还没到M,要将马铃薯从头开始继续传
{
iter1 = list_person.begin();
continue;
}
cout << "erase: " << *iter1 << endl;
iter1 = list_person.erase(iter1); // 持有马铃薯的人退出游戏
m_transmit = 0;
N--;
}
if (iter1 == list_person.end()) // 有可能最后一次淘汰完人,链表指针指向this.end()处,所以要让指针回指到开始处
{
iter1 = list_person.begin();
}
cout << "The winner is: " << *iter1 << endl;
return 0;
}
运行结果图: M=1, N=5
思路二:逆推法
将游戏人数排成环状,如图a,为实验方便可将编号变为从0-N,如图b, 最后得出获胜编号为n,只要n+1就是实际编号
图a 图b
假设N=7,m=3。走过3步之后,3号出队,然后再从4号开始继续查找,实际上从这个时候开始,正是从N-1个元素里开始取元素了。我们可以
将第二轮的元素从4号开始重新以元素0为起始顺序进行查找。
一般的第一个人出队后,第一个出队的人编号必为m%N,剩下的N-1个人组成新的Josephus环,这时候以(m+1)%N开始。假定k=(m+1)%N,
他们组成新的序列,映射关系如下:
k-->0
k+1-->1
k+2-->2
......
k-3-->N-3
k-2-->N-2
k-1是前面一次遍历移除的。
说明对于N-1的环中,任何一个元素index对应到N的环中之间差了k,即(m+1)%N。这里的差不是直接相减差k,而是循环进位的结果。经分析可得出元素在N-1环和N环中对应的映射关系为index(N)=(index(N-1)+m+1)%N,接着移除元素,会发现映射关系index(N-1)=(index(N-2)+m+1)%(N-1);如此往复,会移除到只有一个元素的环,且index(1)=0,再逆着推,得index(2)=(index(1)+m+1)%2。通过归纳,得出数学规律: f(1)=0, f(N)=(f(N-1)+m)%N。 实际结果为f(N)+1。
代码(亲测无误):
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
int person; // N
int transmit; // m
inline void init()
{
cout << "Josephus Game:" << endl << "The Number of Person: ";
cin >> person;
cout << "transmit: ";
cin >> transmit;
}
int main()
{
init();
int index = 0;
int person_trace = 1;
for (; person_trace != person; ++person_trace)
{
index = (index+transmit+1) % (person_trace+1);
}
cout << index+1;
return 0;
}
运行结果: N=7 m=1
- Josephus(约瑟夫斯)问题
- 约瑟夫(Josephus)问题
- 约瑟夫(Josephus)问题
- 约瑟夫(Josephus)问题
- Josephus问题(约瑟夫环)
- Josephus(约瑟夫环)问题
- 约瑟夫问题(Josephus problem)
- 约瑟夫问题(Josephus Problem)
- 约瑟夫问题(Josephus problem)
- 顺序表实现Josephus(约瑟夫斯)环问题
- 约瑟夫(Josephus)问题的实现
- 约瑟夫环问题(josephus problem)详解
- Algorithm Gossip: 约瑟夫问题(Josephus Problem)
- 约瑟夫(Josephus problem)环问题初探
- Java-约瑟夫问题(Josephus Problem)
- 关于约瑟夫问题(Josephus Problem)
- 约瑟夫问题(Josephus)链表实现
- Josephus约瑟夫环问题
- 360行编辑器题目
- ios开发系列之排序算法
- 给出多个可能重叠的区间,找出重叠区间的个数。
- 光流法简单介绍(结合Opencv2)
- Search a 2D Matrix II -- leetcode
- Josephus(约瑟夫斯)问题
- 第三章 关系详解
- ResultSet结果集为空判断
- Android之基于BaseAdapter和SimpleAdapter的GridView
- linux命令-cd
- 第十周第二天
- 插入区间 给定一个没有重叠的区间序列,先插入一个新的区间到该序列中,要求维持没有重叠的情况。假设该序列存放的区间是有序的。
- 合并区间 给定一个区间集合,合并里面重叠的区间,并返回新的不含重叠区间的集合。public class num8 { public ArrayList<Interval> merge(ArrayLi
- hdu 1014