版权声明:本文为博主原创文章,未经允许不得转载。 https://blog.csdn.net/qq_31142553/article/details/84672596
简单粗暴的解释:内部有序外部无序的两个数组的排序。
归并排序以O(NlogN)最坏情形时间运行,而所使用的比较次数几乎是最优的。它是递归算法一个好的实例。
典型应用场景:MapReduce。
递归:一个方法调用自己本身。其关键点是要找到结束方法递归调用的条件出口。
归并排序的合并算法说明,内容直接截取自《数据结构与算法分析·Java语言描述·第3版》。
一、实现Java语言描述的归并排序功能
1、定义归并排序类,使用泛型,仅对实现了Comparable接口的类元素进行排序。
2、首先从整体来看,一个无序的数组,要对其使用归并排序,必须先把它从中间分成两个子数组,然后将他们分别排序,最后对它们进行合并。
3、每个子数组的排序,又可以分别对其使用归并排序,这就涉及到了递归。最终切分成小数组的递归方法调用,终会因为数组只有一个元素而达到出口条件然后结束进入合并环节。
4、在递归前定义一个临时数组变量,然后传递到递归方法里面使用,而不是每次归并时创建一个临时数组,这样可以避免频繁创建对象从而消耗时间和内存。
/**
* 归并排序
* 运行时间:
* T(N)=2T(N/2)+N=O(NlogN)
* @author z_hh
* @time 2018年11月18日
*/
public class MergeSort<AnyType extends Comparable<? super AnyType>> {
/**
* 归并排序
* @param a 实现了Comparable接口的对象数组
*/
public void mergeSort(AnyType[] a) {
/*
* 如果对merge的每个递归调用均局部声明一个临时数组,那么在任一时刻就可能有logN个临时数组处在活动期。
* 因此,我们由始至终使用一个临时数组,可以避免数组拷贝带来的性能消耗。
*/
AnyType[] tmpArray = (AnyType[]) new Comparable[a.length];
mergeSort(a, tmpArray, 0, a.length - 1);
}
/**
* 递归排序
* @param a 实现了Comparable接口的对象数组
* @param tmpArray 临时数组
* @param left 子数组的最左元素索引
* @param right 子数组的最右元素索引
*/
private void mergeSort(AnyType[] a, AnyType[] tmpArray, int left, int right) {
if (left < right) {
int center = (left + right) / 2;// 中间位置
mergeSort(a, tmpArray, left, center);// 左边递归排序
mergeSort(a, tmpArray, center + 1, right);// 右边递归排序
merge(a, tmpArray, left, center + 1, right);// 将两边归并
}
}
/**
* 合并两个已排序的子数组
* @param a 实现了Comparable接口的对象数组
* @param tmpArray 临时数组
* @param leftPos 左边数组的开始元素索引
* @param rightPos 右边数组的开始元素索引
* @param rightEnd 右边数组的结束元素索引
*/
private void merge(AnyType[] a, AnyType[] tmpArray, int leftPos, int rightPos, int rightEnd) {
int leftEnd = rightPos - 1;
int tmpPos = leftPos;
int numElements = rightEnd - leftPos + 1;
// 左右两边从初始位置开始,分别拿出当前位置的元素进行比较,小的放到临时数组的指定位置,然后位置向后移一位
while (leftPos <= leftEnd && rightPos <= rightEnd) {
if (a[leftPos].compareTo(a[rightPos]) <= 0) {
tmpArray[tmpPos++] = a[leftPos++];
}
else {
tmpArray[tmpPos++] = a[rightPos++];
}
}
// 右边元素放完了,将左边的全部元素放到临时数组
while (leftPos <= leftEnd) {
tmpArray[tmpPos++] = a[leftPos++];
}
// 左边元素放完了,将右边的全部元素放到临时数组
while (rightPos <= rightEnd) {
tmpArray[tmpPos++] = a[rightPos++];
}
// 将临时数组的元素拷回原数组
for (int i = 0; i < numElements; i++, rightEnd--) {
a[rightEnd] = tmpArray[rightEnd];
}
}
// 测试
public static void main(String[] args) {
// 产生指定数量的随机排序的数组(比那种随机产生一个数放进集合前判断是否存在效率高多了)
int size = 100;
List<Integer> numbers = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
numbers.add(i + 1);
}
Random random = new Random();
int sourceSize = numbers.size();
Integer[] array = new Integer[sourceSize];
for (int i = 0; i < sourceSize; i++) {
int index = random.nextInt(numbers.size());
array[i] = numbers.remove(index);
}
// 排序前
System.out.println("排序前");
AtomicInteger no = new AtomicInteger(1);
Arrays.stream(array)
.forEach(i -> {
System.out.print(i + " ");
if (no.getAndIncrement() % 20 == 0) {
System.out.println();
}
});
// 排序
MergeSort<Integer> mergeSort = new MergeSort<>();
mergeSort.mergeSort(array);
// 排序后
System.out.println("排序后");
Arrays.stream(array)
.forEach(i -> {
System.out.print(i + " ");
if (i % 20 == 0) {
System.out.println();
}
});
}
}
二、归并排序的时间复杂度
当只有一个排序的元素时,T(1)=1。
设定N个元素使用归并排序的时间复杂度为T(N)。根据其算法,先把数组分为两个子数组也使用归并排序,其时间复杂度分别为为T(N/2),加起来是T(N/2)+T(N/2)=2T(N/2),然后,两个排好序的子数组进行合并的时候,最多需要比较N次,其时间复杂度为N,最后总的时间复杂度为2T(N/2)+N。经过各种化解,T(N)=2T(N/2)+N=O(NlogN)。
三、不使用递归如何实现归并排序
???后续补上。。。