算法(第四版)学习笔记之归并排序的优化
2015-09-08 09:40
417 查看
在之前的文章中记录过归并排序的笔记,只是学习了基本的排序原理,并未对归并排序有更加深入的了解,现在重新回过头来仔细研究了一下归并排序的算法优化。归并排序的优点在于我们只需要比遍历整个数组多个对数因子的时间就能将一个庞大的数组排序,即对于长度为N的任意数组,自顶向下的归并排序(之前的文章中有自顶向上和自底向上两种归并排序的写法)需要1/2NlgN至NlgN次比较次数,最多需要访问数组6NlgN次。那么归并排序的主要缺点就是辅助数组所使用的额外空间和N的大小成正比。那么,既然知道了归并排序的优缺点,就可以从三方面下手,进行优化:
1.当数组长度小到一定规模的时候,插入排序或者选择排序的速度可能会比归并排序的更快,所以我们可以给予一个阙值,当数组长度小到阙值时,就是用插入排序来解决小规模子数组的排序问题;
2.测试数组是否已经有序,我们可以添加一个判断条件,如果a[mid] <= a[mid+1]的话,就可以认为数组已经是有序的了(左半边的最大值小于或等于右半边的最小值),并跳过归并的过程,这个改动不会影响排序的递归调用,但是任意有序的子数组算法的运行时间就变为线性的了;
3.不将元素复制到辅助数组中,我们可以节省将数组元素复制到用于归并的辅助数组所用的时间,但不能节省其空间。要实现这一点,我们需要调用两种排序方法,一种是将数据从输入数组排序到辅助数组,一种将数据从辅助数组排序到输入数组。我们可以在递归调用的每个层次交换输入数组和辅助数组的角色。
以上就是优化的三个方面,下面是优化后的归并排序代码:
上述代码中注释掉的段落时以空间来换时间,使归并从两头开始,省去内循环中检测某半边是否用尽的代码,在速度上提升了好多,但空间耗费太大,并且这样的改动会导致排序结果的不稳定。
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及以下规模的数据量),优化后速度快的不是很明显;
上述代码中注释掉的段落时以空间来换时间,使归并从两头开始,省去内循环中检测某半边是否用尽的代码,在速度上提升了好多,但空间耗费太大,并且这样的改动会导致排序结果的不稳定。
相关文章推荐
- c++のnamespace
- Struts2框架的结构分析和简单的登入示例
- 获取当前控制器
- 将数组构建成大顶推
- 黑马程序员——多线程+单例设计
- Unity3D 向量运算
- Java中的String与常量池
- Oracle 账号密码忘记了咋办
- 在Windows下用Python搭建Deeplearning平台
- DB2数据库日期时间的处理
- git clone操作出现fatal:index-pack failed错误解决方案
- Latex Tools在OSX EI Captain无法编译的问题
- 工具收藏
- PANGU---Planet and Asteroid Natural scene Generation Utility
- linux 安装httpd(验证通过)
- yum 安装redis
- 已知进程pid获取其父进程pid
- SOAPUI测试REST项目(二)——从现有的服务创建REST模拟服务
- vmware workstation 11许可证密钥
- 极简OpenCV的相机标定代码