归并排序及其应用

归并排序及其应用

归并排序采用分治和递归的思想完成排序,简单来说就是,将一个规模较大的排序问题转换成两个较小规模的问题,最终形成两个已经排好的序的子数组,进而进行归并即可。

  • 左路归并
  • 右路归并
  • 合并左右两路已排好序的数组
    具体代码如下:
public class MergeSort {
    /**
     * 归并排序 分治,整体排序=左右两边有序+合并后再排序
     * @param arr
     * @param left
     * @param right
     */
    public static void mergeSort(int[] arr,int left,int right){
        //递归结束条件,只有一个元素不用排序
        if(left==right){
            return ;
        }
        //左右两边排序,确定终中点
        int mid=left+((right-left)>>1);
        mergeSort(arr,left,mid);
        mergeSort(arr,mid+1,right);
        merge(arr,left,mid,right);
    }

    /**
     * 合并两个有序数组,需要一个额外的数组来进行存储
     * 两个指针分别指向两个有序数组的初始位置,一个指针指向新生成数组的位置
     * @param arr
     * @param left
     * @param mid
     * @param right
     */
    public static void merge(int[] arr,int left,int mid,int right){
        int[] help=new int[right-left+1];
        int i=0;
        int p1=left;
        int p2=mid+1;
        //两个数组都没有越界,就比较大小,将指针指向的最小的那个元素放在新数组中
        while(p1<=mid && p2<=right){
            help[i++]=arr[p1]<arr[p2]?arr[p1++]:arr[p2++];
        }
        //当一个数组的指针越界后,将另外的拷贝过来即可,两个中只有一个会越界
        while(p1<=mid){
            help[i++]=arr[p1++];
        }
        while(p2<=right){
            help[i++]=arr[p2++];
        }
        //重新写入原数组 注意数组从left开始
        for(i=0;i<help.length;i++){
            arr[left+i]=help[i];
        }
    }
    public static void main(String[] args) {
        int[] arr=new int[]{4,1,5,6,2,8,7};
        Common.printArr(arr);
        mergeSort(arr,0,arr.length-1);
        Common.printArr(arr);
    }
}

##应用
小和问题以及逆序对
求解一个数组中有多少逆序对,实际在归并过程中,会形成两个排好序的数组,可以进行加速。如果A中一个元素i大于B中一个元素j,那么A中从元素i之后的所有数都会与j形成逆序对。
整个逆序对的个数=左路逆序对的个数+右路个数+合并形成的个数
代码如下:

public class SmallSum {
    //应用 小和问题 与 逆序对
    //在一个数组中,每一个数左边比当前数小的数的和,称为小和,求一个数组的小和

    //笨办法,每个位置都去遍历一遍
    //使用归并排序加速
    /**
     * 归并 分治,整体的小和=左边排列产生的小和+右边的小和+左右两边有序产生的小和
     * @param arr
     * @param left
     * @param right
     */
    public static int mergeSort(int[] arr,int left,int right){
        //递归结束条件,只有一个元素不用排序
        if(left==right){
            return 0 ;
        }
        //左右两边排序,确定终中点
        int mid=left+((right-left)>>1);
        return mergeSort(arr,left,mid)
                +mergeSort(arr,mid+1,right)
                +merge(arr,left,mid,right);
    }

    /**
     * 合并两个有序数组,需要一个额外的数组来进行存储
     * 两个指针分别指向两个有序数组的初始位置,一个指针指向新生成数组的位置
     * @param arr
     * @param left
     * @param mid
     * @param right
     */
    public static int merge(int[] arr,int left,int mid,int right){
        int sum=0;
        int[] help=new int[right-left+1];
        int i=0;
        int p1=left;
        int p2=mid+1;
        //两个数组都没有越界,就比较大小,将指针指向的最小的那个元素放在新数组中
        while(p1<=mid && p2<=right){
            //p1 < p2  p2到后面全部是比p1大的数
            //逆序 左边的数比右边的数大,构成一组逆序对,左边部分有序后,后面所有数都能和其组成逆序对
            sum+=arr[p1]>arr[p2]?(mid-p1+1):0;
            //小和
//            sum+=arr[p1]<arr[p2]?arr[p1]*(right-p2+1):0;
            help[i++]=arr[p1]<arr[p2]?arr[p1++]:arr[p2++];
//            if(arr[p1]<arr[p2]){
//                help[i]=arr[p1];
//                sum+=help[i]*(right-p2+1);
//                p1++;i++;
//            }else{
//                help[i]=arr[p2];
//                p2++;i++;
//            }
        }
        //当一个数组的指针越界后,将另外的拷贝过来即可,两个中只有一个会越界
        while(p1<=mid){
            help[i++]=arr[p1++];
        }
        while(p2<=right){
            help[i++]=arr[p2++];
        }
        //重新写入原数组 注意数组从left开始
        for(i=0;i<help.length;i++){
            arr[left+i]=help[i];
        }
        return sum;
    }
    public static void main(String[] args) {
        int[] arr=new int[]{2,7,5,3,4};
        int a=mergeSort(arr,0,arr.length-1);
        System.out.println(a);
    }
}

可以发现与归并排序的代码几乎相同,因此基础排序算法很重要,尤其是其思想,这里主要是分治,治众如治寡,分数也。

复杂度分析与证明:
T(n)=2T(n/2)+O(n)
T(n)/n=T(n/2)/(n/2) +1;
递推得 T(n)/n=lgn;
数学归纳法

空间复杂度:
由于需要拷贝数组,进行排序,消耗空间,当数据量特别大的时候,内存消耗较大

优化:
在数据量比较小的时候,可以使用插入排序这种原地排序的算法,不占空间,时间复杂度也不高
在这里插入图片描述

还可能存在的情况是,归并的时候左右两边的子排列已经构成一个有序的数组,那么可以通过判断左边最大的那个数是否小于右边最小的那个数,来加速
在这里插入图片描述
除了递归实现外还可以自底向上去实现

public class MergeBU
{
 private static void merge(...)
 { /* as before */ }
 public static void sort(Comparable[] a)
 {
 int N = a.length;
 Comparable[] aux = new Comparable[N];
 //每次扩大一倍的距离
 for (int sz = 1; sz < N; sz = sz+sz)
 //最底层排好
 for (int lo = 0; lo < N-sz; lo += sz+sz){
 	merge(a, aux, lo, lo+sz-1, Math.min(lo+sz+sz-1, N-1));
 	}
 }
}

详细的非递归实现归并排序

 public static void sort(Comparable[] a ){
        Comparable[] b=new Comparable[a.length];
        int left=0,right=a.length-1;
        for(int size=1;size<a.length;size=2*size){
            for(left=0;left+size<a.length;left+=size*2){
                //left+2*size-1可能会越界 ***** [0,size-1] [size,2*size-1],[2*size,2*size-1+size-1+1,2*size+size*2-1]
                //[2*size,3size-1,4size-1]
                //[0,2size-1,4size-1]
                merge(a,b,left,left+size-1,Math.min(left+size*2-1,a.length-1));
            }
        }
    }

    public static int less(Comparable a,Comparable b){
        return a.compareTo(b);
    }

    public static void merge(Comparable[] a,Comparable[] b,int left,int mid,int right){
        for(int i=0;i<a.length;i++){
            b[i]=a[i];
        }
        int i=left,j=mid+1;
        //加速
        if(less(a[mid],a[mid+1])<0){
            return;
        }
        //注意K的取值,从left开始
        for(int k=left;k<=right;k++){
            if(i>mid){
                a[k]=b[j++];
            }else if(j>right){
                a[k]=b[i++];
            }else if(less(b[i],b[j])<0){
                a[k]=b[i++];
            }else{
                a[k]=b[j++];
            }
        }
    }
    public static void show(Comparable[] a){
        for(int i=0;i<a.length;i++){
            System.out.print(a[i]+" ");
        }
    }
    public static void main(String[] args) {
        Integer[] a1={5,2,12,8,3,9,0};
//        String[] a1={"anbd","dafe","add","ee"};
        sort(a1);
        show(a1);
    }
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页