让我们看看能不能解决这个问题!
在归并排序中,在递归的每一级,我们执行以下操作:
- 将数组分成两半。
- 对每一半进行递归排序。
- 使用合并算法将两部分合并在一起。
那么每个步骤进行多少次比较?好吧,除法步骤没有进行任何比较;它只是将数组分成两半。步骤 2 不(直接)进行任何比较;所有比较都是通过递归调用完成的。在第 3 步中,我们有两个大小为 n/2 的数组,需要合并它们。这需要最多 n 次比较,因为合并算法的每一步都会进行一次比较,然后消耗一些数组元素,所以我们最多只能进行 n 次比较。
结合起来,我们得到以下递归:
C(1) = 0
C(n) = 2C(n / 2) + n
(如 cmets 中所述,线性项更准确地说是 (n - 1),但这不会改变总体结论。我们将使用上述递归作为上限。)
为了简化这一点,让我们定义 n = 2k 并用 k 重写这个递归:
C'(0) = 0
C'(k) = 2C'(k - 1) + 2^k
这里的前几个术语是 0, 2, 8, 24, ... 。这看起来像 k 2k,我们可以通过归纳来证明这一点。作为我们的基本情况,当 k = 0 时,第一项为 0,并且 k 2k 的值也为 0。对于归纳步骤,假设声明对于某些 k 成立并考虑 k + 1. 那么值为 2(k 2k) + 2k + 1 = k 2 k + 1 + 2k + 1 = (k + 1)2k + 1,所以对于 k + 1 成立,完成归纳。因此C'(k)的值为k 2k。由于 n = 2 k,这意味着,假设 n 是 2 的完美幂,我们有进行比较的次数是
C(n) = n lg n
令人印象深刻的是,这比快速排序更好!那么为什么快速排序比归并排序更快呢?这与其他与比较次数无关的因素有关。首先,由于快速排序在适当的位置工作,而合并排序在不适当的位置工作,因此在合并排序中的引用局部性几乎不如在快速排序中那样好。这是一个如此巨大的因素,以至于在实践中快速排序最终比合并排序好得多,因为缓存未命中的成本非常高。此外,对数组进行排序所需的时间并不仅仅考虑比较的次数。其他因素,例如每个数组元素移动的次数也很重要。例如,在合并排序中,我们需要为缓冲的元素分配空间,移动元素以便它们可以合并,然后合并回数组中。我们的分析中没有计算这些动作,但它们肯定会加起来。将此与快速排序的分区步骤进行比较,后者将每个数组元素仅移动一次并保留在原始数组中。这些额外的因素,而不是进行的比较次数,决定了算法的运行时间。
这个分析比最优分析稍微不精确,但Wikipedia 确认分析大约是 n lg n,这确实比快速排序的平均情况要少。
希望这会有所帮助!