基本思想
归并排序是利用归并的思想,合并两个已排序的表,将结果输出到第三个表中。基本的合并算法是去两个输入数组A和B,一个输出数组C,以及3个计数器Actr、Bctr、Cctr,它们初始置于对应数组的开始端,当数组A[Actr]或数组B[Bctr]中的较小者被拷贝到C中的下一个位置时,相关的计数器向前推进一步。
归并排序采用经典的分治策略,它将问题分成一些小的问题然后递归求解,而治的阶段则将分的阶段解得的答案修补在一起。
分治策略
由上图可以看出,我们将一个数组通过递归不断的分成两部分,在达到最小数组时再排好序将递归的每一部分合并还原,最后得到的数组还是原来的数组,不同的是,它已经是有序的了。
“分解”
private <T extends Comparable<? super T>> void mergeSort(T[] a, T[] tmpArray, int left, int right){
if(left < right){
int mid = (left + right)/2;
mergeSort(a, tmpArray, left, mid);
mergeSort(a, tmpArray, mid + 1, right);
merge(a, tmpArray, left, mid+1, right);
}
}
public <T extends Comparable<? super T>> void mergeSort(T[] a){
T[] tmpArray = (T[]) new Comparable[a.length];
mergeSort(a, tmpArray, 0, a.length-1);
}
这段程序就是通过不断的“分”,将大问题分解成小问题。通过不断的递归,一个长的数组最终会被分成两个数。
“合并”
private <T extends Comparable<? super T>> void merge(T[] a, T[] tmpArray, int leftPos, int rightPos, int rightEnd){
int leftEnd = rightPos - 1;
int tmp = leftPos;
int numElements = rightEnd - leftPos + 1;
//数组之间比较,并推动计数器前进
while (leftPos <= leftEnd && rightPos <= rightEnd){
if(a[leftPos].compareTo(a[rightPos]) <= 0){
tmpArray[tmp++] = a[leftPos++];
} else {
tmpArray[tmp++] = a[rightPos++];
}
}
//将两个数组中剩余部分全部拷贝到数组C中
while (leftPos <= leftEnd){
tmpArray[tmp++] = a[leftPos++];
}
while (rightPos <= rightEnd){
tmpArray[tmp++] = a[rightPos++];
}
//将数组C中的数据拷贝回原数组
for (int i = 0;i< numElements;i++,rightEnd--){
a[rightEnd] = tmpArray[rightEnd];
}
}
通过从最小递归结果出开始比较大小,逐步合并比较结果,来达到排序结果。
时间复杂度:O(NlogN)
空间复杂度:O(N)
这里归并算法的空间复杂度为O(N),是因为我们需要使用一个临时数组来存储每一次的比较结果。归并算法我这里是通过递归的方式实现,当然了,也可以通过非递归的方式实现。这里我就不实现非递归的了。
总结
我们在写归并排序时,可以很清楚它的运行时间严重依赖于比较元素与在数组间移动元素的相对开销,但这些是与语言相关的。
归并排序使用所有流行的排序算法中最少的比较次数,因此是使用Java的通用排序算法中的上好的选择。事实上,它也是标准Java类库中泛型排序所使用的算法。
更多了解,还请关注我的个人博客:www.zhyocean.cn