归并排序的时间复杂度为, 插入排序的时间复杂度为, 当需要排序的数组足够长时,归并排序肯定比插入排序更快,但当数组长度比较小的时候,常量因子起主导作用,由于插入排序的常量因子比较小,插入排序比归并排序更快。
所以有一个常见的对归并排序的优化:当递归排序的子问题变得足够小时,不继续递归调用归并排序,而是直接调用插入排序。
有两种做法:
- 自顶向下,指定需要使用插入排序的子问题的大小。
- 自底向上。把数组均分为k份,每一份用插入排序进行排序,然后对这k个排好序的子数组进行归并操作。
伪代码
merge_ins_sort(array, subarray_length)
# 自顶向下
n = array.length
merge_ins_sort_aux(array, 1, n, subarray_length)
merge_ins_sort_aux(array, left, right, subarray_length)
length = right - left + 1
if subarray_length >= length
insertion_sort(array, left, right)
else
mid = floor((left + right) / 2)
merge_ins_sort_aux(array, left, mid, subarray_length)
merge_ins_sort_aux(array, mid + 1, right, subarray_length)
merge(array, left, mid, right)
merge_ins_sort(array, partition)
# 自底向上
n = array.length
subarray_length = ceil(n / partition)
subarray_number = partition
for i = 1 to subarray_number
left = sublist_length * (i - 1) + 1
right = min(subarray_length * i, n)
insertion_sort(array, left, right)
while subarray_number > 1
for i = 1 to subarray_number by 2
merge(array, subarray_length * (i - 1) + 1, subarray_length * i,
min(subarray_length * (i + 1), n))
subarray_length *= 2
subarray_number = ceil(subarray_number / 2)
时间复杂度
自底向上
数组被分成k个子数组,则每个子数组的长度为n / k, 所以对所有子数组执行插入排序操作的总时间为,
然后相邻的子数组两两进行归并, 这样,子数组数目变为k / 2, 每个子数组的长度为2n/k, 一轮归并的总时间为, 继续归并相邻的子数组, 直到子数组的数目变为1, 也就是数组被排好序。由于每轮归并相邻的子数组,子数组的数目变为原来的1 / 2, 所以,需要执行, 所以归并的总时间为
所以算法的时间复杂度为
当k = n时,对每个长度为1的子数组进行插入排序,时间为, 然后对n个长度为1的子数组进行归并,时间为。
当k = 1时,只有一个长度为n的子数组,所以只有插入排序,没有归并操作,时间为
自顶向下
该问题形成一个递归树,第一层是长度为n的问题,第二层是两个长度为n/2的子问题,也就是说,每一层比前一层子问题数目增倍,每个子问题长度减倍,每一层的归并操作总时间为, 如果是完全的归并排序,子问题的长度变为1时才停止递归,递归树的高度为, 算法复杂度为, 在我们这个归并排序的变形中,当子问题的长度小于等于指定长度l时,就不再继续向下递归,而是直接使用插入排序,所以递归树的高度为, 归并的总时间为, 需要执行插入排序的子数组有n/l个,每个子数组长度为l,插入排序的总时间为, 所以总的时间复杂度为.
当l=n时,只有插入排序,没有归并操作,时间为
当l=1时,归并的时间为, 插入排序的时间为, 总时间为
总结及讨论
自顶向下和自底向上的时间复杂度是相同的,都是, k为子数组长度
问题1
当k趋近于1,时间复杂度趋近于归并排序的时间复杂度,当k趋近于n时,时间复杂度趋近于插入排序的时间复杂度。那么把k作为n的函数并且使用渐进符号表示,那么使得时间复杂度仍然渐进等于归并排序的时间复杂度的k的最大值是多少呢?
首先我们观察到,k肯定不能大于, 如果k = , 那么, 所以k最大值为
问题2
当k取何值,我们的算法真正起到了优化效果?
算法运行时间分为两部分: 和, 其中是插入排序的常量因子,是归并排序的常量因子,
归并排序的时间为, 所以我们的优化算法和归并排序的时间差别就是, 这个时间差别是怎么来的呢。归并排序的递归树有层,优化算法的递归树只有层,在层直接使用插入排序。所以也就是插入排序所用时间和层递归所用的时间的区别。
插入排序和归并排序的时间差别为, 所以使得归并排序的时间小于插入排序的n都是我们的优化算法中可取的k。
, 由于是递增函数,k的范围为,是使得成立的最大数。
最后来看一下优化效果,时间差除以归并排序的时间, 随着n越来越大,分子所占的比例越来越小,所以我们的优化算法有一定的效果,但并不明显.