看了一下午的约瑟夫问题,稍微理解了这个算法的过程:
- 首先什么是约瑟夫问题:假如m个人围成一圈,从第一个开始报数,第n个将被杀掉,最后剩下一个,其余人都将被杀掉。例如n=5,m=6,被杀掉的顺序是:5,4,6,2,3,1。
用数组来解决这个问题显然会花费很多时间:重复多次遍历数组,经过n个人以后赋值1,标其已死亡,直到死亡人数到达m,输出最后一个人。
- 在网上寻找了递归的办法去解决这个问题:
int ysfdg ( int sum, int value, in tn)
{
if ( n == 1 )
return ( sum + value - 1 ) %sum;
else
return ( ysfdg ( sum-1, value,n-1 ) +value ) %sum;
}
其中ysfdg表示约瑟夫递归算法,这个算法的含义是,从第一个人开始数,数到第n个人的时候,就从头开始重新数,直到只剩最后一个人。
- 比如n=5,m=6时,将会如下:
杀人前:1 2 3 4 5 6(下面下划线表示被杀)
第一次杀人以后:1 2 3 4 _ 6(此时数数为:1 2 3 4 5 6)
第二次杀人以后:1 2 3 _ _ 6(此时数数为:2 3 4 5 _ 1)
第三次杀人以后:1 2 3 _ _ _(此时数数为: 2 3 4 _ _ 1 第二遍 2 3 4 _ _ 5 )
第四次杀人以后:1 _ 3 _ _ _ (此时数数为:1 2 3 _ _ _ 第二遍 4 5 6 _ _ _ )
第五次杀人以后:1 _ _ _ _ _
- 然后用数学递归的方法表示:把每个人的编号先都减1,这样会是0~(m-1)号,方便取余运算,最后的结果需要加1还原。
第一个死掉的是编号应当是(n-1)%m号,所以说之后第二次选谁死的时候,编号 0 就是原来的编号n%m
第二个死掉的人的编号 x2 的原编号就是 ( n%m+x2 )%m = ( n+x2 )%m。编号为0是在第二次选谁死的圈中编号为 n%(m-1) 的家伙
第三个死的编号为 x3 的人在第二次的圈中编号就为 (n%(m-1)+x3)%(m-1) = (n+x3)%(m-1) ,继续推出其在最初的圈中编号为 (n+(n+x3)%(m-1))%m 。
知道了如何还原上一次编号的公式,但这样为什么不会出错呢?不应该剔除掉死掉的家伙吗?因为比如在第二次的圈中 x2 肯定是小于 m-1 的,加起来取余后肯定在上一个死掉的人编号前面,所以不会出现算进去已经死掉的人的问题!
按照这个思想的话,最后一个死掉的肯定是编号是0的,因为只剩他自己了,所以说这个问题就转换成了他原来的编号是多少?依次迭代即可。
迭代公式: f ( x ) = ( f ( x-1 ) +n ) % x ( 1<x<=m ) ,f ( 1 ) = 0