我们先来分析一下函数。有两种情况:
- 如果是
n > 100,那么我们返回n-10;和
- 在
n <= 100 的情况下,我们计算n+11,然后我们执行两个额外的步骤。
因此,有两个可能的“步骤”:一个是减 10,另一个是增加 11。我们可以用图表将其可视化,例如:
第一种情况的边缘用黑色表示,后一种情况的边缘用红色表示。
我们注意到这里有一个循环:92 -> 103 -> 93 -> 104 -> 94 -> 105 -> 95 -> 106 -> 96 -> 107 -> 97 -> 108 -> 98 -> 109 -> 99 -> 110 -> 100 -> 111 -> 101 -> 91 -> ...
现在让我们假设——不管原始值如何——我们总是会进入那个循环。现在循环总是将黑色边缘和红色边缘交错,111 -> 101 -> 91 部分除外,它由两个黑色边缘组成。
由于红色边缘引入了两个额外的递归调用,这意味着,如果我们采用红色跃点,我们将免费获得下一个黑色和红色跃点。下一个红色跃点将再次向“待办事项列表”添加两个递归调用。因此,如果我们从循环中开始,并且我们首先进行红色跳跃,我们将继续在循环中运行。只要我们不采用循环的111 -> 101 -> 91 部分,这就是成立的。由于那是两条黑边,我们可以摆脱递归调用来执行,因此在91 处停止(因为我们总是在每个红色跃点获得两个额外的跃点)。
结果,如果我们从循环中的某个节点开始,然后立即进行一次红色跃点,无论我们还需要执行多少次递归调用,我们最终都会在 91 处停止:每次我们执行一个完整的循环,我们“丢失”了一个递归调用,所以最终我们将用完剩余的递归调用并在 91 处停止。所以现在我们知道,如果我们从94 开始,使用任意数量的递归调用,我们将停在 91。
现在我们仍然需要证明 - 假设数字小于 100 - 我们将最终进入循环,并且我们在循环中遇到的第一个节点是红色边缘开始的节点。我们知道 91..100 范围内的所有数字都在循环中。任何小于 91 的数字都将导致红色跃点,并以 11 递增。因此 - 如图中部分所示 - 所有小于 91 的数字最终将在 [91..100] 范围内始终使用红色边缘。
基于上述推理,更高效的函数版本是:
mc91' n | n > 100 = n-10
| otherwise = 91
现在对于您给定的示例输入 (85),我们看到程序将评估为:
mc91 85
-> mc91 (mc91 96) -- we are in the loop now
-> mc91 (mc91 (mc91 107))
-> mc91 (mc91 97)
-> mc91 (mc91 (mc91 108))
-> mc91 (mc91 98)
-> mc91 (mc91 (mc91 109))
-> mc91 (mc91 99)
-> mc91 (mc91 (mc91 110))
-> mc91 (mc91 100)
-> mc91 (mc91 (mc91 111))
-> mc91 (mc91 101)
-> mc91 91 -- we reached 91, and thus removed one recursive call
-> mc91 (mc91 102)
-> mc91 92
-> mc91 (mc91 103)
-> mc91 93
-> mc91 (mc91 104)
-> mc91 94
-> mc91 (mc91 105)
-> mc91 95
-> mc91 (mc91 106)
-> mc91 96
-> mc91 (mc91 107)
-> mc91 97
-> mc91 (mc91 108)
-> mc91 98
-> mc91 (mc91 109)
-> mc91 99
-> mc91 (mc91 110)
-> mc91 100
-> mc91 (mc91 111)
-> mc91 101
-> 91 -- we hit 91 a second time, and now the last recursive call is gone