笔记
对于一个有 n n n个元素的集合,要寻找其中的最小元素,需要做多少次比较?我们很容易得出需要 n − 1 n−1 n−1次比较。寻找最小元素的过程如下所示。

那么 n − 1 n−1 n−1次比较是寻找最小值问题的最优解吗?答案是肯定的。可以这样来分析。对于任意一个寻找最小值的算法,可以把算法看成一场所有元素都参与的锦标赛。每两个元素之间的比较都是锦标赛中的一场比赛,两个元素中较小者获胜。那么最终获胜者即是所有元素中的最小值。除最终获胜者以外,每个元素都至少会输掉一场比赛。因此可以断定,寻找最小值至少需要进行 n − 1 n−1 n−1次比较。
同理,寻找最大元素也需要 n − 1 n−1 n−1次比较。
在某些应用中,我们可能需要同时找出最小值和最大值。如果分别独立地寻找最小值和最大值,那么各需要 n − 1 n−1 n−1次比较,一共需要 2 n − 2 2n−2 2n−2次比较。然而,有一种方法所需的比较次数比这更少。这种方法将输入元素两两分组,将每一组元素互相进行比较,然后把其中较小者与已知的当前最小值进行比较,把其中较大者与已知的当前最大值进行比较。这样一来,每 2 2 2个元素需要比较 3 3 3次。
假设输入数组一共有 n n n个元素。如果 n n n为奇数,那么就将初始的最小值和最大值都设为第一个元素,然后成对地处理余下的元素。如果 n n n为偶数,那么先对前 2 2 2个元素做一次比较,较小者作为初始的最小值,较大者作为初始的最大值,然后成对地处理余下的元素。以下是这种方法的伪代码。

下面来分析该算法所需的比较次数。如果 n n n为奇数,那么总共需要进行 3 ( n − 1 ) / 2 3(n-1)/2 3(n−1)/2次比较。如果 n n n为偶数,则先进行一次初始比较,然后进行 3 ( n − 2 ) / 2 3(n-2)/2 3(n−2)/2次比较,加起来一共需要进行 3 n / 2 − 2 3n/2-2 3n/2−2次比较,这小于 3 ⌊ n / 2 ⌋ 3⌊n/2⌋ 3⌊n/2⌋。所以,无论n为奇数还是偶数,总的比较次数都不超过 3 ⌊ n / 2 ⌋ 3⌊n/2⌋ 3⌊n/2⌋。
书本上的内容介绍完了,这里再扩展一部分内容,主要是为解决练习题9.1-1做一下铺垫。上文提到,寻找最小值至少需要进行 n − 1 n−1 n−1次比较,这里提供另外一种证明方法。
对于任意一个寻找最小值的算法,可以把所有比较过程构造成为一棵比较树。举例说明,有一个包含 8 8 8个元素的数组 < 10 , 6 , 8 , 5 , 7 , 9 , 12 , 11 > <10, 6, 8, 5, 7, 9, 12, 11> <10,6,8,5,7,9,12,11>,利用代码9.1-1来寻找该数组的最小值,比较树如下图所示。

对比较树来说,任何一个非叶结点代表一次比较过程,非叶结点的两个孩子表示参与比较的两个元素,非叶结点本身则是它的两个孩子比较下来的较小者。所以一棵比较树有多少个非叶结点,就说明一共包含多少次比较过程。而叶结点则表示一个元素首次参与比较,如果比较之后该元素被淘汰,则它不会参与之后的比较;如果没被淘汰,则它会继续参与之后的比较。
上图这棵比较树除根结点外,每一层均有两个结点。比较过程从树的最底层开始,逐步往上进行,直到根结点为止。其中红色实线标示了每一次比较后留下来的元素。这棵比较树一共有 7 7 7个非叶结点,说明一共有 7 7 7次比较。
接下来换一种寻找最小值的算法:将元素两两分组,分别进行比较,淘汰每一组的较大值;对剩下的元素再进行两两分组,进行同样的操作;如此迭代,直到得到最小值为止。将该算法作用于同一个数组 < 10 , 6 , 8 , 5 , 7 , 9 , 12 , 11 > <10, 6, 8, 5, 7, 9, 12, 11> <10,6,8,5,7,9,12,11>,得到的比较树如下图所示。

这棵比较树与上一棵比较完全不同,但是也有 7 7 7个非叶结点,说明一共也进行了 7 7 7次比较。
我们举了两个例子,展示两种不同的寻找最小值的算法,两种算法所需要的比较次数是一样的,都是 n − 1 n−1 n−1,这并非是巧合。我们看到以上两棵比较树形状相差很大,然而它们有一些共同点,即二者都是二叉树,并且二者所有非叶结点的度都为 2 2 2。因为比较树的任意一个非叶结点都是由两个子结点比较得到的,即任意一个非叶结点都包含两个孩子。也就是说,任意一棵比较树,只有度为 0 0 0的结点(叶结点)和度为 1 1 1的结点(非叶结点)。
二叉树有一个有趣的性质:任意一棵二叉树,如果度为 0 0 0的结点个数为 n 0 n_0 n0,度为 2 2 2的结点个数为 n 2 n_2 n2,则有 n 0 = n 2 + 1 n_0 = n_2 + 1 n0=n2+1。这里不给出这一性质的证明过程,感兴趣的可以在网络上搜索。对比较树来说, n 0 n_0 n0就是叶结点个数, n 2 n_2 n2就是非叶结点个数。
上文又提到,比较树中的叶结点表示一个元素首次参与比较。在寻找最小值过程中,任何一个元素都至少要参与一次比较,任意一棵比较树至少包含 n n n个叶结点,于是非叶结点至少有 n − 1 n−1 n−1个。上文提到“一个非叶结点代表一次比较过程”,因此任意一棵比较树都包含至少 n − 1 n−1 n−1次比较。这也说明 n − 1 n−1 n−1次比较是寻找最小值问题的最优解。
练习
9.1-1 证明:在最坏情况下,找到 n n n个元素中第二小的元素需要 n + ⌈ l g n ⌉ − 2 n+⌈{\rm lg}n⌉-2 n+⌈lgn⌉−2次比较。(提示:可以同时找最小元素。)
证
利用上文提到的比较树很容易证明这一结论。首先寻找最小元素,一共需要 n − 1 n−1 n−1次比较,并且得到一棵比较树 T T T,这是第一轮比较。在寻找最小元素的过程中,第二小元素必然会在某次与最小元素的比较过程中被淘汰。
所有被最小元素淘汰掉的元素构成一个集合,假设该集合包含 m m m个元素。接下来寻找该集合中的最小元素,它就是所有元素中的第二小元素,这就是第二轮比较。这一过程需要 m − 1 m−1 m−1次比较。最坏情况下, m m m的值取决于比较树 T T T的高度。再看上文给出的例子,如下图所示。在这一棵比较树中,最小元素是 5 5 5, 3 3 3个标为红色结点都被最小元素给淘汰掉的。第二小元素必然是这 3 3 3个元素中的最小值。

最坏情况下,最小元素在比较树的最底层就出现了,并且最小元素在比较树的每一层(根结点所在层除外)都淘汰掉一个元素。在这种情况下被最小元素淘汰掉的元素个数正好等于比较树的高度。而一棵含有 n n n个叶结点的二叉树,它的高度至少为 ⌈ l g n ⌉ ⌈{\rm lg}n⌉ ⌈lgn⌉。这意味着最坏情况下,第二轮寻比较过程一共包含至少 ⌈ l g n ⌉ ⌈{\rm lg}n⌉ ⌈lgn⌉个元素,至少需要 ⌈ l g n ⌉ − 1 ⌈{\rm lg}n⌉-1 ⌈lgn⌉−1次比较。

文章探讨了寻找集合中最小和最大元素所需比较次数的问题。对于n个元素的集合,寻找最小值和最大值至少需要n-1次比较。通过构造比较树可以证明这一点。同时,文章介绍了寻找第二小元素在最坏情况下的比较次数为n+⌈lgn⌉-2,并解释了这个结论。对于同时找到最大值和最小值,文章证明了最坏情况下的比较次数下界是⌈3n/2⌉-2。
5040

被折叠的 条评论
为什么被折叠?



