0.原理
归并排序的基本思想是分治法。先将无序序列分为若干个无序子序列,然后对无序子序列进行排序,最后合并有序子序列,得到完全有序的序列。这就是分解,求解,合并的过程。
- 将待排序序列data[0,1,2….n-1]看成是n个长度为一的序列,将相邻序列进行合并,得到n/2个长度为2的有序序列;
- 将相邻序列再次进行合并,得到n/4个长度为4的有序序列
- 重复第一步和第二步,直到得到长度为n的一个有序序列,此时的序列为已排序序列
关键就在于如何合并序列,每次合并的过程都是对两个有序序列进行合并,由于这两个是相邻的,因此我们可以将两个序列表示为data[start, mid], data[mid + 1, end]。我们需要一个end-start+1长度的temp[]辅助数据配合合并。
- 从data[start, mid]和data[mid + 1, end]中各取出一个数字进行比较,将较小的数字放入到temp中,并且将数字对应的数组游标加一
- 重复第一步,知道其中一个数组遍历完
- 将另外一个未遍历完的数组直接填入temp末尾
- 将temp拷贝回data[start, end]中
依旧对[2 8 1 5 3]这个数组进行模拟,得到下面的结果:
gap=1 start
2 8 1 5 3
2 8 1 5 3
gap=1 finish
gap=2 start
2 8 1 5 3
1 2 5 8 3
gap=2 finish
gap=4 start
1 2 5 8 3
1 2 3 5 8
gap=4 finish
- 第一个循环将每个长度为1的子序列合并,因为(2,8)(1, 5)都是正确的序列,因此没有变化。
- 第二个循环将每个长度为2的子序列合并,(2, 8)(1, 5)合并后得到(1, 2, 5, 8)。
- 第三个循环将每个长度为4的子序列合并,(1, 2, 5, 8)和(3)合并后得到最终结果(1, 2, 3, 5, 8)
1. 实现
@Override
public int[] sort(int[] data) {
if (data == null || data.length <= 1) {
return data;
}
for (int gap = 1; gap < data.length; gap *= 2) {
System.out.println(String.format(Locale.CHINA, "gap=%d start", gap));
printlnArray(data);
int i;
for (i = 0; i + 2 * gap - 1 < data.length; i = i + 2 * gap) {
merge(data, i, i + gap - 1, i + 2 * gap - 1);
}
if (i + gap - 1 < data.length) {
merge(data, i, i + gap - 1, data.length - 1);
}
printlnArray(data);
System.out.println(String.format(Locale.CHINA, "gap=%d finish", gap));
}
return data;
}
merge方法没有贴上来,具体实现可看
注意,如果最后一个子序列不能正好整除的时候需要特殊处理,将其和倒数第二个子序列合并。
2. 复杂度
归并排序是一种稳定的排序算法,平均,最坏和最好的时间复杂度都为O(nlogn)。但是由于其需要O(n)的辅助空间,因此在一些对内存敏感的机器上,或者数据特别大时有所限制。
从稳定性来说,归并排序相对于快速排序来说是一个既快又稳定的排序方式。但是如果综合考虑到空间和性能来说,快速排序是优于归并排序的。
3. 优化
- 对于小数组可使用插入或者选择排序,不必使用合并的方式。
- 合并之前如果data[middle] < data[middle + 1]可以不需要合并直接返回。因为左边子序列的最大值小于右边子序列的最小值。