归并排序法(自下而上)

上一篇文章中简单介绍了归并排序,在二分的过程中使用了递归的设计,但我们还可以使用for循环的方式来对数组进行“二分”。

思路

这种归并排序就没有递出去二分的过程了,而是直接开始合并,因此也叫自底向上的归并排序,合并的过程并没有区别。

其实这可能比递归的归并排序更容易理解一些。

首先我们定义一个子数组长度gap,使用for循环让它从1开始成倍增长,这就是二分的逆过程。

然后在这个for循环中进行合并,在自上而下的归并排序中我们合并的两个子数组是arr[l, mid]和arr[mid+1, r]。

那么到了这里便成了arr[l, l+gap-1]和arr[l+gap, l+gap*2-1]。

可能这么说还是不太容易理解,我们直接看代码,我已经写满了注释。

实现

//自底向上的归并排序
void mergeSortBU(int arr[], int n) {
    //gap可以理解为子数组长度,作为二分的逆过程,自然是从1开始成倍增长
    for (int gap = 1; gap <= n; gap *= 2) {
        //从数组左端开始,将两个子数组范围传给__merge合并,增量为两个子数组长度和即gap*2
        //循环终止条件为i + gap < n,以免越界
        for (int i = 0; i + gap < n; i += gap * 2) {
            //当子数组长度小于某个点时改用插入排序
            if (gap <= 15) {
                insertionSort2(arr, i, i + gap * 2 - 1);
            } else {
                //当左侧子数组最后一个元素大于右侧第一个元素时才合并
                if (arr[i + gap - 1] > arr[i + gap])
                {
                    //注意右侧范围不能越界
                    __merge(arr, i, i + gap - 1, min(i + gap * 2 - 1, n - 1));
                }
            }
        }
    }
}

由于是自底向上的过程,因此这里要多注意越界的问题,将i+gap-1看成原本的mid应该有助于理解。

测试

随机数组

insertion sort2 : 1.810301s
merge sort : 0.008202s
merge sortBU : 0.008678s

近乎有序的数组

insertion sort2 : 0.001722s
merge sort : 0.001699s
merge sortBU : 0.002333s

可以看到自底向上的归并排序速度与自上而下的归并排序整体相当,稍慢一点点,但是自底向上的归并排序并不依赖数组下标取值,因此它也适用于链表的排序。

本篇到此为止,希望这对你有帮助,如果有错误或是有需要补充的地方,望告知。


种一棵树最好的时间是在十年前,而后是现在。

Loading Disqus comments...
Table of Contents