问题概述:
非原装正版问题,但是道理还是那个道理
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
#include
int 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();
else
ite--;
}
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,自己从头写一次就会找到规律)
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]%n
int 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开始,但是要求从1
system("pause");
return 0;
}
优点:跑的快,而且线性增长,代码量小
缺点:想不出来,别人想出来,自己需要消化
时间复杂度:
T(N)=O(N^2) 无话可说。。。