摘要: 使递归的叶子变粗
【对算法,数学,计算机感兴趣的同学,欢迎关注我哈,阅读更多原创文章】
我的网站:潮汐朝夕的生活实验室
我的公众号:算法题刷刷
我的知乎:潮汐朝夕
我的github:FennelDumplings
我的leetcode:FennelDumplings
在文章 分治算法的设计与分析-归并排序 中,我们了解了分治算法的设计和分析方法,并且得出了归并排序算法的最坏情况运行时间为 Θ ( n log n ) \Theta(n\log n) Θ(nlogn)。
在文章 基于随机访问机模型分析算法 中,我们了解了算法分析的方法论,并且以插入排序为例,得到了插入排序最坏情况运行时间为 Θ ( n 2 ) \Theta(n^{2}) Θ(n2)。
虽然开起来好像归并排序比插入排序的运行时间要好,但插入排序中的常量因子可能使得它在 n 较小时,在许多机器上实际运行得更快。因此归并排序中,当子问题规模足够小时,采用插入排序使得递归的叶变粗是有意义的。
本文我们来分析一下,子问题规模多小的时候采用插入排序,可以取得收益。
问题描述
考虑长度为 n 的数组,使用插入排序来排序长度为 k k k 的 n k \frac{n}{k} kn 个子表,然后用归并排序的合并机制来合并这些子表。
分析这个算法的运行时间,与归并排序进行对比,并研究实践中选择 k 的策略。
排序 n / k n/k n/k 个子表的运行时间
插入排序一个长为 k k k 的数组,最坏情况的运行时间为 Θ ( k 2 ) \Theta(k^{2}) Θ(k2),于是排序 n k \frac{n}{k} kn 个子表的运行时间为:
Θ ( k 2 ) × n k = Θ ( n k ) \Theta(k^{2}) \times \frac{n}{k} = \Theta(nk) Θ(k2)×kn=Θ(nk)
合并 n / k n/k n/k 个子表的运行时间
合并 K K K 个已排序的数组(链表),每个已排序数组长度为 N N N,合并后总长度为 K N KN KN。在 力扣23-合并K个升序链表 中我们解决过这个问题,有堆和分治两种方法,运行时间一样。分析过程如下:
如果用堆的方法,堆的容量为 K K K,因此堆的插入删除运行时间 Θ ( log K ) \Theta(\log K) Θ(logK),而 K K K 个已排序数组的总长度为 K N KN KN,因此总的运行时间为: Θ ( K N log K ) \Theta(KN\log K) Θ(KNlogK)。
如果用分治法,过程如下:
- 分解:将 K K K 个已排序数组分为两份,每份 K / 2 K / 2 K/2 个
- 解决:如果 K = 1 K = 1 K=1,可以直接解决,运行时间 Θ ( 1 ) \Theta(1) Θ(1)
- 合并:将两个有序数组合并为 1 个
按照以上流程,每一轮合k并 K 2 \frac{K}{2} 2K 组链表,每一组 2 个,因此每组运行时间为 Θ ( 2 N ) \Theta(2N) Θ(2N);每二轮合并 k 4 \frac{k}{4} 4k 组链表,每一组 4 个,因此每组运行时间为 Θ ( 4 N ) \Theta(4N) Θ(4N),以此类推,总运行时间为 Θ ( ∑ i = 1 log K K 2 i × 2 i N ) = Θ ( K N log K ) \Theta(\sum\limits_{i=1}\limits^{\log K}\frac{K}{2^{i}}\times 2^{i}N) = \Theta(KN\log K) Θ(i=1∑logK2iK×2iN)=Θ(KNlogK)。
这里我们是要合并 n k \frac{n}{k} kn 个已排序数组,每个的长度为 k k k,合并后总长度 n n n,因此合并的运行时间为 Θ ( n log n k ) \Theta(n\log \frac{n}{k}) Θ(nlogkn)。
整体的运行时间
在归并排序中,对小数组用插入排序,分为排序 n / k n/k n/k 个子表,合并 n / k n/k n/k 个子表两步,前面分别分析了这两步的运行时间,因此总运行时间为:
Θ ( n k ) + Θ ( n log n k ) = Θ ( n k + n log n k ) = Θ ( n log n + n k − n log k ) = Θ ( n log n + n ( k − log k ) ) \begin{align} \Theta(nk) + \Theta(n\log\frac{n}{k}) &= \Theta(nk + n\log\frac{n}{k}) \\ &= \Theta(n\log n + nk - n\log k) \\ &= \Theta(n\log n + n(k - \log k)) \\ \end{align} Θ(nk)+Θ(nlogkn)=Θ(nk+nlogkn)=Θ(nlogn+nk−nlogk)=Θ(nlogn+n(k−logk))
可以看到,修改过的算法的运行时间最低也是 Θ ( n log n ) \Theta(n\log n) Θ(nlogn),与归并排序一样。如果 k k k 取的不好,比如 k > log n k > \log n k>logn,修改过的算法的运行时间还会大于归并排序的 Θ ( n log n ) \Theta(n\log n) Θ(nlogn)。
实践中如何选择 k
根据具体的计算环境,选择使得插入排序比归并排序快的最大的 k k k,但最大不要大于 log n \log n logn。