【Algo】约瑟夫问题(Josephus problem / Josephus permutation)

Backto Algo Index


N N N 个人围一圈, 选一个人, 从 1 开始计数, 计到第 k k k 个人出局, 剩下的人组成新环, 再次从 1 开始计数, 计到 k k k 的人出局. 如此反复, 直到剩下最后一个人, 作为胜利者. 问, 在游戏开始时, 排在第几号位置, 可以保证自己是最后的胜利者?

举一个特例理解题目, 设 N = 6 , k = 5 N = 6, k = 5 N=6,k=5, 则这一圈人是

1 2 3 4 5 6 1

出局的顺序依次是 5 -> 4 -> 6 -> 2-> 3, 所以开局时候排在第 1 1 1 位是胜利者.

编程求解的直接思路: 用一个 循环链表 储存这 N N N 个人, data 里存一个 bool 值, true 表示还在场上, false 表示出局. 然后循环遍历 N − 1 N - 1 N1次, 最后剩下的人就是胜利者. But, 时间复杂度是 N × k N \times k N×k, 任何一个数非常大的话, 这个复杂度都是不可接受的.

优化, 重新分析问题, 试图找到其中的隐含规律.
计算机语言问题描述: n n n 个人(编号 0 → ( n − 1 ) 0\to(n-1) 0(n1),从 0 0 0开始报数,报到 ( m − 1 ) (m-1) m1的退出,剩下的人继续从 0 0 0 开始报数。求胜利者的编号。

我们可以确定, 第一个出列的人编号肯定是 ( m − 1 ) m o d    n (m-1) \mod n (m1)modn, 那么剩下的 n − 1 n-1 n1 个人组成新的 约瑟夫环, 新环肯定以 ( k = m m o d    n k = m \mod n k=mmodn) 开始, 新环的排序肯定是 k , k + 1 , k + 2 , . . . , n − 2 , n − 1 , 0 , 1 , 2 , . . . , k − 2 k, k+1, k+2, ... , n-2, n-1, 0, 1, 2, ... , k - 2 k,k+1,k+2,...,n2,n1,0,1,2,...,k2. 把这个新环的序号调整一下, 看做一个新的约瑟夫环

  • k → 0 k \to 0 k0
  • k + 1 → 1 k+1 \to 1 k+11
  • k + 2 → 2 k+2 \to 2 k+22
  • . . . ... ...
  • k − 2 → n − 2 k-2 \to n-2 k2n2

也就是说变成了 n − 1 n-1 n1 个人报数的子问题. 假设我们知道对于这个 n − 1 n-1 n1个人的子问题, 第 x x x 个人是最终的胜利者, 那么我们再把这个 x x x 塞回去就是刚好 n n n个人情况下的解了. 变回去的公式就是 x ′ = ( x + k ) m o d    n x' = (x+k) \mod n x=(x+k)modn. 因此, 我们知道了 n − 1 n-1 n1 下的解就可以求出 n n n 的解, 典型的递归问题.

const int n = 68; // total number
const int m = 3; // kill the m_th person
int f = 0; // safe position
int main()
{
    for (int i = 1; i <= n; i++) 
    	f = (f + m) % i;
    cout << f + 1 << endl; // counts from 1
}

时间复杂度 O ( n ) O(n) O(n), 空间复杂度 O ( 1 ) O(1) O(1), 好很多了.


Ref

  • 约瑟夫问题 – 百度百科 : 问题的起源, 分析求解, 和不同版本语言实现. 想不到浓眉大眼的百度百科还要这么优秀的词条.
  • 约瑟夫问题 : 语言实现写的比较详细完整

你可能感兴趣的:(Algo)