归并排序草稿

401 阅读4分钟

代码测试

1、测试归并排序的有效性和时间复杂度

SortingUtil.testSortingAlgorithm(new MergeSort(), new GenerateRandomArrayStrategy(100_0000));
SortingUtil.testSortingAlgorithm(new MergeSort(), new GenerateRandomArrayStrategy(400_0000));

结论:几乎是线性的。我们传入的测试用例是一个百万级别的随机数组,我们发现,归并排序平均在 0.2 秒就完成了排序任务。

2、比较归并排序与插入排序,测试用例:随机生成的数组

SortingUtil.compareSortingAlgorithms(new GenerateRandomArrayStrategy(10_0000),
        new InsertionSortOptimize(),
        new MergeSort());

结论:即使是还没有优化过的归并排序,都比插入排序的优化版本要快。

3、比较归并排序与插入排序,测试用例:几乎有序的数组

SortingUtil.compareSortingAlgorithms(new GenerateNearlySortedArrayStrategy(10_0000, 0.99),
        new InsertionSortOptimize(),
        new MergeSort());

结论:依然还是归并排序要快。

归并排序的相关结论

image-20191115160544093

这样做是有原因的:这里隐含的一个知识点是:

当子区间的长度是偶数的时候,位于中间的数其实有 2 个,而 int mid = (left + right) / 2; 因为除法运算符 / 是向下取整的,因此,它取到的是 2 个中间位置的数当中靠左边的那个数,我们不妨称它为左中位数。

你可以在纸上具体举几个例子计算一下。相信这一行代码为什么取的是左中位数并不难理解。

那么在子区间只有 2 个元素的时候,mid 取的就是这个子区间的第 1 个元素,相应的 mid + 1 是这个子区间的第 2 个元素,两个索引值都是有意义的。

如果你将递归处理的部分这样定义:

  • 数组 [left, mid - 1] 递归调用一次;

  • 数组 [mid , right] 递归调用一次。

那就表示 mid 是当前子区间第 2 部分的第 1 个元素的索引。

而此时 mid - 1 就有数组下标越界的风险,还是当子区间只有 2 个元素的时候,mid 在这一行代码下,取的是第 1 个元素,mid - 1 就越界了,那么解决的办法很简单,在括号里加一个 1 即可。

因为递归调用的语义变了,后面归并两个有序子区间的代码也就要做相应的调整,那么有兴趣的同学不妨可以做这样一个练习,取右中位数,怎么把后面的代码都写对,相信并不难。

递归

image-20191115000933924

  • 我们要在待归并的两个数组上设置两个变量。一开始的时候 ij 分别指向待归并数组的首元素,然后比较它们所指向的元素,哪个元素更小,就把它的值复制到归并以后的数组,然后它向右移动一格;我们需要借助两个变量,一开始我们将变量放置在两个数组的起始位置,比较当前这两个变量所指的位置上的元素,哪个元素小,我们把这个元素复制到归并以后的数组中,然后这个变量向后移动一位,而这个变量之前的元素均已经归并完毕,
  • 重复这样的过程,直到 ij 都遍历到了两个待排序的数组的末尾。

暂时不用的东西。

假设两个待归并的数组分别是 [2, 3, 5, 7][1, 3, 6, 8],事实上,这个过程很像合并两叠已经按顺序码好序的扑克牌。

image-20191114225520449

我们每次都只看这两个序列的第 1 个元素,选出它们当中较小的那个元素归并到最终它应该在的位置。

  • 首先 21 比较,1 出列;
  • 接着 23* 比较,2 出列;
  • 接着 33* 比较,3 出列;
  • 接着 3*5 比较,3* 出列;
  • 接下里出列的顺序依次是 5678

这个过程看起来非常简单。不过事实上,如果我们真正要在数组中做这样的操作,其实是比较费劲的一件事,因为我们每次要从数组的头部删除一个元素,然后把它后面的那些元素都向前移动一格,这样的操作是 O(N^2) 的。

image-20191114230723382

归并排序的优化

代码测试

1、测试代码正确性

SortingUtil.testSortingAlgorithm(new MergeSortOptimize(), new GenerateRandomArrayStrategy(100_0000));

2、对比实验

SortingUtil.compareSortingAlgorithms(new GenerateRandomArrayStrategy(100_0000),
                                     new MergeSort(),
                                     new MergeSortOptimize());

结论:优化以后确实速度提升了。