您的位置:首页 > 其它

算法(第四版)学习笔记之归并排序的优化

2015-09-08 09:40 417 查看
在之前的文章中记录过归并排序的笔记,只是学习了基本的排序原理,并未对归并排序有更加深入的了解,现在重新回过头来仔细研究了一下归并排序的算法优化。归并排序的优点在于我们只需要比遍历整个数组多个对数因子的时间就能将一个庞大的数组排序,即对于长度为N的任意数组,自顶向下的归并排序(之前的文章中有自顶向上和自底向上两种归并排序的写法)需要1/2NlgN至NlgN次比较次数,最多需要访问数组6NlgN次。那么归并排序的主要缺点就是辅助数组所使用的额外空间和N的大小成正比。那么,既然知道了归并排序的优缺点,就可以从三方面下手,进行优化:

1.当数组长度小到一定规模的时候,插入排序或者选择排序的速度可能会比归并排序的更快,所以我们可以给予一个阙值,当数组长度小到阙值时,就是用插入排序来解决小规模子数组的排序问题;

2.测试数组是否已经有序,我们可以添加一个判断条件,如果a[mid] <= a[mid+1]的话,就可以认为数组已经是有序的了(左半边的最大值小于或等于右半边的最小值),并跳过归并的过程,这个改动不会影响排序的递归调用,但是任意有序的子数组算法的运行时间就变为线性的了;

3.不将元素复制到辅助数组中,我们可以节省将数组元素复制到用于归并的辅助数组所用的时间,但不能节省其空间。要实现这一点,我们需要调用两种排序方法,一种是将数据从输入数组排序到辅助数组,一种将数据从辅助数组排序到输入数组。我们可以在递归调用的每个层次交换输入数组和辅助数组的角色。

以上就是优化的三个方面,下面是优化后的归并排序代码:

import java.util.Random;

public class testMerge {
//阙值
public static final int CUTOFF = 7;

//public static int[] aux;

public static void sort(int[] a) {
int[] b = a.clone();
//aux = new int[a.length];
sort(b, a, 0, a.length - 1);
}

public static void sort(int[] src, int[] dst, int lo, int hi) {
//当数组长度达到阙值时,使用插入排序
if (hi <= lo + CUTOFF) {
insertSort(dst, lo, hi);
return;
}
int mid = lo + (hi - lo) / 2;
//交换参数
sort(dst, src, lo, mid);
sort(dst, src, mid + 1, hi);
//判断数组是否已经有序
if (src[mid] <= src[mid + 1]) {
System.arraycopy(src, lo, dst, lo, hi - lo + 1);
return;
}
merge(src, dst, lo, mid, hi);
}

public static void insertSort(int[] a, int lo, int hi) {
for (int i = lo; i <= hi; i++) {
for (int j = i; j > lo; j--) {
if (a[j] < a[j - 1]) {
int temp = a[j];
a[j] = a[j - 1];
a[j - 1] = temp;
}
}
}
}

public static void merge(int[] src, int[] dst, int lo, int mid, int hi) {

int i = lo, j = mid + 1;
for (int k = lo; k <= hi; k++) {
if (i > mid)
dst[k] = src[j++];
else if (j > hi)
dst[k] = src[i++];
else if (src[j] < src[i])
dst[k] = src[j++];
else
dst[k] = src[i++];
}

/**
* 以空间换时间,从数组两头开始归并
*/
//		for (int i = lo; i <= mid; i++) {
//			aux[i] = src[i];
//		}
//		for (int j = mid + 1; j <= hi; j++) {
//			aux[j] = src[hi - j + mid + 1];
//		}
//		int i = lo;
//		int j = hi;
//		for (int k = lo; k <= hi; k++) {
//			if (aux[i] < aux[j]) {
//				dst[k] = aux[i++];
//			} else {
//				dst[k] = aux[j--];
//			}
//		}
}
public static void main(String[] args) {
int[] a = new int[50000000];
Random rand = new Random(17);
for (int i = 0; i < 50000000; i++) {
a[i] = rand.nextInt(50000000);
}
long time1 = System.currentTimeMillis();
sort(a);
long time2 = System.currentTimeMillis();
for (int i = 0; i < 100; i++) {
System.out.print(a[i] + " ");
}
System.out.println();
System.out.println(time2 - time1);
}

}
测试后发现(只测试了50000000及以下规模的数据量),优化后速度快的不是很明显;

上述代码中注释掉的段落时以空间来换时间,使归并从两头开始,省去内循环中检测某半边是否用尽的代码,在速度上提升了好多,但空间耗费太大,并且这样的改动会导致排序结果的不稳定。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: