题目:n个人围成一圈(编号从1到n),从第1个人开始报数,报到m的人出列,从下一个人再重新报数,报到m的人出列,如此下去,直至所有人都出列。求最后一个出列的人的编号(使用递归)。
分析:
递归问题分3步走:
1、递归收敛:由于m是不变的,所以只能通过n将规模不断缩小
2、找出口:当递归收敛到最小单位时,能得到一个出口。即当n=1时,出局者的位置为0
3、找规律:分析已知条件,与我们需要结果的关联。
因为我们已知最后一轮出局者在最后一轮中的位置,所以我们只需要找到后一轮出局者的位置,在上一轮中所处的位置,依次递归最终就能找到最后出局者在第一轮中的位置;
我们知道第一轮出局者在本轮的位置(从0开始)为:f(n, m) = (m-1) % n
且第二轮第0个位置在第一轮中的位置为:m % n(注:疑惑下面有解决)
,
第二轮第一个位置在第一轮中的位置为:(m+1) % n,
...
第二轮第k个位置在第一轮中的位置为:(m+k) % n;
由此可推导出如下结论:
第二轮出局者在第一轮中的位置为:f(n, m) = (f(n-1, m) + m) % n
第三轮出局者在第二轮中的位置为:f(n-1, m) = (f(n-2, m) + m) % n
...
第n轮出局者在第n-1轮中的位置为: f(2, m) = (f(1, m) + m) % n
根据步骤二,出口条件可得到:f(1, m) = 0
根据递归性质,第二轮递归表达式即我们写递归函数需要用到的表达式,所以最终表达式为:f(n, m) = (f(n-1, m) + m) % n
// 递归求最后一个幸存者的编号(从0开始)
func josephus4(n, m int) int {
if n == 1 {
return 0
}
return (josephus4(n-1, m) + m) % n
}
疑惑1:为什么第二轮开始位置在第一轮中的位置为m % n 而不是(m-1) % n + 1?
解答:分两步:
1、证明(m-1) % n + 1 不符合我们想要的结果;
当m = kn时,即(kn - 1) % n + 1 = n,而初始位置是从0开始的,最大位置为n - 1,很明显结果超出了。
当m > n && m != kn时或者m < n 时, 都符合我们的结果
2、证明(m+0) % n 符合结果;
当m = kn 时 kn % n = 0 符合结果
当m > n && m != kn 时或者 m < n时,都符合我们的结果
我们要把这n个人看成一个闭环,在m > 0 && n > 0的情况(m - 1) % n + 1 与 m % n 的区别在于前者不可能得到为0的位置,而后者可以。
疑惑2:为什么初始位置排列要从0开始而不从1开始呢?
解答:因为如果m = kn时,取余会得到结果0,位置从0开始比较方便,
位置从1开始分析:
第一轮出局者在本轮的位置为:f(n, m) = m % n,
第二轮第1个位置在第一轮中的位置为:m % n + 1,
...
第二轮第k个位置在第一轮中的位置为:m % n + k,如果结果大于n的话,再次取余即可。
由此可推导出如下结论:
第二轮出局者在第一轮中的位置为:f(n, m) = m % n + f(n-1, m)
第三轮出局者在第二轮中的位置为:f(n-1, m) = m % n + f(n-2, m)
...
第n轮出局者在第n-1轮中的位置为:f(2, m) = m % n + f(1, m)
根据步骤二,出口条件可得到:f(1, m) = 1 注意:因为这里第一个位置是从1开始
所以最终表达式为:f(n, m) = m % n + f(n-1, m) 需要新增条件,如果有一轮中f(n, m) > n 那么 f(n, m) = f(n, m) % n
// 递归求最后一个幸存者的编号(从1开始)
func josephus5(n, m int) int {
if n == 1 {
return 1
}
location := m % n + josephus5(n-1, m)
if location > n {
location = location % n
}
return location
}
以上就是我对约瑟夫环算法的理解,如有不当之处,欢迎指教!!