编辑: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;
}
代码尽可能简单。但不知何故,我无法理解这个问题(说实话有点尴尬)。
我试图理解它的方式是,
主要的问题是,如果我们知道约瑟夫斯(n-1,k)的正确解,我们如何计算约瑟芬(n,k)的解。我们已经有效地向人群增加了一个人,并且以某种方式添加这个k-1值给了我正确的解决方案(让我们暂时忽略mod)。
任何人都可以向我解释一下,在问题的每一步中,最佳子结构属性如何保持?
答案 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
以向前移动适当的数量,然后减去一个并在最后添加一个以进行正确的环绕
希望这有帮助!