我要学大数据之算法——归并排序

版权声明:本文为博主原创文章,未经允许不得转载。 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)。

三、不使用递归如何实现归并排序

???后续补上。。。

猜你喜欢

转载自blog.csdn.net/qq_31142553/article/details/84672596
今日推荐