一尘不染

递归实现Josephus问题的解释

algorithm

编辑: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)的解,则可以计算josephus(n,k)。我认为这是动态编程的“最佳子结构属性”。
  3. 我知道我们需要做一个MOD N,以便如果数字超过n,它将再次从1开始计数(即,确保加法的行为就像我们在盘旋一样)。但是为什么我们要加上这个“ k-1”呢?

主要问题是,如果我们知道josephus(n-1,k)的正确解,我们如何计算josephus(n,k)的解。我们实际上已经在人口中增加了一个人,以某种方式增加这个k-1值可以为我提供正确的解决方案(让我们暂时忽略mod)。

谁能向我解释在问题的每个步骤中如何保持最佳的子结构属性?


阅读 328

收藏
2020-07-28

共1个答案

一尘不染

使该解决方案对我有意义的关键见解如下:josephus(n, k)最好不要将结果视为约瑟夫斯幸存者的 数字 ,而应视为约瑟夫斯幸存者的数字的
索引 。例如,打来电话josephus(5, 2)会告诉您该人的五分之一的 索引 ,该 索引 最终还可以保留下来。

考虑到这种直觉,让我们通过看一个具体的例子来思考约瑟夫斯问题的工作方式。假设我们想知道josephus(n, 2)。您可以想象我们有n个人这样排列:

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。幸存者在4人环中的1索引位置由给出josephus(n - 1, 2)。因此josephus(n - 1, 2) - 1,我们可以向前走,并在必要时绕环圈到达最终位置。换句话说,幸存者的位置

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

但是,以上公式存在问题。如果我们确实在使用单索引,那么如果最后的幸存者位于位置n,会发生什么?在这种情况下,我们会意外地返回位置0作为答案,但我们确实需要位置n。为了解决这个问题,我们将使用技巧使用mod来环绕一个索引:我们将获取内部数量(一个索引位置),然后减去1以获得零索引位置。我们将该数量修改为n,以获得零索引位置。最后,我们将加回一个以获得一个索引位置,并环绕。看起来像这样:

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

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

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

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

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

k+1 k+2 ... n 1 2 ... k-1

因此,我们进行了计算,josephus(n - 1, k)以得到n-1人环的一个索引的幸存者,然后向前移动那么多个步骤:

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

我们需要担心回绕的情况,因此我们需要将n修改为:

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

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

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

简化为

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

相当于

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

如解决方案代码中所示。

总结:k-1项来自于位置k + 1处开始,相josephus(n - 1, k) - 1加以向前移动适当的量,然后相减一并最后相加一以进行正确的环绕操作。

希望这可以帮助!

2020-07-28