归并排序——Java

归并排序

两个有序数组归并成一个更大的有序数组,就叫归并。

归并排序是一种递归排序算法,就对一个数组来说,可以先将它(递归地)分成两半分别排序,然后将结果归并起来。

原地归并的抽象方法

两个不同的有序数组如何实现归并?
一个最简单直接地方法就是创建一个最够大的第三数组,然后将两个有序数组的元素从大到小的排到第三数组中,这就叫原地归并。

/**
* 原地归并的抽象方法
* 归并,数组两边一定要是有序的
*/
public static void merge(Comparable[] a, int lo, int mid, int hi){
        //将a[lo..mid]和a[mid+1..hi]归并
        int i = lo, j = mid+1;

        //将a[lo..hi]复制到aux[lo..hi]
        for(int k=lo; k<=hi; k++){
            aux[k] = a[k];
        }

        //归并回到a[lo..hi]
        for(int k=lo; k<=hi; k++){
            if(i>mid){
                a[k] = aux[j++];
            }else if(j>hi){
                a[k] = aux[i++];
            }else if(less(aux[j], aux[i])){
                a[k] = aux[j++];
            }else{
                a[k] = aux[i++];
            }
        }
    }

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

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

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

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

4、数组2当前元素小于等于数组1当前元素(将数组2当前元素放入数组3)
在这里插入图片描述

自顶向下的归并排序

基于原地归并的抽象实现一种递归归并。

这段递归代码是归纳证明算法能正确地将数组排序的基础:**如果能将两个子数组排序,就能通过归并两个子数组来对整个数组排序。**这一切是通过递归实现的,也叫递归归并。

完整代码:

public class 归并排序 {

    private static Comparable[] aux;

    public static void sort(Comparable[] a)
    {
        aux = new Comparable[a.length];//一次性分配空间
        sort(a,0,a.length-1);
    }
    private static void sort(Comparable[] a, int lo, int hi)
    {
        if (hi<=lo)
            return;
        int mid = lo + (hi-lo)/2;
        //将左半边排序
        sort(a,lo, mid);
        //将右半边排序
        sort(a,mid+1, hi);
        //左右归并
        merge(a, lo, mid, hi);
    }
    //归并,数组两边一定要是有序的
    public static void merge(Comparable[] a, int lo, int mid, int hi) {
        int i = lo, j = mid + 1;
        Comparable[] aux = new Comparable[hi + 1];
        for (int k = lo; k <= hi; k++) {
            aux[k] = a[k];
        }
        for (int k = lo; k <= hi; k++) {
            if (i > mid) {
                a[k] = aux[j++];
            } else if (j > hi) {
                a[k] = aux[i++];
            } else if (less(aux[i], aux[j])) {
                a[k] = aux[i++];
            } else {
                a[k] = aux[j++];
            }
        }
    }
    public static boolean less(Comparable v, Comparable w){
        //对元素进行比较
        return v.compareTo(w)<0;
    }

    public static void exch(Comparable[] a,int i,int j){
        //交换元素
        Comparable t=a[i];
        a[i]=a[j];
        a[j]=t;
    }

    public static void show(Comparable[] a){
        //在单行中打印数组
        for(int i=0;i<a.length;i++){
            System.out.print(a[i]+"");
            System.out.println();
        }
    }

    public static boolean isSorted(Comparable[] a){
        //测试数组元素是否有序
        for(int i=0;i<a.length;i++){
            if(less(a[i],a[i-1])){
                return false;
            }
        }
        return true;
    }

    public static  void main(String[] args){
        String[] a={"4","2","5","2","7","5","3","2"};
        sort(a);
        assert isSorted(a);
        show(a);
    }
}

以一个数组为例展示调用过程:
在这里插入图片描述
sort(a,0,7)

将左半部分排序

左sort(a,0,3)
->左sort(a,0,1)
->>左sort(a,0,0)
->>右sort(a,1,1)
->>merge(a,0,0,1)
->右sort(a,2,3)
->>左sort(a,2,2)
->>右sort(a,3,3)
->>merge(a,2,2,3)
->merge(a,0,1,3)
右sort(a,4,7)
->左sort(a,4,5)
->>左sort(a,4,4)
->>右sort(a,5,5)
->>merge(a,4,4,5)
->右sort(a,6,7)
->>左sort(a,6,6)
->>右sort(a,7,7)
->>merge(a,6,6,7)
->merge(a,4,5,7)
merge(a,0,3,7)

算法第四版的配图:
在这里插入图片描述
对于长度为N的任意数组,自顶向下的归并排序需要 1/2NlogN 至 NlogN次比较
在这里插入图片描述
对于长度为N的任意数组,自顶向下的归并排序最多需要访问数组 6NlogN次
每次归并最多需要访问数组6N次,2N次复制,2N次将排好的元素移动回去,另外最多比较2N次,另最多有logN次归并。

自底向上的归并排序

先归并小数组,再归并大数组

public static void sort(Comparable[] a) {
        int n = a.length;
        aux = new Comparable[n];
        for (int sz = 1; sz < n; sz = sz + sz) //sz子数组的大小
            for (int lo = 0; lo < n - sz; lo += sz + sz) //lo子数组索引
                merge(a, lo, lo + sz - 1, Math.min(lo + 2 * sz - 1, n - 1));
    }

自底向上的归并排序会多次遍历整个数组,根据子数组大小进行两两归并。子数组的大小sz的初始值为1,每次加倍。最后一个子数组的大小只有在数组大小是sz的偶数倍的时候才会等于sz(否则它会比sz小)。

在这里插入图片描述
对于长度为N的任意数组,自底向上的归并排序需要1/2NlgN至NlgN次比较,最多访问数组6NlgN次。

复杂度

时间复杂度:NlogN
空间复杂度:
对数组来说,空间复杂度为N;对链表来说,递归方法空间复杂度为N,迭代方法为O(1)

三项优化

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

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

②测试数组是否有序

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

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

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

参考文章:
《算法第四版》
https://www.cnblogs.com/Unicron/p/9637488.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值