• 排序算法之——归并排序(两种方法及其优化)


    本文将围绕代码从多个方面分析归并算法,归并的操作很简单,稍加思考便能深刻理解。

    1、算法思想:

    要将一个数组排序,可以(递归地)将数组分成两半分别排序,然后将两边归并起来。归并算法最吸引人的地方是它能保证将任意长度为N的数组排序的时间与NlgN成正比。

    主要缺点是需要与N成正比的额外空间。

     (示意图1)

    2、原地归并的抽象方法

    实现归并最直截了当的方法是将两个数组归并到第三个数组,实现的方法很简单,从左到右逐一比较两数组的第一位元素,将小的一个放入第三个数组(假设两数组已经有序),完成操作后第三个数组就是有序的。了解了思路,我们直接看代码。

     1     public static void merge(Comparable[] a, int lo, int mid, int hi) {
     2         int i = lo, j = mid + 1;
     3         for (int k = lo; k <= hi; k++) {
     4             aux[k] = a[k];
     5         }
     6         for (int k = lo; k <= hi; k++) {
     7             if (i > mid) {
     8                 a[k] = aux[j++];
     9             } else if (j > hi) {
    10                 a[k] = aux[i++];
    11             } else if (less(aux[i], aux[j])) {
    12                 a[k] = aux[i++];
    13             } else {
    14                 a[k] = aux[j++];
    15             }
    16         }
    17 
    18     }

    主要操作就是第二个for循环里的四个判断:

    1、数组1走完(将数组2当前元素放入数组3)

    2、数组2走完(将数组1当前元素放入数组3)

    3、数组1当前元素小于数组2当前元素(将数组1当前元素放入数组3)

    4、数组2当前元素小于等于数组1当前元素(将数组2当前元素放入数组3)

    (示意图2:将数组1和组2归并到组3)

    3、自顶向下的归并排序

    如果能将两个子数组排序,就能通过并归两个子数组来对整个数组排序,这一切是通过递归实现的,也叫递归归并。直接看代码:

     1 public class Merge{
     2     private static Comparable[] aux;
     3     public static void sort(Comparable[] a) {
     4         aux = a.clone();// 一次性分配空间
     5         sort(a,0, a.length - 1);
     6     }
     7 
     8     private static void sort(Comparable[] a,int lo, int hi) {
     9         if (hi <= lo) {
    10             return;
    11         }
    12         int mid = lo + (hi - lo) / 2;
    13         sort(aux,a, lo, mid);//左半边排序
    14         sort(aux,a, mid + 1, hi);//右半边排序
    15         merge(a,aux,lo, mid, hi);//归并结果(参考原地归并的抽象方法)
    16     }
    17 }

    示意图:

    (示意图3)

    上图只是merge方法的轨迹,sort方法也极为重要,要想理解就必须知道sort方法调用的轨迹(这里请读者自己先写出sort的轨迹再看下面的答案)

    sort(a,0,7)

    将左半部分排序

    sort(a,0,3)

    sort(a,0,1)

    merge(a,0,0,1)

    sort(a,2,3)

    merge(a,2,2,3)

    将右半部分排序

    sort(a,4,7)

    sort(a,4,5)

    merge(a,4,4,5)

    sort(a,6,7)

    merge(a,6,6,7)

    归并结果

    merge(a,0,3,7)

    4、自底向上的归并排序

    我们已经知道,自顶向下采用的是递归的方法,而自底向上则是循序渐进得解决问题,采用了循环的方法。通过下图可以很容易看出两种方式的区别:

    下面上代码:

    1     public static void sort(Comparable[] a) {
    2         int n = a.length;
    3         aux = new Comparable[n];
    4         for (int sz = 1; sz < n; sz = sz + sz) {
    5             for (int lo = 0; lo < n - sz; lo += sz + sz) {
    6                 merge(a, lo, lo + sz - 1, Math.min(lo + 2 * sz - 1, n - 1));// 最后一次并归的第二个子数组可能比第一个小此时lo+2*sz-1越界
    7             }
    8         }
    9     }

    读者自行考虑自底向上方法的运行轨迹。

    5、三项优化(代码在后面的代码演示中)

    ①对小规模子数组使用插入排序

    用不同的方法处理小规模数组能改进大多递归算法的性能,在小数组上上,插入排序可能比并归排序更快。

    ②测试数组是否有序

    根据归并排序的特点,每次归并的两个小数组都是有序的,当a[mid]<=a[mid+1]时我们可以跳过merge方法,这样并不影响排序的递归调用。

    ③不将元素复制到辅助数组

    我们可以节省将数组复制到辅助数组的时间,这需要一些技巧。先克隆原数组到辅助数组,然后在之后的递归交换输入数组和辅助数组的角色(通过看代码更容易理解)

     

    (画方框的为每次的输出数组)

    6、代码演示(java):

     1 public class Merge implements Comparable<Merge> {// 归并排序(优化前)
     2     private static Comparable[] aux;
     3 
     4     private static boolean less(Comparable v, Comparable w) {
     5         return v.compareTo(w) < 0;
     6     }
     7 
     8     @Override
     9     public int compareTo(Merge arg0) {
    10         // TODO Auto-generated method stub
    11         return 0;
    12     }
    13 
    14     public static void merge(Comparable[] a, int lo, int mid, int hi) {// 原地归并的抽象方法
    15         int i = lo, j = mid + 1;
    16         for (int k = lo; k <= hi; k++) {
    17             aux[k] = a[k];
    18         }
    19         for (int k = lo; k <= hi; k++) {
    20             if (i > mid) {
    21                 a[k] = aux[j++];
    22             } else if (j > hi) {
    23                 a[k] = aux[i++];
    24             } else if (less(aux[j], aux[i])) {
    25                 a[k] = aux[j++];
    26             } else {
    27                 a[k] = aux[i++];
    28             }
    29         }
    30     }
    31 
    32     public static void sort(Comparable[] a) {
    33         aux = new Comparable[a.length];
    34         sort(a, 0, a.length - 1);
    35     }
    36 
    37     private static void sort(Comparable[] a, int lo, int hi) {
    38         /*
    39          * 自顶向下的并归排序 三个改进
    40          */
    41         if (hi <= lo) {
    42             return;
    43         }
    44         int mid = lo + (hi - lo) / 2;
    45         sort(a, lo, mid);
    46         sort(a, mid + 1, hi);
    47         merge(a, lo, mid, hi);
    48     }
    49 
    50     private static void exch(Comparable[] a, int j, int i) {
    51         // TODO Auto-generated method stub
    52         Comparable temp;
    53         temp = a[j];
    54         a[j] = a[i];
    55         a[i] = temp;
    56     }
    57 
    58     public static void main(String[] args) {
    59         Merge mg = new Merge();
    60         Comparable a[] = { 8, 1, 6, 8, 4, 6, 9, 7, 1, 2, 3, 4, 8, 5, 2, 6, 4, 3, 8 };
    61         mg.sort(a);
    62         for (int i = 0; i < a.length; i++) {
    63             System.out.print(a[i] + " ");
    64         }
    65     }
    66 }
     1 public class MergeX implements Comparable<Merge> {// 归并排序(优化后)
     2     private static Comparable[] aux;
     3 
     4     private static boolean less(Comparable v, Comparable w) {
     5         return v.compareTo(w) < 0;
     6     }
     7 
     8     @Override
     9     public int compareTo(Merge arg0) {
    10         // TODO Auto-generated method stub
    11         return 0;
    12     }
    13 
    14     public static void merge(Comparable[] a, Comparable[] aux, int lo, int mid, int hi) {// 原地归并的抽象方法
    15         int i = lo, j = mid + 1;
    16         // for (int k = lo; k <= hi; k++) {
    17         // aux[k] = a[k];
    18         // }
    19         for (int k = lo; k <= hi; k++) {
    20             if (i > mid) {
    21                 a[k] = aux[j++];
    22             } else if (j > hi) {
    23                 a[k] = aux[i++];
    24             } else if (less(aux[j], aux[i])) {
    25                 a[k] = aux[j++];
    26             } else {
    27                 a[k] = aux[i++];
    28             }
    29         }
    30     }
    31 
    32     public static void sort(Comparable[] a) {
    33         aux = a.clone();// 一次性分配空间
    34         sort(a, aux, 0, a.length - 1);
    35     }
    36 
    37     private static void sort(Comparable[] a, Comparable[] aux, int lo, int hi) {
    38         /*
    39          * 自顶向下的并归排序 三个改进
    40          */
    41         // if (hi <= lo) {
    42         // return;
    43         // }
    44         int mid = lo + (hi - lo) / 2;
    45         if (hi - lo <= 7) {// 对小规模子数组使用插入排序
    46             //System.out.println("insert!");
    47             insertionSort(a, lo, hi);
    48             return;
    49         }
    50         sort(aux, a, lo, mid);
    51         sort(aux, a, mid + 1, hi);
    52         if (!less(aux[mid + 1], aux[mid])) {// 已经有序时跳过merge(a中lo到mid mid到hi分别都是有序的)
    53             System.arraycopy(aux, lo, a, lo, hi-lo+1);
    54             return;
    55         }
    56         merge(a, aux, lo, mid, hi);
    57     }
    58 
    59     private static void insertionSort(Comparable[] a, int lo, int hi) {
    60         for (int i = lo; i <= hi; i++)
    61             for (int j = i; j > lo && less(a[j], a[j - 1]); j--)
    62                 exch(a, j, j - 1);
    63     }
    64 
    65     private static void exch(Comparable[] a, int j, int i) {
    66         // TODO Auto-generated method stub
    67         Comparable temp;
    68         temp = a[j];
    69         a[j] = a[i];
    70         a[i] = temp;
    71     }
    72 
    73     public static void main(String[] args) {
    74         MergeX mgx = new MergeX();
    75         Comparable a[] = { 8, 1, 6, 8, 4, 6, 9,7,1, 2, 3,4,8,5,2,6,4,3,8};
    76         mgx.sort(a);
    77         for (int i = 0; i < a.length; i++) {
    78             System.out.print(a[i] + " ");
    79         }
    80     }
    81 }
  • 相关阅读:
    [转]给C++初学者的50个忠告
    [转]代理模式——代理模式的核心思想
    [转]单例模式——C++实现自动释放单例类的实例
    [转]代理模式——何时使用代理模式
    观察者模式——生动的气象信息订阅服务图示
    [转]单例模式——Java中互斥访问单一实例
    [转]C++ const变量使用技巧总结
    [转]用C++设计一个不能被继承的类
    ExtJs2.0学习系列(14)Ext.TreePanel之第三式(可增删改的树)
    ExtJs2.0学习系列(7)Ext.FormPanel之第四式(其他组件示例篇)
  • 原文地址:https://www.cnblogs.com/Unicron/p/9637488.html
Copyright © 2020-2023  润新知