解释约瑟夫斯概率的递归实现

时间:2015-08-02 19:12:47

标签: algorithm josephus

编辑:n是人数。 k是被淘汰的第k个人。所以对于k = 2,每个第二个人都会被淘汰。

int josephus(int n, int k)
{
 if (n == 1)
  return 1;
else
   return (josephus(n - 1, k) + k-1) % n + 1;
}

代码尽可能简单。但不知何故,我无法理解这个问题(说实话有点尴尬)。

我试图理解它的方式是,

  1. josephus(n,k)为大小为n且步长为k的总体提供最终解决方案。
  2. 如果我们知道josephus(n-1,k)的解,可以计算
  3. josephus(n,k)。这是我认为动态规划的“最优子结构属性”。
  4. 我知道我们需要做一个MOD N,这样如果数字越过n,它将再次从1开始计数。(即确保加法的行为就像我们在圆圈中计算一样)。但为什么我们加上这个“k-1”?
  5. 主要的问题是,如果我们知道约瑟夫斯(n-1,k)的正确解,我们如何计算约瑟芬(n,k)的解。我们已经有效地向人群增加了一个人,并且以某种方式添加这个k-1值给了我正确的解决方案(让我们暂时忽略mod)。

    任何人都可以向我解释一下,在问题的每一步中,最佳子结构属性如何保持?

1 个答案:

答案 0 :(得分:13)

使这个解决方案对我有意义的关键见解如下:josephus(n, k)的结果最好不被认为是数字是约瑟夫斯的幸存者,而是约瑟夫斯幸存者号码的指数。例如,调用josephus(5, 2)会告诉您最终存活的五人中的人的索引

考虑到这种直觉,让我们通过一个具体的例子思考约瑟夫斯问题是如何运作的。假设我们想知道josephus(n, 2)。你可以想象我们有这样的人排队:

1 2 3 4 5 ... n

首先发生的事情是1人杀死了人2,如下所示:

1 X 3 4 5 ... n

现在,我们留下了以下形式的子问题:剩下n-1个人,每个人都会被杀,第一个将要刺伤的人是3人。话说,我们留下了一团形状像这样的人:

3 4 5 ... n 1

k = 2.现在,假设我们对josephus(n - 1, 2)进行递归调用,因为我们有n - 1个人。这将返回在一行n - 1人中幸存的索引。鉴于我们拥有将要生存的人的索引,并且我们也知道起始人是谁,我们可以确定将留下哪个人。我们将如何做到这一点。

此行中的起始人是最后被执行者之后的人。这将是人3.幸存者在四人环中的1个索引位置由josephus(n - 1, 2)给出。因此,我们可以向前走josephus(n - 1, 2) - 1个位置,必要时环绕环,以到达我们的最终位置。换句话说,幸存者按位置给出

 (3 + josephus(n - 1, 2) - 1) % n

但上述公式存在问题。如果我们确实使用单索引,如果最终幸存者位于n位置会发生什么?在那种情况下,我们不小心回到0位作为我们的答案,但我们真的想要位置n。作为对此的修复,我们将使用一个技巧来使用mod来包围一个索引:我们将获取内部数量(一个索引位置)并减去一个以获得零索引位置。我们将该数量修改为n以获得零索引位置。最后,我们将添加一个以获得一个索引位置,包裹。看起来像这样:

(3 + josephus(n - 1, 2) - 2) % n + 1

这里的-2项因此来自两个独立的-1:第一个-1是因为josephus(n - 1, 2)返回一个索引的索引,所以要向前迈出正确的位置数量{} 1}}向前迈进。第二个-1来自我们使用单索引而不是零索引的事实。

让我们将其概括为适用于任意k,而不仅仅是k = 2.假设我们想知道josephus(n - 1, 2) - 1。在这种情况下,人1将刺伤人k,留下我们这样的阵列:

josephus(n, k)

我们现在基本上需要解决人k + 1首先出现的子问题:

1 2 3 ... k-1 X k+1 ... n

因此,我们计算k+1 k+2 ... n 1 2 ... k-1 以获得一个k人环的单索引幸存者,然后向前移动那么多步:

josephus(n - 1, k)

我们需要担心我们环绕的情况,所以我们需要修改n:

(k+1 + josephus(n - 1, k) - 1)

但是,我们是单索引的,所以我们需要使用从内部数量减去1然后在结尾添加1的技巧:

(k+1 + josephus(n - 1, k) - 1) % n

简化为

(k+1 + josephus(n - 1, k) - 2) % n + 1

相当于

(k-1 + josephus(n - 1, k)) % n + 1

与解决方案代码一样。

总结一下:k-1项来自于位置k + 1,加上(josephus(n - 1, k) + k-1) % n + 1 以向前移动适当的数量,然后减去一个并在最后添加一个以进行正确的环绕

希望这有帮助!