归并排序是基于归并操作的一种效率较高的排序算法,同快速排序一样,归并排序也是分治法的一种应用。其时间复杂度为:O(nlogn),最好和最坏情况下都是O(nlogn)。空间复杂度为O(n)。归并排序是一种稳定的排序算法(两个相等的数据,在排序后其先后顺序不变,因为归并不涉及两个相等的数据进行交换)。
归并排序的思想
归并排序的思想就是将待排序数列num[n]看做是n个有序数列,然后将相邻的有序表进行归并。归并排序其实就是做两件事:
(1)分解:将待排序数列进行折半划分;
(2)归并:将划分后的序列段和并排序。
例如:有一待排序序列nums[14, 12, 15, 13, 11, 16],下图是其分解和归并过程:
我们首先来分析第二步归并过程。
合并两个有序数列
要想合并两个有序数列很简单,只需要比较两个数列的第一个数,然后取最小的,然后移动被取的那个数列的指针。直到有一个数列取完指针为空,然后再把另一个不为空的数列复制过去。
核心代码为:
//合并有序数组序列,利用辅助数组。
public static void merge(int[] nums, int left, int mid, int right, int[] result) {
int left1 = left;
int right1 = mid;
int left2 = mid + 1;
int right2 = right;
int k = 0;
//先把较小的数移入到临时数组
while (left1 <= right1 && left2 <= right2) {
if (nums[left1] <= nums[left2]) {
result[k++] = nums[left1++];
} else {
result[k++] = nums[left2++];
}
}
//把左边剩余的数移入
while (left1 <= right1) {
result[k++] = nums[left1++];
}
//把右边剩余的数移入
while (left2 <= right2) {
result[k++] = nums[left2++];
}
//将排序好的覆盖到nums数组
for (int i = 0; i < k; i++) {
nums[left + i] = result[i];
}
}
从上边代码中可以看到,我们用了一个临时数组来存储排序好的数据,所以归并排序的空间复杂度为O(n)。合并的时间度是O(n)。
分析完归并后,我们再来看分解。第一步:先把原始数列除以2分为A,B两个部分,然后再将A,B各自一分为二,重复此过程,直到数列只有一个数据了,这意味着这个数列是有序的。可以明显看到,这个过程是一个递归过程,所以用递归方法实现。
归并排序的核心代码
public class MergeSort {
//将待排序数组分解
public static void mergeSort(int[] nums, int left, int right, int[] result) {
if (left < right) {
int mid = (left + right) / 2;
mergeSort(nums, left, mid, result);
mergeSort(nums, mid + 1, right, result);
merge(nums, left, mid, right, result);
}
}
//合并有序数组序列,利用辅助数组。
public static void merge(int[] nums, int left, int mid, int right, int[] result) {
int left1 = left;
int right1 = mid;
int left2 = mid + 1;
int right2 = right;
int k = 0;
//先把较小的数移入到临时数组
while (left1 <= right1 && left2 <= right2) {
if (nums[left1] <= nums[left2]) {
result[k++] = nums[left1++];
} else {
result[k++] = nums[left2++];
}
}
//把左边剩余的数移入
while (left1 <= right1) {
result[k++] = nums[left1++];
}
//把右边剩余的数移入
while (left2 <= right2) {
result[k++] = nums[left2++];
}
//将排序好的覆盖到nums数组
for (int i = 0; i < k; i++) {
nums[left + i] = result[i];
}
}
}
归并排序的空间复杂度是O(n),因为其用到了一个大小为n的辅助数组来保存合并排序。
在这里我们分析一下快速排序的空间复杂度:首先快排使用的空间是O(1)的,但其递归调用会消耗空间,因为递归栈(全局)的深度是O(logn)。所以其最优的空间复杂度为O(logn)。最差的情况下空间复杂度为:O(n):退化为冒泡排序的情况。
从空间复杂度来看:堆排序最好O(1),其次是快速排序O(logn),最后是归并排序O(n)。
从稳定性来看:应选归并排序,因为快速排序和堆排序都是不稳定的。
堆排序,快排,归并排序的时间复杂度都是O(nlong)。快排最坏情况下是O(n^2)--退化为冒泡的时候。
综合来看,选快速排序。