我想将此作为对 templatetypedef 答案的评论发布,但它太长了。
他的回答是正确的,因为最简单的方法是看到这是线性的,因为每条边都被访问了两次,并且树中的边数总是比节点数少一(因为每个节点都有一个父级,除了根!)。
问题在于,他对正式证明的表述方式使用了似乎暗示矛盾的词语。一般来说,数学家不赞成使用矛盾,因为它经常产生内容多余的证明。例如:
Proof that 2 + 2 != 5:
Assume for contradiction that 2 + 2 = 5 (<- Remove this line)
Well 2 + 2 = 4
And 4 != 5
Contradiction! (<- Remove this line)
矛盾往往很冗长,有时甚至会混淆证明背后的想法!有时矛盾似乎非常必要,但相对较少,这是一个单独的讨论。
在这种情况下,我认为矛盾证明并不比直接证明更容易。另一方面,不管证明技术如何,这个证明在形式上都很难看。这是一个尝试:
1) succ(n) 算法遍历两条路径之一
我们想要证明任意一条边恰好在两个succ() 调用中被遍历,一次用于succ() 的第一种情况,一次用于succ() 的第二种情况。好吧,除了最右边的分支之外的每个边缘都是如此,但是您可以单独处理这些边缘情况。或者,我们可以证明访问最后一个元素后返回根的更简单的论点
这是双重的,因为对于给定的边 e,我们必须找到 n1 和 n2 使得 succ(n1) 遍历 e 和 succ(n2) 也遍历 e,以及证明每隔一个succ() 生成一个不包含e 的路径。
2) 首先我们实际上证明对于succ() 访问的每种类型的路径,没有两条路径重叠(即如果succ(n) 和succ(n') 都遍历相同类型的路径,这些路径不共享边)
在第一种情况下,简单路径精确定义如下。从节点n 开始,向右走到r 的一侧。然后遍历以r 为根的子树的左分支。现在考虑从其他节点n' 开始的任何其他此类路径(注意,我们不假设n != n')。它必须右移一个节点到r'。然后它遍历以r' 为根的子树最左边的分支。如果路径重叠,则选择重叠的边缘之一。如果它是(n,r) = (n',r'),那么我们有n = n',所以它是相同的路径。如果在最左边的两个分支中都有一些e = e',那么您可以再次显示n = n'(您可以跟踪最左边的分支并显示每条边都是相同的,然后最终得出r = r' => n = n' 的结论,因为对于一棵树父级是唯一的。您将在下面看到此跟踪参数)。因此我们知道对于任何n 和n',如果它们的路径重叠,它们实际上是同一个节点!对立面是这样说的:如果它们是不同的节点,那么它们的路径就不会重叠。这正是我们想要的(相反的总是与原始陈述同样正确)。
在第二种情况下,我们定义从节点 n 开始的简单路径,然后沿着祖先节点 p_1, p_2, ..., p_k = g 向上直到我们到达第一个节点 p_k,这样 p_(k-1) 就在 @987654359 的左侧@。考虑从节点n' 开始的其他一些相同类型的路径,其中n != n'。同样它访问p_1', p_2', ..., p_k' = g'。因为它是一棵树,所以这些祖先中没有一个与第一组相同。因为两条路径上的节点都不相同,所以边不可能相同,因此succ(n) 和 succ(n') 不会遍历任何相同的边
3) 现在我们只需要证明对于给定的边,每种类型至少存在一条路径。那么采取任何这样的边缘e = (c,p)(注意这里我忽略了最右边分支上的特殊边缘,这些边缘在技术上只被访问过一次,我也忽略了最左边分支上的特殊边缘,它们在技术上被find_min()访问过一次,然后一次由succ() 调用)
如果它是从左子 c 到其父 p 则 succ(c) 将覆盖第二种类型的路径。要找到另一条路径,请继续向上p 的祖先p_1, p_2, ..., p_k 使p_(k-1) 位于p_k 的右侧。根据定义,succ(p_k) 将遍历包含e 的路径(因为e 位于p_(k-1) 的子树的最左侧分支,即p_k 的右孩子)。
当c 是p 的右孩子时,类似的论点也适用于对称情况
总结一下我们已经证明succ() 生成两种类型的路径。对于每种类型的路径,这些类型的所有路径都不重叠。此外,对于任何边缘,我们至少有每种类型的路径中的一种。由于我们在每个节点上调用succ(),我们最终可以得出结论,每条边都被遍历了两次(因此算法是Theta(n))。
尽管这个证明持续了多长时间,但它实际上并不完整(即使忽略了我明确表示我正在跳过细节时的要点!)。在某些情况下,我说某物存在而没有证明它存在。您可以根据需要弄清楚这些细节,并且将其完全正确确实很令人满意(至少在我看来。也许当您是天才时会发现它很乏味,呵呵)
希望这会有所帮助。如果您希望我澄清一些步骤,请告诉我